From 696ade15ca22b746d52386caba01937f179b079e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 13 May 2019 18:58:56 +0200 Subject: [PATCH 001/627] New way of starting arrange and rotation optimization. To prevent segfaults when exiting while processing is running. --- src/libslic3r/SLA/SLARotfinder.cpp | 4 +- src/slic3r/GUI/MainFrame.cpp | 6 +- src/slic3r/GUI/MainFrame.hpp | 4 +- src/slic3r/GUI/Plater.cpp | 279 +++++++++++++++++---------- src/slic3r/GUI/Plater.hpp | 1 + src/slic3r/GUI/ProgressStatusBar.cpp | 5 + src/slic3r/GUI/ProgressStatusBar.hpp | 1 + 7 files changed, 191 insertions(+), 109 deletions(-) diff --git a/src/libslic3r/SLA/SLARotfinder.cpp b/src/libslic3r/SLA/SLARotfinder.cpp index 1a91041b78..2e64059d68 100644 --- a/src/libslic3r/SLA/SLARotfinder.cpp +++ b/src/libslic3r/SLA/SLARotfinder.cpp @@ -44,7 +44,7 @@ std::array find_best_rotation(const ModelObject& modelobj, // call the status callback in each iteration but the actual value may be // the same for subsequent iterations (status goes from 0 to 100 but // iterations can be many more) - auto objfunc = [&emesh, &status, &statuscb, max_tries] + auto objfunc = [&emesh, &status, &statuscb, &stopcond, max_tries] (double rx, double ry, double rz) { EigenMesh3D& m = emesh; @@ -91,7 +91,7 @@ std::array find_best_rotation(const ModelObject& modelobj, } // report status - statuscb( unsigned(++status * 100.0/max_tries) ); + if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) ); return score; }; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 128aba16d0..7e897f9a04 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -54,7 +54,7 @@ DPIFrame(NULL, wxID_ANY, wxString(SLIC3R_BUILD_ID) + " " + _(L("based on Slic3r" #endif // _WIN32 // initialize status bar - m_statusbar = new ProgressStatusBar(this); + m_statusbar.reset(new ProgressStatusBar(this)); m_statusbar->embed(this); m_statusbar->set_status_text(_(L("Version")) + " " + SLIC3R_VERSION + @@ -107,6 +107,8 @@ DPIFrame(NULL, wxID_ANY, wxString(SLIC3R_BUILD_ID) + " " + _(L("based on Slic3r" // Also the application closes much faster without these unnecessary screen refreshes. // In addition, there were some crashes due to the Paint events sent to already destructed windows. this->Show(false); + + if(m_plater) m_plater->stop_jobs(); // Save the slic3r.ini.Usually the ini file is saved from "on idle" callback, // but in rare cases it may not have been called yet. @@ -136,6 +138,8 @@ DPIFrame(NULL, wxID_ANY, wxString(SLIC3R_BUILD_ID) + " " + _(L("based on Slic3r" update_ui_from_settings(); // FIXME (?) } +MainFrame::~MainFrame() {} + void MainFrame::init_tabpanel() { diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index f3d5826814..321fbe61b1 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -86,7 +86,7 @@ protected: public: MainFrame(); - ~MainFrame() {} + ~MainFrame(); Plater* plater() { return m_plater; } @@ -121,7 +121,7 @@ public: Plater* m_plater { nullptr }; wxNotebook* m_tabpanel { nullptr }; wxProgressDialog* m_progress_dialog { nullptr }; - ProgressStatusBar* m_statusbar { nullptr }; + std::unique_ptr m_statusbar; }; } // GUI diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index fd29bcfa80..f485c4016a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5,6 +5,8 @@ #include #include #include +#include + #include #include #include @@ -1219,8 +1221,107 @@ struct Plater::priv wxString project_filename; BackgroundSlicingProcess background_process; - bool arranging; - bool rotoptimizing; + + class Job: public wxEvtHandler { + int m_range = 100; + std::future m_ftr; + priv *m_plater = nullptr; + std::atomic m_running {false}, m_canceled {false}; + + void run() { + m_running.store(true); process(); m_running.store(false); + + // ensure to call the last status to finalize the job + update_status(status_range(), ""); + } + + protected: + + // status range for a particular job + virtual int status_range() const { return 100; } + + void update_status(int st, const wxString& msg = "") { + auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg); + wxQueueEvent(this, evt); + } + + priv& plater() { return *m_plater; } + bool was_canceled() const { return m_canceled.load(); } + + public: + + Job(priv *_plater): m_plater(_plater) { + Bind(wxEVT_THREAD, [this](const wxThreadEvent& evt){ + auto msg = evt.GetString(); + if(! msg.empty()) plater().statusbar()->set_status_text(msg); + + if(! m_range) return; + + plater().statusbar()->set_progress(evt.GetInt()); + if(evt.GetInt() == status_range()) { + + // set back the original range and cancel callback + plater().statusbar()->set_range(m_range); + plater().statusbar()->set_cancel_callback(); + wxEndBusyCursor(); + + // Do a full refresh of scene tree, including regenerating + // all the GLVolumes. FIXME The update function shall just + // reload the modified matrices. + if(! was_canceled()) plater().update(true); + + // dont do finalization again for the same process + m_range = 0; + } + }); + } + + virtual void process() = 0; + + void start() { // only if not running + if(! m_running.load()) { + m_range = plater().statusbar()->get_range(); + m_canceled.store(false); + plater().statusbar()->set_range(status_range()); + plater().statusbar()->set_cancel_callback( [this](){ + m_canceled.store(true); + }); + wxBeginBusyCursor(); + m_ftr = std::async(std::launch::async, &Job::run, this); + } + } + + bool join(int timeout_ms = 0) { + if(!m_ftr.valid()) return true; + + if(timeout_ms <= 0) + m_ftr.wait(); + else if(m_ftr.wait_for(std::chrono::milliseconds(timeout_ms)) == + std::future_status::timeout) + return false; + + return true; + } + + bool is_running() const { return m_running.load(); } + void cancel() { m_canceled.store(true); } + }; + + class ArrangeJob: public Job { + int count = 0; + public: + using Job::Job; + int status_range() const override { return count; } + void set_count(int c) { count = c; } + void process() override; + } arrange_job {this}; + + class RotoptimizeJob: public Job { + public: + using Job::Job; + void process() override; + } rotoptimize_job {this}; + bool delayed_scene_refresh; std::string delayed_error_message; @@ -1389,8 +1490,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) { this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - arranging = false; - rotoptimizing = false; background_process.set_fff_print(&fff_print); background_process.set_sla_print(&sla_print); background_process.set_gcode_preview_data(&gcode_preview_data); @@ -1564,7 +1663,7 @@ void Plater::priv::update_ui_from_settings() ProgressStatusBar* Plater::priv::statusbar() { - return main_frame->m_statusbar; + return main_frame->m_statusbar.get(); } std::string Plater::priv::get_config(const std::string &key) const @@ -2089,46 +2188,37 @@ void Plater::priv::mirror(Axis axis) void Plater::priv::arrange() { - if (arranging) { return; } - arranging = true; - Slic3r::ScopeGuard arranging_guard([this]() { arranging = false; }); + if(!arrange_job.is_running()) { + int count = 0; + for(auto obj : model.objects) count += int(obj->instances.size()); + arrange_job.set_count(count); + arrange_job.start(); + } +} - wxBusyCursor wait; +// This method will find an optimal orientation for the currently selected item +// Very similar in nature to the arrange method above... +void Plater::priv::sla_optimize_rotation() { + + rotoptimize_job.start(); +} - this->background_process.stop(); - - unsigned count = 0; - for(auto obj : model.objects) count += obj->instances.size(); - - auto prev_range = statusbar()->get_range(); - statusbar()->set_range(count); - - auto statusfn = [this, count] (unsigned st, const std::string& msg) { - /* // In case we would run the arrange asynchronously - wxCommandEvent event(EVT_PROGRESS_BAR); - event.SetInt(st); - event.SetString(msg); - wxQueueEvent(this->q, event.Clone()); */ - statusbar()->set_progress(count - st); - statusbar()->set_status_text(_(msg)); - - // ok, this is dangerous, but we are protected by the flag - // 'arranging' and the arrange button is also disabled. - // This call is needed for the cancel button to work. - wxYieldIfNeeded(); - }; - - statusbar()->set_cancel_callback([this, statusfn](){ - arranging = false; - statusfn(0, L("Arranging canceled")); - }); +void Plater::priv::ArrangeJob::process() { + + // TODO: we should decide whether to allow arrange when the search is + // running we should probably disable explicit slicing and background + // processing static const std::string arrangestr = L("Arranging"); + + auto& config = plater().config; + auto& view3D = plater().view3D; + auto& model = plater().model; // 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(printer_technology == ptFFF) { + if(plater().printer_technology == ptFFF) { dist = PrintConfig::min_object_distance(config); } @@ -2141,7 +2231,7 @@ void Plater::priv::arrange() Polyline bed; bed.points.reserve(bedpoints.size()); for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - statusfn(0, arrangestr); + update_status(0, arrangestr); arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); @@ -2157,67 +2247,34 @@ void Plater::priv::arrange() bed, hint, false, // create many piles not just one pile - [statusfn](unsigned st) { statusfn(st, arrangestr); }, - [this] () { return !arranging; }); + [this](unsigned st) { if(st > 0) update_status(count - int(st), arrangestr); }, + [this] () { return was_canceled(); }); } catch(std::exception& /*e*/) { - GUI::show_error(this->q, L("Could not arrange model objects! " - "Some geometries may be invalid.")); + GUI::show_error(plater().q, L("Could not arrange model objects! " + "Some geometries may be invalid.")); } + + update_status(count, was_canceled() ? L("Arranging canceled.") : L("Arranging done.")); // it remains to move the wipe tower: - view3D->get_canvas3d()->arrange_wipe_tower(wti); - - statusfn(0, L("Arranging done.")); - statusbar()->set_range(prev_range); - statusbar()->set_cancel_callback(); // remove cancel button - - // Do a full refresh of scene tree, including regenerating all the GLVolumes. - //FIXME The update function shall just reload the modified matrices. - update(true); + view3D->get_canvas3d()->arrange_wipe_tower(wti); } -// 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() { +void Plater::priv::RotoptimizeJob::process() +{ - // TODO: we should decide whether to allow arrange when the search is - // running we should probably disable explicit slicing and background - // processing - - if (rotoptimizing) { return; } - rotoptimizing = true; - Slic3r::ScopeGuard rotoptimizing_guard([this]() { rotoptimizing = false; }); - - int obj_idx = get_selected_object_idx(); + int obj_idx = plater().get_selected_object_idx(); if (obj_idx < 0) { return; } - ModelObject * o = model.objects[size_t(obj_idx)]; - - background_process.stop(); - - auto prev_range = statusbar()->get_range(); - statusbar()->set_range(100); - - auto stfn = [this] (unsigned st, const std::string& msg) { - statusbar()->set_progress(int(st)); - statusbar()->set_status_text(msg); - - // could be problematic, but we need the cancel button. - wxYieldIfNeeded(); - }; - - statusbar()->set_cancel_callback([this, stfn](){ - rotoptimizing = false; - stfn(0, L("Orientation search canceled")); - }); - + ModelObject * o = plater().model.objects[size_t(obj_idx)]; + auto r = sla::find_best_rotation( *o, .005f, - [stfn](unsigned s) { stfn(s, L("Searching for optimal orientation")); }, - [this](){ return !rotoptimizing; } + [this](unsigned s) { if(s < 100) update_status(int(s), L("Searching for optimal orientation")); }, + [this](){ return was_canceled(); } ); - const auto *bed_shape_opt = config->opt("bed_shape"); + const auto *bed_shape_opt = plater().config->opt("bed_shape"); assert(bed_shape_opt); auto& bedpoints = bed_shape_opt->values; @@ -2227,7 +2284,7 @@ void Plater::priv::sla_optimize_rotation() { double mindist = 6.0; // FIXME double offs = mindist / 2.0 - EPSILON; - if(rotoptimizing) // wasn't canceled + if(! was_canceled()) // wasn't canceled for(ModelInstance * oi : o->instances) { oi->set_rotation({r[X], r[Y], r[Z]}); @@ -2267,19 +2324,18 @@ void Plater::priv::sla_optimize_rotation() { Vec3d rt = oi->get_rotation(); rt(Z) += r; oi->set_rotation(rt); + + arr::WipeTowerInfo wti; // useless in SLA context + arr::find_new_position(plater().model, o->instances, + coord_t(mindist/SCALING_FACTOR), bed, wti); + + // Correct the z offset of the object which was corrupted be the rotation + o->ensure_on_bed(); + + update_status(100, L("Orientation found.")); + } else { + update_status(100, L("Orientation search canceled.")); } - - arr::WipeTowerInfo wti; // useless in SLA context - arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed, wti); - - // Correct the z offset of the object which was corrupted be the rotation - o->ensure_on_bed(); - - stfn(0, L("Orientation found.")); - statusbar()->set_range(prev_range); - statusbar()->set_cancel_callback(); - - update(true); } void Plater::priv::split_object() @@ -2455,7 +2511,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation) // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState. bool Plater::priv::restart_background_process(unsigned int state) { - if (arranging || rotoptimizing) { + if (arrange_job.is_running() || rotoptimize_job.is_running()) { // Avoid a race condition return false; } @@ -2686,7 +2742,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) { if (evt.status.percent >= -1) { - if (arranging || rotoptimizing) { + if (arrange_job.is_running() || rotoptimize_job.is_running()) { // Avoid a race condition return; } @@ -3140,7 +3196,7 @@ bool Plater::priv::can_fix_through_netfabb() const bool Plater::priv::can_increase_instances() const { - if (arranging || rotoptimizing) { + if (arrange_job.is_running() || rotoptimize_job.is_running()) { return false; } @@ -3150,7 +3206,7 @@ bool Plater::priv::can_increase_instances() const bool Plater::priv::can_decrease_instances() const { - if (arranging || rotoptimizing) { + if (arrange_job.is_running() || rotoptimize_job.is_running()) { return false; } @@ -3170,7 +3226,7 @@ bool Plater::priv::can_split_to_volumes() const bool Plater::priv::can_arrange() const { - return !model.objects.empty() && !arranging; + return !model.objects.empty() && !arrange_job.is_running(); } bool Plater::priv::can_layers_editing() const @@ -3292,6 +3348,21 @@ void Plater::load_files(const std::vector& input_files, bool load_m void Plater::update() { p->update(); } +void Plater::stop_jobs() +{ + static const int ABORT_WAIT_MAX_MS = 10000; + bool aborted = false; + + p->rotoptimize_job.cancel(); + aborted = p->rotoptimize_job.join(ABORT_WAIT_MAX_MS); + + p->arrange_job.cancel(); + aborted &= p->arrange_job.join(ABORT_WAIT_MAX_MS); + + if(!aborted) + BOOST_LOG_TRIVIAL(error) << "Could not abort an optimization job!"; +} + void Plater::update_ui_from_settings() { p->update_ui_from_settings(); } void Plater::select_view(const std::string& direction) { p->select_view(direction); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 3e70036864..8c62b742f4 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -143,6 +143,7 @@ public: void load_files(const std::vector& input_files, bool load_model = true, bool load_config = true); void update(); + void stop_jobs(); void select_view(const std::string& direction); void select_view_3D(const std::string& name); diff --git a/src/slic3r/GUI/ProgressStatusBar.cpp b/src/slic3r/GUI/ProgressStatusBar.cpp index b48c5732b5..f848e663d2 100644 --- a/src/slic3r/GUI/ProgressStatusBar.cpp +++ b/src/slic3r/GUI/ProgressStatusBar.cpp @@ -168,6 +168,11 @@ void ProgressStatusBar::set_status_text(const char *txt) this->set_status_text(wxString::FromUTF8(txt)); } +wxString ProgressStatusBar::get_status_text() const +{ + return self->GetStatusText(); +} + void ProgressStatusBar::show_cancel_button() { if(m_cancelbutton) m_cancelbutton->Show(); diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index 225b0331ef..fa21ba47c6 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -51,6 +51,7 @@ public: void set_status_text(const wxString& txt); void set_status_text(const std::string& txt); void set_status_text(const char *txt); + wxString get_status_text() const; // Temporary methods to satisfy Perl side void show_cancel_button(); From 170789a78715ba27303c38ac93687f1d3878f525 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 14 May 2019 10:34:11 +0200 Subject: [PATCH 002/627] Some comments and avoid race condition with background process. --- src/slic3r/GUI/Plater.cpp | 45 +++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f485c4016a..f82174ea9b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1222,11 +1222,22 @@ struct Plater::priv BackgroundSlicingProcess background_process; + // A class to handle UI jobs like arranging and optimizing rotation. + // These are not instant jobs, the user has to be informed about their + // state in the status progress indicator. On the other hand they are + // separated from the background slicing process. Ideally, these jobs should + // run when the background process is not running. + // + // TODO: A mechanism would be useful for blocking the plater interactions: + // objects would be frozen for the user. In case of arrange, an animation + // could be shown, or with the optimize orientations, partial results + // could be displayed. class Job: public wxEvtHandler { int m_range = 100; std::future m_ftr; priv *m_plater = nullptr; std::atomic m_running {false}, m_canceled {false}; + bool m_stop_slicing = false; void run() { m_running.store(true); process(); m_running.store(false); @@ -1240,6 +1251,7 @@ struct Plater::priv // 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); @@ -1250,7 +1262,9 @@ struct Plater::priv public: - Job(priv *_plater): m_plater(_plater) { + Job(priv *_plater, bool stop_slicing = false): + m_plater(_plater), m_stop_slicing(stop_slicing) + { Bind(wxEVT_THREAD, [this](const wxThreadEvent& evt){ auto msg = evt.GetString(); if(! msg.empty()) plater().statusbar()->set_status_text(msg); @@ -1278,19 +1292,39 @@ struct Plater::priv virtual void process() = 0; - void start() { // only if not running + void start() { // Start the job. No effect if the job is already running if(! m_running.load()) { + + if(m_stop_slicing) plater().background_process.stop(); + + // Save the current status indicatior range and push the new one m_range = plater().statusbar()->get_range(); - m_canceled.store(false); plater().statusbar()->set_range(status_range()); + + // init cancellation flag and set the cancel callback + m_canceled.store(false); plater().statusbar()->set_cancel_callback( [this](){ m_canceled.store(true); }); + + // Changing cursor to busy wxBeginBusyCursor(); - m_ftr = std::async(std::launch::async, &Job::run, this); + + try { // Execute the job + m_ftr = std::async(std::launch::async, &Job::run, this); + } catch(std::exception& ) { + update_status(status_range(), + _(L("ERROR: not enough resources to execute a new job."))); + } + + // The state changes will be undone when the process hits the + // last status value, in the status update handler (see ctor) } } + // To wait for the running job and join the threads. False is returned + // if the timeout has been reached and the job is still running. Call + // cancel() before this fn if you want to explicitly end the job. bool join(int timeout_ms = 0) { if(!m_ftr.valid()) return true; @@ -3676,6 +3710,9 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) void Plater::reslice() { + // Stop arrange and (or) optimize rotation tasks. + this->stop_jobs(); + //FIXME Don't reslice if export of G-code or sending to OctoPrint is running. // bitmask of UpdateBackgroundProcessReturnState unsigned int state = this->p->update_background_process(true); From 8c6304688de8575fa3634868b00cca59e1b2c8bd Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 16 May 2019 15:54:11 +0200 Subject: [PATCH 003/627] Camera refactoring: Frustrum calculations moved into Camera class --- src/slic3r/GUI/Camera.cpp | 43 ++++++++++++++++++++++----- src/slic3r/GUI/Camera.hpp | 7 +++-- src/slic3r/GUI/GLCanvas3D.cpp | 56 +++-------------------------------- 3 files changed, 44 insertions(+), 62 deletions(-) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index dd6cbefe19..5f7a7ce4b2 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -28,6 +28,8 @@ Camera::Camera() , inverted_phi(false) , m_theta(45.0f) , m_target(Vec3d::Zero()) + , m_view_matrix(Transform3d::Identity()) + , m_projection_matrix(Transform3d::Identity()) { } @@ -65,11 +67,6 @@ void Camera::set_theta(float theta, bool apply_limit) } } -void Camera::set_scene_box(const BoundingBoxf3& box) -{ - m_scene_box = box; -} - bool Camera::select_view(const std::string& direction) { const float* dir_vec = nullptr; @@ -111,13 +108,43 @@ void Camera::apply_view_matrix() const glsafe(::glLoadIdentity()); glsafe(::glRotatef(-m_theta, 1.0f, 0.0f, 0.0f)); // pitch - glsafe(::glRotatef(phi, 0.0f, 0.0f, 1.0f)); // yaw - glsafe(::glTranslated(-m_target(0), -m_target(1), -m_target(2))); + glsafe(::glRotatef(phi, 0.0f, 0.0f, 1.0f)); // yaw + + glsafe(::glTranslated(-m_target(0), -m_target(1), -m_target(2))); // target to origin glsafe(::glGetDoublev(GL_MODELVIEW_MATRIX, m_view_matrix.data())); } -void Camera::apply_ortho_projection(float x_min, float x_max, float y_min, float y_max, float z_min, float z_max) const +void Camera::apply_projection(const BoundingBoxf3& box) const +{ + switch (type) + { + case Ortho: + { + double w2 = (double)m_viewport[2]; + double h2 = (double)m_viewport[3]; + double two_zoom = 2.0 * zoom; + if (two_zoom != 0.0) + { + double inv_two_zoom = 1.0 / two_zoom; + w2 *= inv_two_zoom; + h2 *= inv_two_zoom; + } + + // FIXME: calculate a tighter value for depth will improve z-fighting + // Set at least some minimum depth in case the bounding box is empty to avoid an OpenGL driver error. + double depth = std::max(1.0, 5.0 * box.max_size()); + apply_ortho_projection(-w2, w2, -h2, h2, -depth, depth); + + break; + } +// case Perspective: +// { +// } + } +} + +void Camera::apply_ortho_projection(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max) const { glsafe(::glMatrixMode(GL_PROJECTION)); glsafe(::glLoadIdentity()); diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 6e1b539ab6..f0d22fd67a 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -46,7 +46,7 @@ public: void set_theta(float theta, bool apply_limit); const BoundingBoxf3& get_scene_box() const { return m_scene_box; } - void set_scene_box(const BoundingBoxf3& box); + void set_scene_box(const BoundingBoxf3& box){ m_scene_box = box; } bool select_view(const std::string& direction); @@ -62,7 +62,10 @@ public: void apply_viewport(int x, int y, unsigned int w, unsigned int h) const; void apply_view_matrix() const; - void apply_ortho_projection(float x_min, float x_max, float y_min, float y_max, float z_min, float z_max) const; + void apply_projection(const BoundingBoxf3& box) const; + +private: + void apply_ortho_projection(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max) const; }; } // GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index bb2cb5e2dd..ed9c1bd60a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1472,6 +1472,7 @@ BoundingBoxf3 GLCanvas3D::scene_bounding_box() const bb.min(2) = std::min(bb.min(2), -h); bb.max(2) = std::max(bb.max(2), h); } + return bb; } @@ -3593,59 +3594,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) // ensures that this canvas is current _set_current(); + + // updates camera m_camera.apply_viewport(0, 0, w, h); - - const BoundingBoxf3& bbox = _max_bounding_box(); - - switch (m_camera.type) - { - case Camera::Ortho: - { - float w2 = w; - float h2 = h; - float two_zoom = 2.0f * m_camera.zoom; - if (two_zoom != 0.0f) - { - float inv_two_zoom = 1.0f / two_zoom; - w2 *= inv_two_zoom; - h2 *= inv_two_zoom; - } - - // FIXME: calculate a tighter value for depth will improve z-fighting - // Set at least some minimum depth in case the bounding box is empty to avoid an OpenGL driver error. - float depth = std::max(1.f, 5.0f * (float)bbox.max_size()); - m_camera.apply_ortho_projection(-w2, w2, -h2, h2, -depth, depth); - - break; - } -// case Camera::Perspective: -// { -// float bbox_r = (float)bbox.radius(); -// float fov = PI * 45.0f / 180.0f; -// float fov_tan = tan(0.5f * fov); -// float cam_distance = 0.5f * bbox_r / fov_tan; -// m_camera.distance = cam_distance; -// -// float nr = cam_distance - bbox_r * 1.1f; -// float fr = cam_distance + bbox_r * 1.1f; -// if (nr < 1.0f) -// nr = 1.0f; -// -// if (fr < nr + 1.0f) -// fr = nr + 1.0f; -// -// float h2 = fov_tan * nr; -// float w2 = h2 * w / h; -// ::glFrustum(-w2, w2, -h2, h2, nr, fr); -// -// break; -// } - default: - { - throw std::runtime_error("Invalid camera type."); - break; - } - } + m_camera.apply_projection(_max_bounding_box()); m_dirty = false; } From baab5e49f1a95f48f4bafcdd9060b0c6f896cb00 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 18 May 2019 16:56:46 +0200 Subject: [PATCH 004/627] Mirroring parameters prepared for UI. Actual mirroring disabled, it will be refactored to maintain clarity of code. --- src/libslic3r/PrintConfig.cpp | 12 ++++++ src/libslic3r/PrintConfig.hpp | 4 ++ src/libslic3r/PrintExport.hpp | 22 ++++++++++ src/libslic3r/Rasterizer/Rasterizer.hpp | 7 +++- src/libslic3r/SLAPrint.cpp | 54 +++++++++++++------------ 5 files changed, 71 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 87ea263012..bd29916af3 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2258,6 +2258,18 @@ void PrintConfigDef::init_sla_params() def->min = 100; def->set_default_value(new ConfigOptionInt(1440)); + def = this->add("display_mirror_x", coBool); + def->full_label = L("Display mirroring in X axis"); + def->label = L("Mirror X"); + def->tooltip = L("Enable mirroring of output images in the X axis"); + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("display_mirror_y", coBool); + def->full_label = L("Display mirroring in Y axis"); + def->label = L("Mirror Y"); + def->tooltip = L("Enable mirroring of output images in the Y axis"); + def->set_default_value(new ConfigOptionBool(true)); + def = this->add("display_orientation", coEnum); def->label = L("Display orientation"); def->tooltip = L("Set the actual LCD display orientation inside the SLA printer." diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 1da22b377f..248b89e321 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1083,6 +1083,8 @@ public: ConfigOptionInt display_pixels_x; ConfigOptionInt display_pixels_y; ConfigOptionEnum display_orientation; + ConfigOptionBool display_mirror_x; + ConfigOptionBool display_mirror_y; ConfigOptionFloats relative_correction; ConfigOptionFloat absolute_correction; ConfigOptionFloat gamma_correction; @@ -1099,6 +1101,8 @@ protected: OPT_PTR(display_height); OPT_PTR(display_pixels_x); OPT_PTR(display_pixels_y); + OPT_PTR(display_mirror_x); + OPT_PTR(display_mirror_y); OPT_PTR(display_orientation); OPT_PTR(relative_correction); OPT_PTR(absolute_correction); diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp index f6537ed324..c7d462b4f3 100644 --- a/src/libslic3r/PrintExport.hpp +++ b/src/libslic3r/PrintExport.hpp @@ -210,6 +210,28 @@ public: { } + inline FilePrinter(const SLAPrinterConfig& cfg, const SLAMaterialConfig& mcfg, double layer_height) + { + double w = cfg.display_width.getFloat(); + double h = cfg.display_height.getFloat(); + auto pw = unsigned(cfg.display_pixels_x.getInt()); + auto ph = unsigned(cfg.display_pixels_y.getInt()); + + m_res = Raster::Resolution(pw, ph); + m_pxdim = Raster::PixelDim(w/pw, h/ph); + m_exp_time_s = mcfg.exposure_time.getFloat(); + m_exp_time_first_s = mcfg.initial_exposure_time.getFloat(); + m_layer_height = layer_height; + + auto ro = cfg.display_orientation.getInt(); + + // Here is the trick with the orientation. + m_o = ro == RO_LANDSCAPE? Raster::Origin::BOTTOM_LEFT : + Raster::Origin::TOP_LEFT; + + m_gamma = cfg.gamma_correction.getFloat(); + } + FilePrinter(const FilePrinter& ) = delete; FilePrinter(FilePrinter&& m): m_layers_rst(std::move(m.m_layers_rst)), diff --git a/src/libslic3r/Rasterizer/Rasterizer.hpp b/src/libslic3r/Rasterizer/Rasterizer.hpp index 3fffe1a365..d338a5e3b8 100644 --- a/src/libslic3r/Rasterizer/Rasterizer.hpp +++ b/src/libslic3r/Rasterizer/Rasterizer.hpp @@ -75,7 +75,10 @@ public: struct Resolution { unsigned width_px; unsigned height_px; - inline Resolution(unsigned w, unsigned h): width_px(w), height_px(h) {} + + inline Resolution(unsigned w = 0, unsigned h = 0): + width_px(w), height_px(h) {} + inline unsigned pixels() const /*noexcept*/ { return width_px * height_px; } @@ -85,7 +88,7 @@ public: struct PixelDim { double w_mm; double h_mm; - inline PixelDim(double px_width_mm, double px_height_mm ): + inline PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0): w_mm(px_width_mm), h_mm(px_height_mm) {} }; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 457be23ba5..6d2318bea2 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1008,7 +1008,7 @@ void SLAPrint::process() namespace sl = libnest2d::shapelike; // For algorithms // If the raster has vertical orientation, we will flip the coordinates - bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait; +// bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait; // Set up custom union and diff functions for clipper polygons auto polyunion = [] (const ClipperPolygons& subjects) @@ -1066,9 +1066,9 @@ void SLAPrint::process() // get polygons for all instances in the object auto get_all_polygons = - [flpXY](const ExPolygons& input_polygons, - const std::vector& instances, - bool is_lefthanded) + [](const ExPolygons& input_polygons, + const std::vector& instances, + bool is_lefthanded) { ClipperPolygons polygons; polygons.reserve(input_polygons.size() * instances.size()); @@ -1082,7 +1082,7 @@ void SLAPrint::process() // We need to reverse if flpXY OR is_lefthanded is true but // not if both are true which is a logical inequality (XOR) - bool needreverse = flpXY != is_lefthanded; + bool needreverse = /*flpXY !=*/ is_lefthanded; // should be a move poly.Contour.reserve(polygon.contour.size() + 1); @@ -1117,10 +1117,10 @@ void SLAPrint::process() sl::translate(poly, ClipperPoint{instances[i].shift(X), instances[i].shift(Y)}); - if (flpXY) { - for(auto& p : poly.Contour) std::swap(p.X, p.Y); - for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y); - } +// if (flpXY) { +// for(auto& p : poly.Contour) std::swap(p.X, p.Y); +// for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y); +// } polygons.emplace_back(std::move(poly)); } @@ -1289,27 +1289,29 @@ void SLAPrint::process() { // create a raster printer for the current print parameters // I don't know any better - auto& ocfg = m_objects.front()->m_config; - auto& matcfg = m_material_config; - auto& printcfg = m_printer_config; +// auto& ocfg = m_objects.front()->m_config; +// auto& matcfg = m_material_config; +// auto& printcfg = m_printer_config; - double w = printcfg.display_width.getFloat(); - double h = printcfg.display_height.getFloat(); - auto pw = unsigned(printcfg.display_pixels_x.getInt()); - auto ph = unsigned(printcfg.display_pixels_y.getInt()); - double lh = ocfg.layer_height.getFloat(); - double exp_t = matcfg.exposure_time.getFloat(); - double iexp_t = matcfg.initial_exposure_time.getFloat(); +// double w = printcfg.display_width.getFloat(); +// double h = printcfg.display_height.getFloat(); +// auto pw = unsigned(printcfg.display_pixels_x.getInt()); +// auto ph = unsigned(printcfg.display_pixels_y.getInt()); +// double lh = ocfg.layer_height.getFloat(); +// double exp_t = matcfg.exposure_time.getFloat(); +// double iexp_t = matcfg.initial_exposure_time.getFloat(); - double gamma = m_printer_config.gamma_correction.getFloat(); +// double gamma = m_printer_config.gamma_correction.getFloat(); - if(flpXY) { std::swap(w, h); std::swap(pw, ph); } +// if(flpXY) { std::swap(w, h); std::swap(pw, ph); } - m_printer.reset( - new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t, - flpXY? SLAPrinter::RO_PORTRAIT : - SLAPrinter::RO_LANDSCAPE, - gamma)); +// m_printer.reset( +// new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t, +// flpXY? SLAPrinter::RO_PORTRAIT : +// SLAPrinter::RO_LANDSCAPE, +// gamma)); + + m_printer.reset(new SLAPrinter(m_printer_config, m_material_config, m_default_object_config.layer_height.getFloat())); } // Allocate space for all the layers From bb73b59aa672632eab7e6f87514cb94688c5fb02 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Sat, 18 May 2019 22:45:24 +0200 Subject: [PATCH 005/627] Mirroring refactored. --- src/libslic3r/CMakeLists.txt | 7 +- src/libslic3r/PrintConfig.cpp | 4 +- src/libslic3r/PrintExport.hpp | 349 ------------------ src/libslic3r/Rasterizer/bicubic.h | 186 ---------- .../Rasterizer.cpp => SLA/SLARaster.cpp} | 99 +++-- .../Rasterizer.hpp => SLA/SLARaster.hpp} | 64 ++-- src/libslic3r/SLA/SLARasterWriter.cpp | 136 +++++++ src/libslic3r/SLA/SLARasterWriter.hpp | 139 +++++++ src/libslic3r/SLAPrint.cpp | 34 +- src/libslic3r/SLAPrint.hpp | 42 +-- 10 files changed, 380 insertions(+), 680 deletions(-) delete mode 100644 src/libslic3r/PrintExport.hpp delete mode 100644 src/libslic3r/Rasterizer/bicubic.h rename src/libslic3r/{Rasterizer/Rasterizer.cpp => SLA/SLARaster.cpp} (77%) rename src/libslic3r/{Rasterizer/Rasterizer.hpp => SLA/SLARaster.hpp} (68%) create mode 100644 src/libslic3r/SLA/SLARasterWriter.cpp create mode 100644 src/libslic3r/SLA/SLARasterWriter.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index ce93d95fab..3c32a22ed2 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -130,13 +130,10 @@ add_library(libslic3r STATIC Print.hpp PrintBase.cpp PrintBase.hpp - PrintExport.hpp PrintConfig.cpp PrintConfig.hpp PrintObject.cpp PrintRegion.cpp - Rasterizer/Rasterizer.hpp - Rasterizer/Rasterizer.cpp SLAPrint.cpp SLAPrint.hpp SLA/SLAAutoSupports.hpp @@ -173,6 +170,10 @@ add_library(libslic3r STATIC SLA/SLARotfinder.cpp SLA/SLABoostAdapter.hpp SLA/SLASpatIndex.hpp + SLA/SLARaster.hpp + SLA/SLARaster.cpp + SLA/SLARasterWriter.hpp + SLA/SLARasterWriter.cpp ) if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index bd29916af3..d000c2c2c5 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2262,13 +2262,13 @@ void PrintConfigDef::init_sla_params() def->full_label = L("Display mirroring in X axis"); def->label = L("Mirror X"); def->tooltip = L("Enable mirroring of output images in the X axis"); - def->set_default_value(new ConfigOptionBool(false)); + def->set_default_value(new ConfigOptionBool(true)); def = this->add("display_mirror_y", coBool); def->full_label = L("Display mirroring in Y axis"); def->label = L("Mirror Y"); def->tooltip = L("Enable mirroring of output images in the Y axis"); - def->set_default_value(new ConfigOptionBool(true)); + def->set_default_value(new ConfigOptionBool(false)); def = this->add("display_orientation", coEnum); def->label = L("Display orientation"); diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp deleted file mode 100644 index c7d462b4f3..0000000000 --- a/src/libslic3r/PrintExport.hpp +++ /dev/null @@ -1,349 +0,0 @@ -#ifndef PRINTEXPORT_HPP -#define PRINTEXPORT_HPP - -// For png export of the sliced model -#include -#include -#include - -#include -#include - -#include "Rasterizer/Rasterizer.hpp" -//#include -//#include //#include "tbb/mutex.h" - -namespace Slic3r { - -// Used for addressing parameters of FilePrinter::set_statistics() -enum ePrintStatistics -{ - psUsedMaterial = 0, - psNumFade, - psNumSlow, - psNumFast, - - psCnt -}; - -enum class FilePrinterFormat { - SLA_PNGZIP, - SVG -}; - -/* - * Interface for a file printer of the slices. Implementation can be an SVG - * or PNG printer or any other format. - * - * The format argument specifies the output format of the printer and it enables - * different implementations of this class template for each supported format. - * - */ -template -class FilePrinter { -public: - - // Draw a polygon which is a polygon inside a slice on the specified layer. - void draw_polygon(const ExPolygon& p, unsigned lyr); - void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr); - - // Tell the printer how many layers should it consider. - void layers(unsigned layernum); - - // Get the number of layers in the print. - unsigned layers() const; - - /* Switch to a particular layer. If there where less layers then the - * specified layer number than an appropriate number of layers will be - * allocated in the printer. - */ - void begin_layer(unsigned layer); - - // Allocate a new layer on top of the last and switch to it. - void begin_layer(); - - /* - * Finish the selected layer. It means that no drawing is allowed on that - * layer anymore. This fact can be used to prepare the file system output - * data like png comprimation and so on. - */ - void finish_layer(unsigned layer); - - // Finish the top layer. - void finish_layer(); - - // Save all the layers into the file (or dir) specified in the path argument - // An optional project name can be added to be used for the layer file names - void save(const std::string& path, const std::string& projectname = ""); - - // Save only the selected layer to the file specified in path argument. - void save_layer(unsigned lyr, const std::string& path); -}; - -// Provokes static_assert in the right way. -template struct VeryFalse { static const bool value = false; }; - -// This can be explicitly implemented in the gui layer or the default Zipper -// API in libslic3r with minz. -template class LayerWriter { -public: - - LayerWriter(const std::string& /*zipfile_path*/) - { - static_assert(VeryFalse::value, - "No layer writer implementation provided!"); - } - - // Should create a new file within the zip with the given filename. It - // should also finish any previous entry. - void next_entry(const std::string& /*fname*/) {} - - // Should create a new file within the archive and write the provided data. - void binary_entry(const std::string& /*fname*/, - const std::uint8_t* buf, size_t len); - - // Test whether the object can still be used for writing. - bool is_ok() { return false; } - - // Write some data (text) into the current file (entry) within the archive. - template LayerWriter& operator<<(T&& /*arg*/) { - return *this; - } - - // Flush the current entry into the archive. - void finalize() {} -}; - -// Implementation for PNG raster output -// Be aware that if a large number of layers are allocated, it can very well -// exhaust the available memory especially on 32 bit platform. -template<> class FilePrinter -{ - struct Layer { - Raster raster; - RawBytes rawbytes; - - Layer() {} - - Layer(const Layer&) = delete; - Layer(Layer&& m): - raster(std::move(m.raster)) {} - }; - - // We will save the compressed PNG data into stringstreams which can be done - // in parallel. Later we can write every layer to the disk sequentially. - std::vector m_layers_rst; - Raster::Resolution m_res; - Raster::PixelDim m_pxdim; - double m_exp_time_s = .0, m_exp_time_first_s = .0; - double m_layer_height = .0; - Raster::Origin m_o = Raster::Origin::TOP_LEFT; - double m_gamma; - - double m_used_material = 0.0; - int m_cnt_fade_layers = 0; - int m_cnt_slow_layers = 0; - int m_cnt_fast_layers = 0; - - std::string createIniContent(const std::string& projectname) { - using std::string; - using std::to_string; - - auto expt_str = to_string(m_exp_time_s); - auto expt_first_str = to_string(m_exp_time_first_s); - auto layerh_str = to_string(m_layer_height); - - const std::string cnt_fade_layers = to_string(m_cnt_fade_layers); - const std::string cnt_slow_layers = to_string(m_cnt_slow_layers); - const std::string cnt_fast_layers = to_string(m_cnt_fast_layers); - const std::string used_material = to_string(m_used_material); - - return string( - "action = print\n" - "jobDir = ") + projectname + "\n" + - "expTime = " + expt_str + "\n" - "expTimeFirst = " + expt_first_str + "\n" - "numFade = " + cnt_fade_layers + "\n" - "layerHeight = " + layerh_str + "\n" - "usedMaterial = " + used_material + "\n" - "numSlow = " + cnt_slow_layers + "\n" - "numFast = " + cnt_fast_layers + "\n"; - } - -public: - - enum RasterOrientation { - RO_LANDSCAPE, - RO_PORTRAIT - }; - - // We will play with the raster's coordinate origin parameter. When the - // printer should print in landscape mode it should have the Y axis flipped - // because the layers should be displayed upside down. PNG has its - // coordinate origin in the top-left corner so normally the Raster objects - // should be instantiated with the TOP_LEFT flag. However, in landscape mode - // we do want the pictures to be upside down so we will make BOTTOM_LEFT - // type rasters and the PNG format will do the flipping automatically. - - // In case of portrait images, we have to rotate the image by a 90 degrees - // and flip the y axis. To get the correct upside-down orientation of the - // slice images, we can flip the x and y coordinates of the input polygons - // and do the Y flipping of the image. This will generate the correct - // orientation in portrait mode. - - inline FilePrinter(double width_mm, double height_mm, - unsigned width_px, unsigned height_px, - double layer_height, - double exp_time, double exp_time_first, - RasterOrientation ro = RO_PORTRAIT, - double gamma = 1.0): - m_res(width_px, height_px), - m_pxdim(width_mm/width_px, height_mm/height_px), - m_exp_time_s(exp_time), - m_exp_time_first_s(exp_time_first), - m_layer_height(layer_height), - - // Here is the trick with the orientation. - m_o(ro == RO_LANDSCAPE? Raster::Origin::BOTTOM_LEFT : - Raster::Origin::TOP_LEFT ), - m_gamma(gamma) - { - } - - inline FilePrinter(const SLAPrinterConfig& cfg, const SLAMaterialConfig& mcfg, double layer_height) - { - double w = cfg.display_width.getFloat(); - double h = cfg.display_height.getFloat(); - auto pw = unsigned(cfg.display_pixels_x.getInt()); - auto ph = unsigned(cfg.display_pixels_y.getInt()); - - m_res = Raster::Resolution(pw, ph); - m_pxdim = Raster::PixelDim(w/pw, h/ph); - m_exp_time_s = mcfg.exposure_time.getFloat(); - m_exp_time_first_s = mcfg.initial_exposure_time.getFloat(); - m_layer_height = layer_height; - - auto ro = cfg.display_orientation.getInt(); - - // Here is the trick with the orientation. - m_o = ro == RO_LANDSCAPE? Raster::Origin::BOTTOM_LEFT : - Raster::Origin::TOP_LEFT; - - m_gamma = cfg.gamma_correction.getFloat(); - } - - FilePrinter(const FilePrinter& ) = delete; - FilePrinter(FilePrinter&& m): - m_layers_rst(std::move(m.m_layers_rst)), - m_res(m.m_res), - m_pxdim(m.m_pxdim) {} - - inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } - inline unsigned layers() const { return unsigned(m_layers_rst.size()); } - - inline void draw_polygon(const ExPolygon& p, unsigned lyr) { - assert(lyr < m_layers_rst.size()); - m_layers_rst[lyr].raster.draw(p); - } - - inline void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr) { - assert(lyr < m_layers_rst.size()); - m_layers_rst[lyr].raster.draw(p); - } - - inline void begin_layer(unsigned lyr) { - if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); - m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_o, m_gamma); - } - - inline void begin_layer() { - m_layers_rst.emplace_back(); - m_layers_rst.front().raster.reset(m_res, m_pxdim, m_o, m_gamma); - } - - inline void finish_layer(unsigned lyr_id) { - assert(lyr_id < m_layers_rst.size()); - m_layers_rst[lyr_id].rawbytes = - m_layers_rst[lyr_id].raster.save(Raster::Compression::PNG); - m_layers_rst[lyr_id].raster.reset(); - } - - inline void finish_layer() { - if(!m_layers_rst.empty()) { - m_layers_rst.back().rawbytes = - m_layers_rst.back().raster.save(Raster::Compression::PNG); - m_layers_rst.back().raster.reset(); - } - } - - template - inline void save(const std::string& fpath, const std::string& prjname = "") - { - try { - LayerWriter writer(fpath); - if(!writer.is_ok()) return; - - std::string project = prjname.empty()? - boost::filesystem::path(fpath).stem().string() : prjname; - - writer.next_entry("config.ini"); - if(!writer.is_ok()) return; - - writer << createIniContent(project); - - for(unsigned i = 0; i < m_layers_rst.size() && writer.is_ok(); i++) - { - if(m_layers_rst[i].rawbytes.size() > 0) { - char lyrnum[6]; - std::sprintf(lyrnum, "%.5d", i); - auto zfilename = project + lyrnum + ".png"; - if(!writer.is_ok()) break; - - writer.binary_entry(zfilename, - m_layers_rst[i].rawbytes.data(), - m_layers_rst[i].rawbytes.size()); - } - } - - writer.finalize(); - } catch(std::exception& e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - // Rethrow the exception - throw; - } - } - - void save_layer(unsigned lyr, const std::string& path) { - unsigned i = lyr; - assert(i < m_layers_rst.size()); - - char lyrnum[6]; - std::sprintf(lyrnum, "%.5d", lyr); - std::string loc = path + "layer" + lyrnum + ".png"; - - std::fstream out(loc, std::fstream::out | std::fstream::binary); - if(out.good()) { - m_layers_rst[i].raster.save(out, Raster::Compression::PNG); - } else { - BOOST_LOG_TRIVIAL(error) << "Can't create file for layer"; - } - - out.close(); - m_layers_rst[i].raster.reset(); - } - - void set_statistics(const std::vector statistics) - { - if (statistics.size() != psCnt) - return; - - m_used_material = statistics[psUsedMaterial]; - m_cnt_fade_layers = int(statistics[psNumFade]); - m_cnt_slow_layers = int(statistics[psNumSlow]); - m_cnt_fast_layers = int(statistics[psNumFast]); - } -}; - -} - -#endif // PRINTEXPORT_HPP diff --git a/src/libslic3r/Rasterizer/bicubic.h b/src/libslic3r/Rasterizer/bicubic.h deleted file mode 100644 index 870d00dbd6..0000000000 --- a/src/libslic3r/Rasterizer/bicubic.h +++ /dev/null @@ -1,186 +0,0 @@ -#ifndef BICUBIC_HPP -#define BICUBIC_HPP - -#include -#include -#include - -#include - -namespace Slic3r { - -namespace BicubicInternal { - // Linear kernel, to be able to test cubic methods with hat kernels. - template - struct LinearKernel - { - typedef T FloatType; - - static T a00() { return T(0.); } - static T a01() { return T(0.); } - static T a02() { return T(0.); } - static T a03() { return T(0.); } - static T a10() { return T(1.); } - static T a11() { return T(-1.); } - static T a12() { return T(0.); } - static T a13() { return T(0.); } - static T a20() { return T(0.); } - static T a21() { return T(1.); } - static T a22() { return T(0.); } - static T a23() { return T(0.); } - static T a30() { return T(0.); } - static T a31() { return T(0.); } - static T a32() { return T(0.); } - static T a33() { return T(0.); } - }; - - // Interpolation kernel aka Catmul-Rom aka Keyes kernel. - template - struct CubicCatmulRomKernel - { - typedef T FloatType; - - static T a00() { return 0; } - static T a01() { return (T)-0.5; } - static T a02() { return (T) 1.; } - static T a03() { return (T)-0.5; } - static T a10() { return (T) 1.; } - static T a11() { return 0; } - static T a12() { return (T)-5./2.; } - static T a13() { return (T) 3./2.; } - static T a20() { return 0; } - static T a21() { return (T) 0.5; } - static T a22() { return (T) 2.; } - static T a23() { return (T)-3./2.; } - static T a30() { return 0; } - static T a31() { return 0; } - static T a32() { return (T)-0.5; } - static T a33() { return (T) 0.5; } - }; - - // B-spline kernel - template - struct CubicBSplineKernel - { - typedef T FloatType; - - static T a00() { return (T) 1./6.; } - static T a01() { return (T) -3./6.; } - static T a02() { return (T) 3./6.; } - static T a03() { return (T) -1./6.; } - static T a10() { return (T) 4./6.; } - static T a11() { return 0; } - static T a12() { return (T) -6./6.; } - static T a13() { return (T) 3./6.; } - static T a20() { return (T) 1./6.; } - static T a21() { return (T) 3./6.; } - static T a22() { return (T) 3./6.; } - static T a23() { return (T)- 3./6.; } - static T a30() { return 0; } - static T a31() { return 0; } - static T a32() { return 0; } - static T a33() { return (T) 1./6.; } - }; - - template - inline T clamp(T a, T lower, T upper) - { - return (a < lower) ? lower : - (a > upper) ? upper : a; - } -} - -template -struct CubicKernel -{ - typedef typename KERNEL KernelInternal; - typedef typename KERNEL::FloatType FloatType; - - static FloatType kernel(FloatType x) - { - x = fabs(x); - if (x >= (FloatType)2.) - return 0.0f; - if (x <= (FloatType)1.) { - FloatType x2 = x * x; - FloatType x3 = x2 * x; - return KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3; - } - assert(x > (FloatType)1. && x < (FloatType)2.); - x -= (FloatType)1.; - FloatType x2 = x * x; - FloatType x3 = x2 * x; - return KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3; - } - - static FloatType interpolate(FloatType f0, FloatType f1, FloatType f2, FloatType f3, FloatType x) - { - const FloatType x2 = x*x; - const FloatType x3 = x*x*x; - return f0*(KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3) + - f1*(KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3) + - f2*(KERNEL::a20() + KERNEL::a21() * x + KERNEL::a22() * x2 + KERNEL::a23() * x3) + - f3*(KERNEL::a30() + KERNEL::a31() * x + KERNEL::a32() * x2 + KERNEL::a33() * x3); - } -}; - -// Linear splines -typedef CubicKernel> LinearKernelf; -typedef CubicKernel> LinearKerneld; -// Catmul-Rom splines -typedef CubicKernel> CubicCatmulRomKernelf; -typedef CubicKernel> CubicCatmulRomKerneld; -typedef CubicKernel> CubicInterpolationKernelf; -typedef CubicKernel> CubicInterpolationKerneld; -// Cubic B-splines -typedef CubicKernel> CubicBSplineKernelf; -typedef CubicKernel> CubicBSplineKerneld; - -template -static float cubic_interpolate(const Eigen::ArrayBase &F, const typename KERNEL::FloatType pt, const typename KERNEL::FloatType dx) -{ - typedef typename KERNEL::FloatType T; - const int w = int(F.size()); - const int ix = (int)floor(pt); - const T s = pt - (T)ix; - - if (ix > 1 && ix + 2 < w) { - // Inside the fully interpolated region. - return KERNEL::interpolate(F[ix - 1], F[ix], F[ix + 1], F[ix + 2], s); - } - // Transition region. Extend with a constant function. - auto f = [&F, w](x) { return F[BicubicInternal::clamp(x, 0, w - 1)]; } - return KERNEL::interpolate(f(ix - 1), f(ix), f(ix + 1), f(ix + 2), s); -} - -template -static float bicubic_interpolate(const Eigen::MatrixBase &F, const Eigen::Matrix &pt, const typename KERNEL::FloatType dx) -{ - typedef typename KERNEL::FloatType T; - const int w = F.cols(); - const int h = F.rows(); - const int ix = (int)floor(pt[0]); - const int iy = (int)floor(pt[1]); - const T s = pt[0] - (T)ix; - const T t = pt[1] - (T)iy; - - if (ix > 1 && ix + 2 < w && iy > 1 && iy + 2 < h) { - // Inside the fully interpolated region. - return KERNEL::interpolate( - KERNEL::interpolate(F(ix-1,iy-1),F(ix ,iy-1),F(ix+1,iy-1),F(ix+2,iy-1),s), - KERNEL::interpolate(F(ix-1,iy ),F(ix ,iy ),F(ix+1,iy ),F(ix+2,iy ),s), - KERNEL::interpolate(F(ix-1,iy+1),F(ix ,iy+1),F(ix+1,iy+1),F(ix+2,iy+1),s), - KERNEL::interpolate(F(ix-1,iy+2),F(ix ,iy+2),F(ix+1,iy+2),F(ix+2,iy+2),s),t); - } - // Transition region. Extend with a constant function. - auto f = [&f, w, h](int x, int y) { return F(BicubicInternal::clamp(x,0,w-1),BicubicInternal::clamp(y,0,h-1)); } - return KERNEL::interpolate( - KERNEL::interpolate(f(ix-1,iy-1),f(ix ,iy-1),f(ix+1,iy-1),f(ix+2,iy-1),s), - KERNEL::interpolate(f(ix-1,iy ),f(ix ,iy ),f(ix+1,iy ),f(ix+2,iy ),s), - KERNEL::interpolate(f(ix-1,iy+1),f(ix ,iy+1),f(ix+1,iy+1),f(ix+2,iy+1),s), - KERNEL::interpolate(f(ix-1,iy+2),f(ix ,iy+2),f(ix+1,iy+2),f(ix+2,iy+2),s),t); -} - -} // namespace Slic3r - -#endif /* BICUBIC_HPP */ diff --git a/src/libslic3r/Rasterizer/Rasterizer.cpp b/src/libslic3r/SLA/SLARaster.cpp similarity index 77% rename from src/libslic3r/Rasterizer/Rasterizer.cpp rename to src/libslic3r/SLA/SLARaster.cpp index 6384a241f3..fba1f46f33 100644 --- a/src/libslic3r/Rasterizer/Rasterizer.cpp +++ b/src/libslic3r/SLA/SLARaster.cpp @@ -1,5 +1,8 @@ -#include "Rasterizer.hpp" -#include +#ifndef SLARASTER_CPP +#define SLARASTER_CPP + +#include "SLARaster.hpp" +#include "libslic3r/ExPolygon.hpp" #include // For rasterizing @@ -19,11 +22,13 @@ namespace Slic3r { -const Polygon& contour(const ExPolygon& p) { return p.contour; } -const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } +inline const Polygon& contour(const ExPolygon& p) { return p.contour; } +inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } -const Polygons& holes(const ExPolygon& p) { return p.holes; } -const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } +inline const Polygons& holes(const ExPolygon& p) { return p.holes; } +inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } + +namespace sla { class Raster::Impl { public: @@ -39,7 +44,7 @@ public: static const TPixel ColorWhite; static const TPixel ColorBlack; - using Origin = Raster::Origin; + using Format = Raster::Format; private: Raster::Resolution m_resolution; @@ -52,16 +57,20 @@ private: TRendererAA m_renderer; std::function m_gammafn; - Origin m_o; + std::array m_mirror; inline void flipy(agg::path_storage& path) const { path.flip_y(0, m_resolution.height_px); } + + inline void flipx(agg::path_storage& path) const { + path.flip_x(0, m_resolution.width_px); + } public: inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd, - Origin o, double gamma = 1.0): + const std::array& mirror, double gamma = 1.0): m_resolution(res), // m_pxdim(pd), m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm), @@ -72,7 +81,7 @@ public: m_pixfmt(m_rbuf), m_raw_renderer(m_pixfmt), m_renderer(m_raw_renderer), - m_o(o) + m_mirror(mirror) { m_renderer.color(ColorWhite); @@ -81,6 +90,18 @@ public: clear(); } + + inline Impl(const Raster::Resolution& res, + const Raster::PixelDim &pd, + Format fmt, + double gamma = 1.0): + Impl(res, pd, {false, false}, gamma) + { + switch (fmt) { + case Format::PNG: m_mirror = {false, true}; break; + case Format::RAW: m_mirror = {false, false}; break; + } + } template void draw(const P &poly) { agg::rasterizer_scanline_aa<> ras; @@ -89,14 +110,16 @@ public: ras.gamma(m_gammafn); auto&& path = to_path(contour(poly)); - - if(m_o == Origin::TOP_LEFT) flipy(path); + + if(m_mirror[X]) flipx(path); + if(m_mirror[Y]) flipy(path); ras.add_path(path); for(auto& h : holes(poly)) { auto&& holepath = to_path(h); - if(m_o == Origin::TOP_LEFT) flipy(holepath); + if(m_mirror[X]) flipx(holepath); + if(m_mirror[Y]) flipy(holepath); ras.add_path(holepath); } @@ -110,9 +133,7 @@ public: inline TBuffer& buffer() { return m_buf; } inline const Raster::Resolution resolution() { return m_resolution; } - - inline Origin origin() const /*noexcept*/ { return m_o; } - + private: inline double getPx(const Point& p) { return p(0) * m_pxdim_scaled.w_mm; @@ -154,30 +175,23 @@ private: const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255); const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0); -Raster::Raster(const Resolution &r, const PixelDim &pd, Origin o, double g): - m_impl(new Impl(r, pd, o, g)) {} +Raster::Raster() = default; +Raster::~Raster() = default; +Raster::Raster(Raster &&m) = default; +Raster& Raster::operator=(Raster&&) = default; -Raster::Raster() {} - -Raster::~Raster() {} - -Raster::Raster(Raster &&m): - m_impl(std::move(m.m_impl)) {} - -void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, - double g) +void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, + Format fmt, double gamma) { - // Free up the unnecessary memory and make sure it stays clear after - // an exception - auto o = m_impl? m_impl->origin() : Origin::TOP_LEFT; - reset(r, pd, o, g); + m_impl.reset(); + m_impl.reset(new Impl(r, pd, fmt, gamma)); } void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, - Raster::Origin o, double gamma) + const std::array& mirror, double gamma) { m_impl.reset(); - m_impl.reset(new Impl(r, pd, o, gamma)); + m_impl.reset(new Impl(r, pd, mirror, gamma)); } void Raster::reset() @@ -208,13 +222,13 @@ void Raster::draw(const ClipperLib::Polygon &poly) m_impl->draw(poly); } -void Raster::save(std::ostream& stream, Compression comp) +void Raster::save(std::ostream& stream, Format fmt) { assert(m_impl); if(!stream.good()) return; - switch(comp) { - case Compression::PNG: { + switch(fmt) { + case Format::PNG: { auto& b = m_impl->buffer(); size_t out_len = 0; void * rawdata = tdefl_write_image_to_png_file_in_memory( @@ -231,7 +245,7 @@ void Raster::save(std::ostream& stream, Compression comp) break; } - case Compression::RAW: { + case Format::RAW: { stream << "P5 " << m_impl->resolution().width_px << " " << m_impl->resolution().height_px << " " @@ -244,14 +258,14 @@ void Raster::save(std::ostream& stream, Compression comp) } } -RawBytes Raster::save(Raster::Compression comp) +RawBytes Raster::save(Format fmt) { assert(m_impl); std::vector data; size_t s = 0; - switch(comp) { - case Compression::PNG: { + switch(fmt) { + case Format::PNG: { void *rawdata = tdefl_write_image_to_png_file_in_memory( m_impl->buffer().data(), int(resolution().width_px), @@ -265,7 +279,7 @@ RawBytes Raster::save(Raster::Compression comp) MZ_FREE(rawdata); break; } - case Compression::RAW: { + case Format::RAW: { auto header = std::string("P5 ") + std::to_string(m_impl->resolution().width_px) + " " + std::to_string(m_impl->resolution().height_px) + " " + "255 "; @@ -287,3 +301,6 @@ RawBytes Raster::save(Raster::Compression comp) } } +} + +#endif // SLARASTER_CPP diff --git a/src/libslic3r/Rasterizer/Rasterizer.hpp b/src/libslic3r/SLA/SLARaster.hpp similarity index 68% rename from src/libslic3r/Rasterizer/Rasterizer.hpp rename to src/libslic3r/SLA/SLARaster.hpp index d338a5e3b8..03fb06d6c8 100644 --- a/src/libslic3r/Rasterizer/Rasterizer.hpp +++ b/src/libslic3r/SLA/SLARaster.hpp @@ -1,5 +1,5 @@ -#ifndef RASTERIZER_HPP -#define RASTERIZER_HPP +#ifndef SLARASTER_HPP +#define SLARASTER_HPP #include #include @@ -8,10 +8,12 @@ namespace ClipperLib { struct Polygon; } -namespace Slic3r { +namespace Slic3r { class ExPolygon; +namespace sla { + // Raw byte buffer paired with its size. Suitable for compressed PNG data. class RawBytes { @@ -23,19 +25,24 @@ public: size_t size() const { return m_buffer.size(); } const uint8_t * data() { return m_buffer.data(); } + + RawBytes(const RawBytes&) = delete; + RawBytes(RawBytes&&) = default; + RawBytes& operator=(const RawBytes&) = delete; + RawBytes& operator=(RawBytes&&) = default; // ///////////////////////////////////////////////////////////////////////// // FIXME: the following is needed for MSVC2013 compatibility // ///////////////////////////////////////////////////////////////////////// - RawBytes(const RawBytes&) = delete; - RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {} +// RawBytes(const RawBytes&) = delete; +// RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {} - RawBytes& operator=(const RawBytes&) = delete; - RawBytes& operator=(RawBytes&& mv) { - m_buffer = std::move(mv.m_buffer); - return *this; - } +// RawBytes& operator=(const RawBytes&) = delete; +// RawBytes& operator=(RawBytes&& mv) { +// m_buffer = std::move(mv.m_buffer); +// return *this; +// } // ///////////////////////////////////////////////////////////////////////// }; @@ -54,23 +61,11 @@ class Raster { public: /// Supported compression types - enum class Compression { + enum class Format { RAW, //!> Uncompressed pixel data PNG //!> PNG compression }; - /// The Rasterizer expects the input polygons to have their coordinate - /// system origin in the bottom left corner. If the raster is then - /// configured with the TOP_LEFT origin parameter (in the constructor) than - /// it will flip the Y axis in output to maintain the correct orientation. - /// This is the default case with PNG images. They have the origin in the - /// top left corner. Without the flipping, the image would be upside down - /// with the scaled (clipper) coordinate system of the input polygons. - enum class Origin { - TOP_LEFT, - BOTTOM_LEFT - }; - /// Type that represents a resolution in pixels. struct Resolution { unsigned width_px; @@ -93,19 +88,21 @@ public: }; /// Constructor taking the resolution and the pixel dimension. - Raster(const Resolution& r, const PixelDim& pd, - Origin o = Origin::BOTTOM_LEFT, double gamma = 1.0); + template Raster(Args...args) { + reset(std::forward(args)...); + } Raster(); Raster(const Raster& cpy) = delete; Raster& operator=(const Raster& cpy) = delete; Raster(Raster&& m); + Raster& operator=(Raster&&); ~Raster(); /// Reallocated everything for the given resolution and pixel dimension. - void reset(const Resolution& r, const PixelDim& pd, double gamma = 1.0); - void reset(const Resolution& r, const PixelDim& pd, Origin o, double gamma); - + void reset(const Resolution&, const PixelDim&, const std::array& mirror, double gamma = 1.0); + void reset(const Resolution& r, const PixelDim& pd, Format o, double gamma = 1.0); + /** * Release the allocated resources. Drawing in this state ends in * unspecified behavior. @@ -123,10 +120,13 @@ public: void draw(const ClipperLib::Polygon& poly); /// Save the raster on the specified stream. - void save(std::ostream& stream, Compression comp = Compression::RAW); + void save(std::ostream& stream, Format = Format::PNG); - RawBytes save(Compression comp = Compression::RAW); + /// Save into a continuous byte stream which is returned. + RawBytes save(Format fmt = Format::PNG); }; -} -#endif // RASTERIZER_HPP +} // sla +} // Slic3r + +#endif // SLARASTER_HPP diff --git a/src/libslic3r/SLA/SLARasterWriter.cpp b/src/libslic3r/SLA/SLARasterWriter.cpp new file mode 100644 index 0000000000..b2fe0c72c3 --- /dev/null +++ b/src/libslic3r/SLA/SLARasterWriter.cpp @@ -0,0 +1,136 @@ +#include "SLARasterWriter.hpp" +#include "libslic3r/Zipper.hpp" +#include "ExPolygon.hpp" +#include + +#include +#include + +namespace Slic3r { namespace sla { + +std::string SLARasterWriter::createIniContent(const std::string& projectname) const +{ + auto expt_str = std::to_string(m_exp_time_s); + auto expt_first_str = std::to_string(m_exp_time_first_s); + auto layerh_str = std::to_string(m_layer_height); + + const std::string cnt_fade_layers = std::to_string(m_cnt_fade_layers); + const std::string cnt_slow_layers = std::to_string(m_cnt_slow_layers); + const std::string cnt_fast_layers = std::to_string(m_cnt_fast_layers); + const std::string used_material = std::to_string(m_used_material); + + return std::string( + "action = print\n" + "jobDir = ") + projectname + "\n" + + "expTime = " + expt_str + "\n" + "expTimeFirst = " + expt_first_str + "\n" + "numFade = " + cnt_fade_layers + "\n" + "layerHeight = " + layerh_str + "\n" + "usedMaterial = " + used_material + "\n" + "numSlow = " + cnt_slow_layers + "\n" + "numFast = " + cnt_fast_layers + "\n"; +} + +void SLARasterWriter::flpXY(ClipperLib::Polygon &poly) +{ + for(auto& p : poly.Contour) std::swap(p.X, p.Y); + std::reverse(poly.Contour.begin(), poly.Contour.end()); + + for(auto& h : poly.Holes) { + for(auto& p : h) std::swap(p.X, p.Y); + std::reverse(h.begin(), h.end()); + } +} + +void SLARasterWriter::flpXY(ExPolygon &poly) +{ + for(auto& p : poly.contour.points) p = {p.y(), p.x()}; + std::reverse(poly.contour.points.begin(), poly.contour.points.end()); + + for(auto& h : poly.holes) { + for(auto& p : h.points) p = {p.y(), p.x()}; + std::reverse(h.points.begin(), h.points.end()); + } +} + +SLARasterWriter::SLARasterWriter(const SLAPrinterConfig &cfg, + const SLAMaterialConfig &mcfg, + double layer_height) +{ + double w = cfg.display_width.getFloat(); + double h = cfg.display_height.getFloat(); + auto pw = unsigned(cfg.display_pixels_x.getInt()); + auto ph = unsigned(cfg.display_pixels_y.getInt()); + + m_mirror[X] = cfg.display_mirror_x.getBool(); + + // PNG raster will implicitly do an Y mirror + m_mirror[Y] = ! cfg.display_mirror_y.getBool(); + + auto ro = cfg.display_orientation.getInt(); + + if(ro == roPortrait) { + std::swap(w, h); + std::swap(pw, ph); + m_o = roPortrait; + + // XY flipping implicitly does an X mirror + m_mirror[X] = ! m_mirror[X]; + } else m_o = roLandscape; + + m_res = Raster::Resolution(pw, ph); + m_pxdim = Raster::PixelDim(w/pw, h/ph); + m_exp_time_s = mcfg.exposure_time.getFloat(); + m_exp_time_first_s = mcfg.initial_exposure_time.getFloat(); + m_layer_height = layer_height; + + m_gamma = cfg.gamma_correction.getFloat(); +} + +void SLARasterWriter::save(const std::string &fpath, const std::string &prjname) +{ + try { + Zipper zipper(fpath); // zipper with no compression + + std::string project = prjname.empty()? + boost::filesystem::path(fpath).stem().string() : prjname; + + zipper.add_entry("config.ini"); + + zipper << createIniContent(project); + + for(unsigned i = 0; i < m_layers_rst.size(); i++) + { + if(m_layers_rst[i].rawbytes.size() > 0) { + char lyrnum[6]; + std::sprintf(lyrnum, "%.5d", i); + auto zfilename = project + lyrnum + ".png"; + + // Add binary entry to the zipper + zipper.add_entry(zfilename, + m_layers_rst[i].rawbytes.data(), + m_layers_rst[i].rawbytes.size()); + } + } + + zipper.finalize(); + } catch(std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + // Rethrow the exception + throw; + } +} + +void SLARasterWriter::set_statistics(const std::vector statistics) +{ + if (statistics.size() != psCnt) + return; + + m_used_material = statistics[psUsedMaterial]; + m_cnt_fade_layers = int(statistics[psNumFade]); + m_cnt_slow_layers = int(statistics[psNumSlow]); + m_cnt_fast_layers = int(statistics[psNumFast]); +} + +} // namespace sla +} // namespace Slic3r diff --git a/src/libslic3r/SLA/SLARasterWriter.hpp b/src/libslic3r/SLA/SLARasterWriter.hpp new file mode 100644 index 0000000000..9fc23840ea --- /dev/null +++ b/src/libslic3r/SLA/SLARasterWriter.hpp @@ -0,0 +1,139 @@ +#ifndef SLARASTERWRITER_HPP +#define SLARASTERWRITER_HPP + +// For png export of the sliced model +#include +#include +#include + +#include "libslic3r/PrintConfig.hpp" + +#include "SLARaster.hpp" + +namespace Slic3r { namespace sla { + +// Implementation for PNG raster output +// Be aware that if a large number of layers are allocated, it can very well +// exhaust the available memory especially on 32 bit platform. +// This class is designed to be used in parallel mode. Layers have an ID and +// each layer can be written and compressed independently (in parallel). +// At the end when all layers where written, the save method can be used to +// write out the result into a zipped archive. +class SLARasterWriter +{ +public: + enum RasterOrientation { + roLandscape, + roPortrait + }; + + // Used for addressing parameters of set_statistics() + enum ePrintStatistics + { + psUsedMaterial = 0, + psNumFade, + psNumSlow, + psNumFast, + + psCnt + }; + +private: + + // A struct to bind the raster image data and its compressed bytes together. + struct Layer { + Raster raster; + RawBytes rawbytes; + + Layer() = default; + Layer(const Layer&) = delete; // The image is big, do not copy by accident + Layer& operator=(const Layer&) = delete; + + Layer(Layer&& m) = default; + Layer& operator=(Layer&&) = default; + }; + + // We will save the compressed PNG data into RawBytes type buffers in + // parallel. Later we can write every layer to the disk sequentially. + std::vector m_layers_rst; + Raster::Resolution m_res; + Raster::PixelDim m_pxdim; + double m_exp_time_s = .0, m_exp_time_first_s = .0; + double m_layer_height = .0; + RasterOrientation m_o = roPortrait; + std::array m_mirror; + + double m_gamma; + + double m_used_material = 0.0; + int m_cnt_fade_layers = 0; + int m_cnt_slow_layers = 0; + int m_cnt_fast_layers = 0; + + std::string createIniContent(const std::string& projectname) const; + + static void flpXY(ClipperLib::Polygon& poly); + static void flpXY(ExPolygon& poly); + +public: + + SLARasterWriter(const SLAPrinterConfig& cfg, + const SLAMaterialConfig& mcfg, + double layer_height); + + SLARasterWriter(const SLARasterWriter& ) = delete; + SLARasterWriter& operator=(const SLARasterWriter&) = delete; + SLARasterWriter(SLARasterWriter&& m) = default; + SLARasterWriter& operator=(SLARasterWriter&&) = default; +// SLARasterWriter(SLARasterWriter&& m) = default; +// SLARasterWriter(SLARasterWriter&& m): +// m_layers_rst(std::move(m.m_layers_rst)), +// m_res(m.m_res), +// m_pxdim(m.m_pxdim) {} + + inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } + inline unsigned layers() const { return unsigned(m_layers_rst.size()); } + + template void draw_polygon(const Poly& p, unsigned lyr) { + assert(lyr < m_layers_rst.size()); + if(m_o == roPortrait) { + Poly poly(p); flpXY(poly); + m_layers_rst[lyr].raster.draw(poly); + } + else m_layers_rst[lyr].raster.draw(p); + } + + inline void begin_layer(unsigned lyr) { + if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); + m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_mirror, m_gamma); + } + + inline void begin_layer() { + m_layers_rst.emplace_back(); + m_layers_rst.front().raster.reset(m_res, m_pxdim, m_mirror, m_gamma); + } + + inline void finish_layer(unsigned lyr_id) { + assert(lyr_id < m_layers_rst.size()); + m_layers_rst[lyr_id].rawbytes = + m_layers_rst[lyr_id].raster.save(Raster::Format::PNG); + m_layers_rst[lyr_id].raster.reset(); + } + + inline void finish_layer() { + if(!m_layers_rst.empty()) { + m_layers_rst.back().rawbytes = + m_layers_rst.back().raster.save(Raster::Format::PNG); + m_layers_rst.back().raster.reset(); + } + } + + void save(const std::string& fpath, const std::string& prjname = ""); + + void set_statistics(const std::vector statistics); +}; + +} // namespace sla +} // namespace Slic3r + +#endif // SLARASTERWRITER_HPP diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 6d2318bea2..fba9748225 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1281,37 +1281,11 @@ void SLAPrint::process() auto rasterize = [this, max_objstatus]() { if(canceled()) return; - // collect all the keys - - // If the raster has vertical orientation, we will flip the coordinates - bool flpXY = m_printer_config.display_orientation.getInt() == - SLADisplayOrientation::sladoPortrait; - { // create a raster printer for the current print parameters - // I don't know any better -// auto& ocfg = m_objects.front()->m_config; -// auto& matcfg = m_material_config; -// auto& printcfg = m_printer_config; - -// double w = printcfg.display_width.getFloat(); -// double h = printcfg.display_height.getFloat(); -// auto pw = unsigned(printcfg.display_pixels_x.getInt()); -// auto ph = unsigned(printcfg.display_pixels_y.getInt()); -// double lh = ocfg.layer_height.getFloat(); -// double exp_t = matcfg.exposure_time.getFloat(); -// double iexp_t = matcfg.initial_exposure_time.getFloat(); - -// double gamma = m_printer_config.gamma_correction.getFloat(); - -// if(flpXY) { std::swap(w, h); std::swap(pw, ph); } - -// m_printer.reset( -// new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t, -// flpXY? SLAPrinter::RO_PORTRAIT : -// SLAPrinter::RO_LANDSCAPE, -// gamma)); - - m_printer.reset(new SLAPrinter(m_printer_config, m_material_config, m_default_object_config.layer_height.getFloat())); + double layerh = m_default_object_config.layer_height.getFloat(); + m_printer.reset(new SLAPrinter(m_printer_config, + m_material_config, + layerh)); } // Allocate space for all the layers diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index dea468e7a0..1ad40ef154 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -3,11 +3,11 @@ #include #include "PrintBase.hpp" -#include "PrintExport.hpp" +//#include "PrintExport.hpp" +#include "SLA/SLARasterWriter.hpp" #include "Point.hpp" #include "MTUtils.hpp" #include -#include "Zipper.hpp" namespace Slic3r { @@ -322,37 +322,6 @@ struct SLAPrintStatistics } }; -// The implementation of creating zipped archives with wxWidgets -template<> class LayerWriter { - Zipper m_zip; -public: - - LayerWriter(const std::string& zipfile_path): m_zip(zipfile_path) {} - - void next_entry(const std::string& fname) { m_zip.add_entry(fname); } - - void binary_entry(const std::string& fname, - const std::uint8_t* buf, - size_t l) - { - m_zip.add_entry(fname, buf, l); - } - - template inline LayerWriter& operator<<(T&& arg) { - m_zip << std::forward(arg); return *this; - } - - bool is_ok() const { - return true; // m_zip blows up if something goes wrong... - } - - // After finalize, no writing to the archive will have an effect. The only - // valid operation is to dispose the object calling the destructor which - // should close the file. This method can throw and signal potential errors - // when flushing the archive. This is why its present. - void finalize() { m_zip.finalize(); } -}; - /** * @brief This class is the high level FSM for the SLA printing process. * @@ -385,11 +354,10 @@ public: // Returns true if the last step was finished with success. bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } - template inline void export_raster(const std::string& fpath, - const std::string& projectname = "") + const std::string& projectname = "") { - if(m_printer) m_printer->save(fpath, projectname); + if(m_printer) m_printer->save(fpath, projectname); } const PrintObjects& objects() const { return m_objects; } @@ -450,7 +418,7 @@ public: const std::vector& print_layers() const { return m_printer_input; } private: - using SLAPrinter = FilePrinter; + using SLAPrinter = sla::SLARasterWriter; using SLAPrinterPtr = std::unique_ptr; // Implement same logic as in SLAPrintObject From 38d54d779a1b5ca2d7bd992ec5920d6963f94420 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Sat, 18 May 2019 23:21:59 +0200 Subject: [PATCH 006/627] Mirror checkboxes added to the UI. Everything seems to work fine. --- src/libslic3r/PrintConfig.cpp | 14 ++++++++------ src/libslic3r/SLA/SLARaster.cpp | 14 ++++++++++++++ src/libslic3r/SLA/SLARaster.hpp | 23 +++++++++++++++++++---- src/libslic3r/SLAPrint.cpp | 2 ++ src/slic3r/GUI/Preset.cpp | 1 + src/slic3r/GUI/Tab.cpp | 4 ++++ 6 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d000c2c2c5..89e21934a1 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2259,15 +2259,17 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionInt(1440)); def = this->add("display_mirror_x", coBool); - def->full_label = L("Display mirroring in X axis"); - def->label = L("Mirror X"); - def->tooltip = L("Enable mirroring of output images in the X axis"); + def->full_label = L("Display horizontal mirroring"); + def->label = L("Mirror horizontally"); + def->tooltip = L("Enable horizontal mirroring of output images"); + def->mode = comExpert; def->set_default_value(new ConfigOptionBool(true)); def = this->add("display_mirror_y", coBool); - def->full_label = L("Display mirroring in Y axis"); - def->label = L("Mirror Y"); - def->tooltip = L("Enable mirroring of output images in the Y axis"); + def->full_label = L("Display vertical mirroring"); + def->label = L("Mirror vertically"); + def->tooltip = L("Enable vertical mirroring of output images"); + def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); def = this->add("display_orientation", coEnum); diff --git a/src/libslic3r/SLA/SLARaster.cpp b/src/libslic3r/SLA/SLARaster.cpp index fba1f46f33..f1b1c8c42b 100644 --- a/src/libslic3r/SLA/SLARaster.cpp +++ b/src/libslic3r/SLA/SLARaster.cpp @@ -58,6 +58,7 @@ private: std::function m_gammafn; std::array m_mirror; + Format m_fmt = Format::PNG; inline void flipy(agg::path_storage& path) const { path.flip_y(0, m_resolution.height_px); @@ -101,6 +102,7 @@ public: case Format::PNG: m_mirror = {false, true}; break; case Format::RAW: m_mirror = {false, false}; break; } + m_fmt = fmt; } template void draw(const P &poly) { @@ -131,6 +133,8 @@ public: } inline TBuffer& buffer() { return m_buf; } + + inline Format format() const { return m_fmt; } inline const Raster::Resolution resolution() { return m_resolution; } @@ -258,6 +262,11 @@ void Raster::save(std::ostream& stream, Format fmt) } } +void Raster::save(std::ostream &stream) +{ + save(stream, m_impl->format()); +} + RawBytes Raster::save(Format fmt) { assert(m_impl); @@ -300,6 +309,11 @@ RawBytes Raster::save(Format fmt) return {std::move(data)}; } +RawBytes Raster::save() +{ + return save(m_impl->format()); +} + } } diff --git a/src/libslic3r/SLA/SLARaster.hpp b/src/libslic3r/SLA/SLARaster.hpp index 03fb06d6c8..5051498c58 100644 --- a/src/libslic3r/SLA/SLARaster.hpp +++ b/src/libslic3r/SLA/SLARaster.hpp @@ -100,8 +100,17 @@ public: ~Raster(); /// Reallocated everything for the given resolution and pixel dimension. - void reset(const Resolution&, const PixelDim&, const std::array& mirror, double gamma = 1.0); - void reset(const Resolution& r, const PixelDim& pd, Format o, double gamma = 1.0); + /// The third parameter is either the X, Y mirroring or a supported format + /// for which the correct mirroring will be configured. + void reset(const Resolution&, + const PixelDim&, + const std::array& mirror, + double gamma = 1.0); + + void reset(const Resolution& r, + const PixelDim& pd, + Format o, + double gamma = 1.0); /** * Release the allocated resources. Drawing in this state ends in @@ -119,11 +128,17 @@ public: void draw(const ExPolygon& poly); void draw(const ClipperLib::Polygon& poly); + // Saving the raster: + // It is possible to override the format given in the constructor but + // be aware that the mirroring will not be modified. + /// Save the raster on the specified stream. - void save(std::ostream& stream, Format = Format::PNG); + void save(std::ostream& stream, Format); + void save(std::ostream& stream); /// Save into a continuous byte stream which is returned. - RawBytes save(Format fmt = Format::PNG); + RawBytes save(Format fmt); + RawBytes save(); }; } // sla diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index fba9748225..13df2fa792 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1461,6 +1461,8 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector& Preset::sla_printer_options() "printer_technology", "bed_shape", "max_print_height", "display_width", "display_height", "display_pixels_x", "display_pixels_y", + "display_mirror_x", "display_mirror_y", "display_orientation", "fast_tilt_time", "slow_tilt_time", "area_fill", "relative_correction", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b6bdb7a4bb..8fa34e37aa 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2056,6 +2056,10 @@ void TabPrinter::build_sla() line.append_option(optgroup->get_option("display_pixels_y")); optgroup->append_line(line); optgroup->append_single_option_line("display_orientation"); + + // FIXME: This should be on one line in the UI + optgroup->append_single_option_line("display_mirror_x"); + optgroup->append_single_option_line("display_mirror_y"); optgroup = page->new_optgroup(_(L("Tilt"))); line = { _(L("Tilt time")), "" }; From 783a52710949153a64beaf1dd50b23c6d0bfe7a7 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 20 May 2019 09:39:57 +0200 Subject: [PATCH 007/627] Added imgui debug dialog for camera statistics --- src/libslic3r/Technologies.hpp | 2 ++ src/slic3r/GUI/Camera.cpp | 27 ++++++++++++++++++++++++++- src/slic3r/GUI/Camera.hpp | 4 ++++ src/slic3r/GUI/GLCanvas3D.cpp | 4 ++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 3375a282b1..fdc5ef7d27 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -13,6 +13,8 @@ #define ENABLE_RENDER_SELECTION_CENTER 0 // Shows an imgui dialog with render related data #define ENABLE_RENDER_STATISTICS 0 +// Shows an imgui dialog with camera related data +#define ENABLE_CAMERA_STATISTICS 1 //==================== diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 5f7a7ce4b2..a9edb76264 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -2,6 +2,9 @@ #include "Camera.hpp" #include "3DScene.hpp" +#if ENABLE_CAMERA_STATISTICS +#include "GUI_App.hpp" +#endif // ENABLE_CAMERA_STATISTICS #include @@ -109,7 +112,6 @@ void Camera::apply_view_matrix() const glsafe(::glRotatef(-m_theta, 1.0f, 0.0f, 0.0f)); // pitch glsafe(::glRotatef(phi, 0.0f, 0.0f, 1.0f)); // yaw - glsafe(::glTranslated(-m_target(0), -m_target(1), -m_target(2))); // target to origin glsafe(::glGetDoublev(GL_MODELVIEW_MATRIX, m_view_matrix.data())); @@ -144,6 +146,29 @@ void Camera::apply_projection(const BoundingBoxf3& box) const } } +#if ENABLE_CAMERA_STATISTICS +void Camera::debug_render() const +{ + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.set_next_window_bg_alpha(0.5f); + imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + Vec3f position = get_position().cast(); + Vec3f target = m_target.cast(); + Vec3f forward = get_dir_forward().cast(); + Vec3f right = get_dir_right().cast(); + Vec3f up = get_dir_up().cast(); + + ImGui::InputFloat3("Position", position.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat3("Target", target.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); + ImGui::InputFloat3("Forward", forward.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat3("Right", right.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat3("Up", up.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + imgui.end(); +} +#endif // ENABLE_CAMERA_STATISTICS + void Camera::apply_ortho_projection(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max) const { glsafe(::glMatrixMode(GL_PROJECTION)); diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index f0d22fd67a..1f217be280 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -64,6 +64,10 @@ public: void apply_view_matrix() const; void apply_projection(const BoundingBoxf3& box) const; +#if ENABLE_CAMERA_STATISTICS + void debug_render() const; +#endif // ENABLE_CAMERA_STATISTICS + private: void apply_ortho_projection(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max) const; }; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ed9c1bd60a..f01d1a3ada 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1700,6 +1700,10 @@ void GLCanvas3D::render() imgui.end(); #endif // ENABLE_RENDER_STATISTICS +#if ENABLE_CAMERA_STATISTICS + m_camera.debug_render(); +#endif // ENABLE_CAMERA_STATISTICS + wxGetApp().imgui()->render(); m_canvas->SwapBuffers(); From 4e2ef09a506409697cc5f1f9340e2bf04343ede7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 20 May 2019 11:19:43 +0200 Subject: [PATCH 008/627] Fixing build on Win and OSX --- src/libslic3r/SLA/SLARaster.cpp | 15 +++++++-- src/libslic3r/SLA/SLARaster.hpp | 23 ++++++++------ src/libslic3r/SLA/SLARasterWriter.cpp | 4 +-- src/libslic3r/SLA/SLARasterWriter.hpp | 46 +++++++++++++++++++++------ src/libslic3r/SLAPrint.cpp | 2 +- 5 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/libslic3r/SLA/SLARaster.cpp b/src/libslic3r/SLA/SLARaster.cpp index f1b1c8c42b..20891c3d4c 100644 --- a/src/libslic3r/SLA/SLARaster.cpp +++ b/src/libslic3r/SLA/SLARaster.cpp @@ -1,6 +1,8 @@ #ifndef SLARASTER_CPP #define SLARASTER_CPP +#include + #include "SLARaster.hpp" #include "libslic3r/ExPolygon.hpp" #include @@ -179,10 +181,17 @@ private: const Raster::Impl::TPixel Raster::Impl::ColorWhite = Raster::Impl::TPixel(255); const Raster::Impl::TPixel Raster::Impl::ColorBlack = Raster::Impl::TPixel(0); -Raster::Raster() = default; +template<> Raster::Raster() { reset(); }; Raster::~Raster() = default; -Raster::Raster(Raster &&m) = default; -Raster& Raster::operator=(Raster&&) = default; + +// Raster::Raster(Raster &&m) = default; +// Raster& Raster::operator=(Raster&&) = default; + +// FIXME: remove after migrating to higher version of windows compiler +Raster::Raster(Raster &&m): m_impl(std::move(m.m_impl)) {} +Raster& Raster::operator=(Raster &&m) { + m_impl = std::move(m.m_impl); return *this; +} void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, Format fmt, double gamma) diff --git a/src/libslic3r/SLA/SLARaster.hpp b/src/libslic3r/SLA/SLARaster.hpp index 5051498c58..d3bd52d92c 100644 --- a/src/libslic3r/SLA/SLARaster.hpp +++ b/src/libslic3r/SLA/SLARaster.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include namespace ClipperLib { struct Polygon; } @@ -27,22 +29,20 @@ public: const uint8_t * data() { return m_buffer.data(); } RawBytes(const RawBytes&) = delete; - RawBytes(RawBytes&&) = default; RawBytes& operator=(const RawBytes&) = delete; - RawBytes& operator=(RawBytes&&) = default; // ///////////////////////////////////////////////////////////////////////// // FIXME: the following is needed for MSVC2013 compatibility // ///////////////////////////////////////////////////////////////////////// -// RawBytes(const RawBytes&) = delete; -// RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {} + // RawBytes(RawBytes&&) = default; + // RawBytes& operator=(RawBytes&&) = default; -// RawBytes& operator=(const RawBytes&) = delete; -// RawBytes& operator=(RawBytes&& mv) { -// m_buffer = std::move(mv.m_buffer); -// return *this; -// } + RawBytes(RawBytes&& mv) : m_buffer(std::move(mv.m_buffer)) {} + RawBytes& operator=(RawBytes&& mv) { + m_buffer = std::move(mv.m_buffer); + return *this; + } // ///////////////////////////////////////////////////////////////////////// }; @@ -92,7 +92,6 @@ public: reset(std::forward(args)...); } - Raster(); Raster(const Raster& cpy) = delete; Raster& operator=(const Raster& cpy) = delete; Raster(Raster&& m); @@ -141,6 +140,10 @@ public: RawBytes save(); }; +// This prevents the duplicate default constructor warning on MSVC2013 +template<> Raster::Raster(); + + } // sla } // Slic3r diff --git a/src/libslic3r/SLA/SLARasterWriter.cpp b/src/libslic3r/SLA/SLARasterWriter.cpp index b2fe0c72c3..f7c3925ac7 100644 --- a/src/libslic3r/SLA/SLARasterWriter.cpp +++ b/src/libslic3r/SLA/SLARasterWriter.cpp @@ -44,11 +44,11 @@ void SLARasterWriter::flpXY(ClipperLib::Polygon &poly) void SLARasterWriter::flpXY(ExPolygon &poly) { - for(auto& p : poly.contour.points) p = {p.y(), p.x()}; + for(auto& p : poly.contour.points) p = Point(p.y(), p.x()); std::reverse(poly.contour.points.begin(), poly.contour.points.end()); for(auto& h : poly.holes) { - for(auto& p : h.points) p = {p.y(), p.x()}; + for(auto& p : h.points) p = Point(p.y(), p.x()); std::reverse(h.points.begin(), h.points.end()); } } diff --git a/src/libslic3r/SLA/SLARasterWriter.hpp b/src/libslic3r/SLA/SLARasterWriter.hpp index 9fc23840ea..7133d2ddec 100644 --- a/src/libslic3r/SLA/SLARasterWriter.hpp +++ b/src/libslic3r/SLA/SLARasterWriter.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "libslic3r/PrintConfig.hpp" @@ -49,8 +50,18 @@ private: Layer(const Layer&) = delete; // The image is big, do not copy by accident Layer& operator=(const Layer&) = delete; - Layer(Layer&& m) = default; - Layer& operator=(Layer&&) = default; + // ///////////////////////////////////////////////////////////////////// + // FIXME: the following is needed for MSVC2013 compatibility + // ///////////////////////////////////////////////////////////////////// + + // Layer(Layer&& m) = default; + // Layer& operator=(Layer&&) = default; + Layer(Layer &&m): + raster(std::move(m.raster)), rawbytes(std::move(m.rawbytes)) {} + Layer& operator=(Layer &&m) { + raster = std::move(m.raster); rawbytes = std::move(m.rawbytes); + return *this; + } }; // We will save the compressed PNG data into RawBytes type buffers in @@ -83,13 +94,30 @@ public: SLARasterWriter(const SLARasterWriter& ) = delete; SLARasterWriter& operator=(const SLARasterWriter&) = delete; - SLARasterWriter(SLARasterWriter&& m) = default; - SLARasterWriter& operator=(SLARasterWriter&&) = default; -// SLARasterWriter(SLARasterWriter&& m) = default; -// SLARasterWriter(SLARasterWriter&& m): -// m_layers_rst(std::move(m.m_layers_rst)), -// m_res(m.m_res), -// m_pxdim(m.m_pxdim) {} + + // ///////////////////////////////////////////////////////////////////////// + // FIXME: the following is needed for MSVC2013 compatibility + // ///////////////////////////////////////////////////////////////////////// + + // SLARasterWriter(SLARasterWriter&& m) = default; + // SLARasterWriter& operator=(SLARasterWriter&&) = default; + SLARasterWriter(SLARasterWriter&& m): + m_layers_rst(std::move(m.m_layers_rst)), + m_res(m.m_res), + m_pxdim(m.m_pxdim), + m_exp_time_s(m.m_exp_time_s), + m_exp_time_first_s(m.m_exp_time_first_s), + m_layer_height(m.m_layer_height), + m_o(m.m_o), + m_mirror(std::move(m.m_mirror)), + m_gamma(m.m_gamma), + m_used_material(m.m_used_material), + m_cnt_fade_layers(m.m_cnt_fade_layers), + m_cnt_slow_layers(m.m_cnt_slow_layers), + m_cnt_fast_layers(m.m_cnt_fast_layers) + {} + + // ///////////////////////////////////////////////////////////////////////// inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } inline unsigned layers() const { return unsigned(m_layers_rst.size()); } diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 13df2fa792..d07eba2b87 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -742,7 +742,7 @@ void SLAPrint::process() // We apply the printer correction offset here. if(clpr_offs != 0) po.m_model_slices[id] = - offset_ex(po.m_model_slices[id], clpr_offs); + offset_ex(po.m_model_slices[id], float(clpr_offs)); mit->set_model_slice_idx(po, id); ++mit; } From efd247fc585ebd7f848680da7f06aab1a3e00e46 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 21 May 2019 14:19:03 +0200 Subject: [PATCH 009/627] Use texture compression on GPU --- src/libslic3r/Technologies.hpp | 6 ++++- src/slic3r/GUI/GLCanvas3D.cpp | 24 +++++++++++++++++-- src/slic3r/GUI/GLCanvas3DManager.cpp | 36 +++++++++++++++++----------- src/slic3r/GUI/GLCanvas3DManager.hpp | 15 ++++++++---- src/slic3r/GUI/GLTexture.cpp | 35 +++++++++++++++++++++++++++ src/slic3r/GUI/ImGuiWrapper.cpp | 7 ++++++ 6 files changed, 102 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index bffc45cde7..3c5626a09d 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -12,7 +12,7 @@ // Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active #define ENABLE_RENDER_SELECTION_CENTER 0 // Shows an imgui dialog with render related data -#define ENABLE_RENDER_STATISTICS 0 +#define ENABLE_RENDER_STATISTICS 1 //==================== @@ -46,4 +46,8 @@ #define ENABLE_SVG_ICONS (1 && ENABLE_1_42_0_ALPHA8 && ENABLE_TEXTURES_FROM_SVG) +// Enable saving textures on GPU in compressed format +#define ENABLE_COMPRESSED_TEXTURES 1 + + #endif // _technologies_h_ diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 26d205055e..bac3a3c64f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -864,7 +864,14 @@ bool GLCanvas3D::WarningTexture::_generate(const std::string& msg_utf8, const GL glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glGenTextures(1, &m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id)); +#if ENABLE_COMPRESSED_TEXTURES + if (GLEW_EXT_texture_compression_s3tc) + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + else + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); +#else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); +#endif // ENABLE_COMPRESSED_TEXTURES glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); @@ -1147,7 +1154,14 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glGenTextures(1, &m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id)); +#if ENABLE_COMPRESSED_TEXTURES + if (GLEW_EXT_texture_compression_s3tc) + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + else + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); +#else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); +#endif // ENABLE_COMPRESSED_TEXTURES glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); @@ -1691,11 +1705,17 @@ void GLCanvas3D::render() ImGuiWrapper& imgui = *wxGetApp().imgui(); imgui.set_next_window_bg_alpha(0.5f); imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - imgui.text(_(L("Last frame")) +": "); + imgui.text("Last frame: "); ImGui::SameLine(); imgui.text(std::to_string(m_render_stats.last_frame)); ImGui::SameLine(); - imgui.text(" "+_(L("ms"))); + imgui.text(" ms"); +#if ENABLE_COMPRESSED_TEXTURES + ImGui::Separator(); + imgui.text("Textures: "); + ImGui::SameLine(); + imgui.text(GLCanvas3DManager::are_compressed_textures_supported() ? "Compressed" : "Uncompressed"); +#endif // ENABLE_COMPRESSED_TEXTURES imgui.end(); #endif // ENABLE_RENDER_STATISTICS diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index e409bed0dd..5f4391a5e2 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -111,6 +111,9 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten } GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown; +#if ENABLE_COMPRESSED_TEXTURES +bool GLCanvas3DManager::s_compressed_textures_supported = false; +#endif // ENABLE_COMPRESSED_TEXTURES GLCanvas3DManager::GLCanvas3DManager() : m_context(nullptr) @@ -134,7 +137,7 @@ bool GLCanvas3DManager::add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLTo if (canvas == nullptr) return false; - if (_get_canvas(canvas) != m_canvases.end()) + if (do_get_canvas(canvas) != m_canvases.end()) return false; GLCanvas3D* canvas3D = new GLCanvas3D(canvas, bed, camera, view_toolbar); @@ -159,7 +162,7 @@ bool GLCanvas3DManager::add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLTo bool GLCanvas3DManager::remove(wxGLCanvas* canvas) { - CanvasesMap::iterator it = _get_canvas(canvas); + CanvasesMap::iterator it = do_get_canvas(canvas); if (it == m_canvases.end()) return false; @@ -195,6 +198,12 @@ void GLCanvas3DManager::init_gl() m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1"); m_use_VBOs = !m_use_legacy_opengl && m_gl_info.is_version_greater_or_equal_to(2, 0); m_gl_initialized = true; +#if ENABLE_COMPRESSED_TEXTURES + if (GLEW_EXT_texture_compression_s3tc) + s_compressed_textures_supported = true; + else + s_compressed_textures_supported = false; +#endif // ENABLE_COMPRESSED_TEXTURES } } @@ -205,16 +214,16 @@ std::string GLCanvas3DManager::get_gl_info(bool format_as_html, bool extensions) bool GLCanvas3DManager::init(wxGLCanvas* canvas) { - CanvasesMap::const_iterator it = _get_canvas(canvas); + CanvasesMap::const_iterator it = do_get_canvas(canvas); if (it != m_canvases.end()) - return (it->second != nullptr) ? _init(*it->second) : false; + return (it->second != nullptr) ? init(*it->second) : false; else return false; } GLCanvas3D* GLCanvas3DManager::get_canvas(wxGLCanvas* canvas) { - CanvasesMap::const_iterator it = _get_canvas(canvas); + CanvasesMap::const_iterator it = do_get_canvas(canvas); return (it != m_canvases.end()) ? it->second : nullptr; } @@ -224,29 +233,28 @@ wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent) if (s_multisample == MS_Unknown) { - _detect_multisample(attribList); - // debug output - std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl; + detect_multisample(attribList); +// // debug output +// std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl; } - if (! can_multisample()) { + if (! can_multisample()) attribList[4] = 0; - } return new wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS); } -GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) +GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::do_get_canvas(wxGLCanvas* canvas) { return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas); } -GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) const +GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::do_get_canvas(wxGLCanvas* canvas) const { return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas); } -bool GLCanvas3DManager::_init(GLCanvas3D& canvas) +bool GLCanvas3DManager::init(GLCanvas3D& canvas) { if (!m_gl_initialized) init_gl(); @@ -254,7 +262,7 @@ bool GLCanvas3DManager::_init(GLCanvas3D& canvas) return canvas.init(m_use_VBOs, m_use_legacy_opengl); } -void GLCanvas3DManager::_detect_multisample(int* attribList) +void GLCanvas3DManager::detect_multisample(int* attribList) { int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER; const AppConfig* app_config = GUI::get_app_config(); diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 75647e6b25..d391cd60c7 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -60,6 +60,9 @@ class GLCanvas3DManager bool m_use_legacy_opengl; bool m_use_VBOs; static EMultisampleState s_multisample; +#if ENABLE_COMPRESSED_TEXTURES + static bool s_compressed_textures_supported; +#endif // ENABLE_COMPRESSED_TEXTURES public: GLCanvas3DManager(); @@ -79,14 +82,18 @@ public: GLCanvas3D* get_canvas(wxGLCanvas* canvas); static bool can_multisample() { return s_multisample == MS_Enabled; } +#if ENABLE_COMPRESSED_TEXTURES + static bool are_compressed_textures_supported() { return s_compressed_textures_supported; } +#endif // ENABLE_COMPRESSED_TEXTURES + static wxGLCanvas* create_wxglcanvas(wxWindow *parent); private: - CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas); - CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const; + CanvasesMap::iterator do_get_canvas(wxGLCanvas* canvas); + CanvasesMap::const_iterator do_get_canvas(wxGLCanvas* canvas) const; - bool _init(GLCanvas3D& canvas); - static void _detect_multisample(int* attribList); + bool init(GLCanvas3D& canvas); + static void detect_multisample(int* attribList); }; } // namespace GUI diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 2fff0869ad..f7d2edfe7b 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -178,7 +178,14 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vectorTexID = (ImTextureID)(intptr_t)m_font_texture; From 844e99f84e4ac640cd9ad0c905efc6068a35cdbb Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 22 May 2019 14:42:38 +0200 Subject: [PATCH 010/627] Prototype of scale to fit print volume command --- src/libslic3r/Technologies.hpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 13 ++++ src/slic3r/GUI/Plater.cpp | 24 ++++++- src/slic3r/GUI/Plater.hpp | 3 + src/slic3r/GUI/Selection.cpp | 81 ++++++++++++++++++++--- src/slic3r/GUI/Selection.hpp | 3 + 6 files changed, 119 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 3c5626a09d..0bf984b1bf 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -12,7 +12,7 @@ // Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active #define ENABLE_RENDER_SELECTION_CENTER 0 // Shows an imgui dialog with render related data -#define ENABLE_RENDER_STATISTICS 1 +#define ENABLE_RENDER_STATISTICS 0 //==================== @@ -47,7 +47,9 @@ // Enable saving textures on GPU in compressed format -#define ENABLE_COMPRESSED_TEXTURES 1 +#define ENABLE_COMPRESSED_TEXTURES 0 +// Enable scale object to fit print volume +#define ENABLE_SCALE_TO_FIT_PRINT_VOLUME 1 #endif // _technologies_h_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 1a431708aa..ceffd6e0d3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -847,6 +847,19 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas) break; } +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME + case 'F': + case 'f': + { + if (m_current == Scale) + { + wxGetApp().plater()->scale_selection_to_fit_print_volume(); + processed = true; + } + + break; + } +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME } } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1a5c322463..05b378407b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1286,7 +1286,10 @@ struct Plater::priv void sla_optimize_rotation(); void split_object(); void split_volume(); - bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; } +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME + void scale_selection_to_fit_print_volume(); +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME + bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; } void update_print_volume_state(); void schedule_background_process(); // Update background processing thread from the current config and Model. @@ -2346,6 +2349,13 @@ void Plater::priv::split_volume() wxGetApp().obj_list()->split(); } +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME +void Plater::priv::scale_selection_to_fit_print_volume() +{ + this->view3D->get_canvas3d()->get_selection().scale_to_fit_print_volume(*config); +} +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME + void Plater::priv::schedule_background_process() { delayed_error_message.clear(); @@ -3008,6 +3018,11 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME + append_menu_item(menu, wxID_ANY, _(L("Scale to print volume")), _(L("Scale the selected object to fit the print volume")), + [this](wxCommandEvent&) { scale_selection_to_fit_print_volume(); }, "", menu); +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME + wxMenu* mirror_menu = new wxMenu(); if (mirror_menu == nullptr) return false; @@ -3447,6 +3462,13 @@ bool Plater::is_selection_empty() const return p->get_selection().is_empty() || p->get_selection().is_wipe_tower(); } +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME +void Plater::scale_selection_to_fit_print_volume() +{ + p->scale_selection_to_fit_print_volume(); +} +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME + void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower) { wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index bc6d4b942d..b836649dbe 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -162,6 +162,9 @@ public: void decrease_instances(size_t num = 1); void set_number_of_copies(/*size_t num*/); bool is_selection_empty() const; +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME + void scale_selection_to_fit_print_volume(); +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 00fbaa42a5..836f1d5727 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -662,14 +662,34 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type { GLVolume &volume = *(*m_volumes)[i]; if (is_single_full_instance()) { - assert(transformation_type.absolute()); - if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { - // Non-uniform scaling. Transform the scaling factors into the local coordinate system. - // This is only possible, if the instance rotation is mulitples of ninety degrees. - assert(Geometry::is_rotation_ninety_degrees(volume.get_instance_rotation())); - volume.set_instance_scaling_factor((volume.get_instance_transformation().get_matrix(true, false, true, true).matrix().block<3, 3>(0, 0).transpose() * scale).cwiseAbs()); - } else - volume.set_instance_scaling_factor(scale); +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME + if (transformation_type.relative()) + { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); + Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); + // extracts scaling factors from the composed transformation + Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + if (transformation_type.joint()) + volume.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + + volume.set_instance_scaling_factor(new_scale); + } + else + { +#else + assert(transformation_type.absolute()); +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME + if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { + // Non-uniform scaling. Transform the scaling factors into the local coordinate system. + // This is only possible, if the instance rotation is mulitples of ninety degrees. + assert(Geometry::is_rotation_ninety_degrees(volume.get_instance_rotation())); + volume.set_instance_scaling_factor((volume.get_instance_transformation().get_matrix(true, false, true, true).matrix().block<3, 3>(0, 0).transpose() * scale).cwiseAbs()); + } + else + volume.set_instance_scaling_factor(scale); +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME + } +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME } else if (is_single_volume() || is_single_modifier()) volume.set_volume_scaling_factor(scale); @@ -713,6 +733,51 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type this->set_bounding_boxes_dirty(); } +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME +void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) +{ + if (is_empty() || (m_mode == Volume)) + return; + + // adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings + Vec3d box_size = get_bounding_box().size() + 0.01 * Vec3d::Ones(); + + const ConfigOptionPoints* opt = dynamic_cast(config.option("bed_shape")); + if (opt != nullptr) + { + BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); + BoundingBoxf3 print_volume(Vec3d(unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0), Vec3d(unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config.opt_float("max_print_height"))); + Vec3d print_volume_size = print_volume.size(); + double sx = (box_size(0) != 0.0) ? print_volume_size(0) / box_size(0) : 0.0; + double sy = (box_size(1) != 0.0) ? print_volume_size(1) / box_size(1) : 0.0; + double sz = (box_size(2) != 0.0) ? print_volume_size(2) / box_size(2) : 0.0; + if ((sx != 0.0) && (sy != 0.0) && (sz != 0.0)) + { + double s = std::min(sx, std::min(sy, sz)); + if (s != 1.0) + { + TransformationType type; + type.set_world(); + type.set_relative(); + type.set_joint(); + + // apply scale + start_dragging(); + scale(s * Vec3d::Ones(), type); + wxGetApp().plater()->canvas3D()->do_scale(); + + // center selection on print bed + start_dragging(); + translate(print_volume.center() - get_bounding_box().center()); + wxGetApp().plater()->canvas3D()->do_move(); + + wxGetApp().obj_manipul()->set_dirty(); + } + } + } +} +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME + void Selection::mirror(Axis axis) { if (!m_valid) diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 99d939acc5..03967b4d4c 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -287,6 +287,9 @@ public: void rotate(const Vec3d& rotation, TransformationType transformation_type); void flattening_rotate(const Vec3d& normal); void scale(const Vec3d& scale, TransformationType transformation_type); +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME + void scale_to_fit_print_volume(const DynamicPrintConfig& config); +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME void mirror(Axis axis); void translate(unsigned int object_idx, const Vec3d& displacement); From a8e92be6ebd39dacdf1a9db74ec6f60e2ecfc2a5 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 23 May 2019 09:20:11 +0200 Subject: [PATCH 011/627] 1) Added Scale to print volume menu item to objects list context menu 2) Disable [F] key when scale sizmo is dragging --- src/slic3r/GUI/GUI_ObjectList.cpp | 12 ++++++++++++ src/slic3r/GUI/GUI_ObjectList.hpp | 3 +++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 4 +++- src/slic3r/GUI/Plater.cpp | 3 +-- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 5a837f14dd..d49198ca8c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,3 +1,4 @@ +#include "libslic3r/libslic3r.h" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_App.hpp" @@ -1283,6 +1284,14 @@ void ObjectList::append_menu_item_delete(wxMenu* menu) [this](wxCommandEvent&) { remove(); }, "", menu); } +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME +void ObjectList::append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _(L("Scale to print volume")), _(L("Scale the selected object to fit the print volume")), + [this](wxCommandEvent&) { wxGetApp().plater()->scale_selection_to_fit_print_volume(); }, "", menu); +} +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME + void ObjectList::create_object_popupmenu(wxMenu *menu) { #ifdef __WXOSX__ @@ -1291,6 +1300,9 @@ void ObjectList::create_object_popupmenu(wxMenu *menu) append_menu_item_export_stl(menu); append_menu_item_fix_through_netfabb(menu); +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME + append_menu_item_scale_selection_to_fit_print_volume(menu); +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME // Split object to parts m_menu_item_split = append_menu_item_split(menu); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 0dcfe25600..d030b5c512 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -207,6 +207,9 @@ public: void append_menu_item_export_stl(wxMenu* menu) const ; void append_menu_item_change_extruder(wxMenu* menu) const; void append_menu_item_delete(wxMenu* menu); +#if ENABLE_SCALE_TO_FIT_PRINT_VOLUME + void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu); +#endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME void create_object_popupmenu(wxMenu *menu); void create_sla_object_popupmenu(wxMenu*menu); void create_part_popupmenu(wxMenu*menu); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index ceffd6e0d3..4926293247 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -853,7 +853,9 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas) { if (m_current == Scale) { - wxGetApp().plater()->scale_selection_to_fit_print_volume(); + if (!is_dragging()) + wxGetApp().plater()->scale_selection_to_fit_print_volume(); + processed = true; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 22f239516e..c82955b097 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3021,8 +3021,7 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); #if ENABLE_SCALE_TO_FIT_PRINT_VOLUME - append_menu_item(menu, wxID_ANY, _(L("Scale to print volume")), _(L("Scale the selected object to fit the print volume")), - [this](wxCommandEvent&) { scale_selection_to_fit_print_volume(); }, "", menu); + sidebar->obj_list()->append_menu_item_scale_selection_to_fit_print_volume(menu); #endif // ENABLE_SCALE_TO_FIT_PRINT_VOLUME wxMenu* mirror_menu = new wxMenu(); From 3aa14bddf519bc86d907421c56012d27c9bc3bb9 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 23 May 2019 13:49:57 +0200 Subject: [PATCH 012/627] Max texture size dependent on OpenGL version on Win and Linux and on retina monitors on Mac --- src/libslic3r/Technologies.hpp | 7 +- src/slic3r/GUI/3DBed.cpp | 8 ++ src/slic3r/GUI/3DScene.cpp | 4 + src/slic3r/GUI/GLCanvas3D.cpp | 7 +- src/slic3r/GUI/GLCanvas3DManager.cpp | 155 +++++++++++++++++++++++++++ src/slic3r/GUI/GLCanvas3DManager.hpp | 47 ++++++++ src/slic3r/GUI/GLTexture.cpp | 18 ++++ 7 files changed, 243 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 0bf984b1bf..e571965549 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -12,7 +12,7 @@ // Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active #define ENABLE_RENDER_SELECTION_CENTER 0 // Shows an imgui dialog with render related data -#define ENABLE_RENDER_STATISTICS 0 +#define ENABLE_RENDER_STATISTICS 1 //==================== @@ -47,7 +47,10 @@ // Enable saving textures on GPU in compressed format -#define ENABLE_COMPRESSED_TEXTURES 0 +#define ENABLE_COMPRESSED_TEXTURES 1 + +// Enable texture max size to be dependent on detected OpenGL version +#define ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION 1 // Enable scale object to fit print volume #define ENABLE_SCALE_TO_FIT_PRINT_VOLUME 1 diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 8392e534a4..2f20a65d90 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -493,6 +493,13 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const std::string model_path = resources_dir() + "/models/" + key; +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION + // use anisotropic filter if graphic card allows + GLfloat max_anisotropy = GLCanvas3DManager::get_gl_info().get_max_anisotropy(); + + // use higher resolution images if graphic card and opengl version allow + GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); +#else // use anisotropic filter if graphic card allows GLfloat max_anisotropy = 0.0f; if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) @@ -504,6 +511,7 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const // clamp or the texture generation becomes too slow max_tex_size = std::min(max_tex_size, 8192); +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION std::string filename = tex_path + ".svg"; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 59480de1ce..37e022329c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -2026,7 +2026,11 @@ bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs) std::string _3DScene::get_gl_info(bool format_as_html, bool extensions) { +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION + return Slic3r::GUI::GLCanvas3DManager::get_gl_info().to_string(format_as_html, extensions); +#else return s_canvas_mgr.get_gl_info(format_as_html, extensions); +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION } bool _3DScene::add_canvas(wxGLCanvas* canvas, GUI::Bed3D& bed, GUI::Camera& camera, GUI::GLToolbar& view_toolbar) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index bac3a3c64f..0630e42656 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1714,8 +1714,13 @@ void GLCanvas3D::render() ImGui::Separator(); imgui.text("Textures: "); ImGui::SameLine(); - imgui.text(GLCanvas3DManager::are_compressed_textures_supported() ? "Compressed" : "Uncompressed"); + imgui.text(GLCanvas3DManager::are_compressed_textures_supported() ? "compressed" : "uncompressed"); #endif // ENABLE_COMPRESSED_TEXTURES +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION + imgui.text("Max texture size: "); + ImGui::SameLine(); + imgui.text(std::to_string(GLCanvas3DManager::get_gl_info().get_max_tex_size())); +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION imgui.end(); #endif // ENABLE_RENDER_STATISTICS diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index 5f4391a5e2..d213990ba3 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -15,17 +15,115 @@ #include #include +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION +#ifdef __APPLE__ +#include "../Utils/MacDarkMode.hpp" +#endif // __APPLE__ +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION + namespace Slic3r { namespace GUI { GLCanvas3DManager::GLInfo::GLInfo() +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION + : m_detected(false) + , m_version("") + , m_glsl_version("") + , m_vendor("") + , m_renderer("") + , m_max_tex_size(0) + , m_max_anisotropy(0.0f) +#else : version("") , glsl_version("") , vendor("") , renderer("") +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION { } +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION +const std::string& GLCanvas3DManager::GLInfo::get_version() const +{ + if (!m_detected) + detect(); + + return m_version; +} + +const std::string& GLCanvas3DManager::GLInfo::get_glsl_version() const +{ + if (!m_detected) + detect(); + + return m_glsl_version; +} + +const std::string& GLCanvas3DManager::GLInfo::get_vendor() const +{ + if (!m_detected) + detect(); + + return m_vendor; +} + +const std::string& GLCanvas3DManager::GLInfo::get_renderer() const +{ + if (!m_detected) + detect(); + + return m_renderer; +} + +int GLCanvas3DManager::GLInfo::get_max_tex_size() const +{ + if (!m_detected) + detect(); + + // clamp to avoid the texture generation become too slow and use too much GPU memory +#ifdef __APPLE__ + // and use smaller texture for non retina systems + return (Slic3r::GUI::mac_max_scaling_factor() > 1.0) ? std::min(m_max_tex_size, 8192) : std::min(m_max_tex_size / 2, 4096); +#else + // and use smaller texture for older OpenGL versions + return is_version_greater_or_equal_to(3, 0) ? std::min(m_max_tex_size, 8192) : std::min(m_max_tex_size / 2, 4096); +#endif // __APPLE__ +} + +float GLCanvas3DManager::GLInfo::get_max_anisotropy() const +{ + if (!m_detected) + detect(); + + return m_max_anisotropy; +} + +void GLCanvas3DManager::GLInfo::detect() const +{ + const char* data = (const char*)::glGetString(GL_VERSION); + if (data != nullptr) + m_version = data; + + data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION); + if (data != nullptr) + m_glsl_version = data; + + data = (const char*)::glGetString(GL_VENDOR); + if (data != nullptr) + m_vendor = data; + + data = (const char*)::glGetString(GL_RENDERER); + if (data != nullptr) + m_renderer = data; + + glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size)); + + if (GLEW_EXT_texture_filter_anisotropic) + glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &m_max_anisotropy)); + + m_detected = true; +} +#else void GLCanvas3DManager::GLInfo::detect() { const char* data = (const char*)::glGetString(GL_VERSION); @@ -44,7 +142,40 @@ void GLCanvas3DManager::GLInfo::detect() if (data != nullptr) renderer = data; } +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION +bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const +{ + if (!m_detected) + detect(); + + std::vector tokens; + boost::split(tokens, m_version, boost::is_any_of(" "), boost::token_compress_on); + + if (tokens.empty()) + return false; + + std::vector numbers; + boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on); + + unsigned int gl_major = 0; + unsigned int gl_minor = 0; + + if (numbers.size() > 0) + gl_major = ::atoi(numbers[0].c_str()); + + if (numbers.size() > 1) + gl_minor = ::atoi(numbers[1].c_str()); + + if (gl_major < major) + return false; + else if (gl_major > major) + return true; + else + return gl_minor >= minor; +} +#else bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { std::vector tokens; @@ -72,9 +203,15 @@ bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int majo else return gl_minor >= minor; } +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool extensions) const { +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION + if (!m_detected) + detect(); +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION + std::stringstream out; std::string h2_start = format_as_html ? "" : ""; @@ -84,10 +221,17 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten std::string line_end = format_as_html ? "
" : "\n"; out << h2_start << "OpenGL installation" << h2_end << line_end; +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION + out << b_start << "GL version: " << b_end << (m_version.empty() ? "N/A" : m_version) << line_end; + out << b_start << "Vendor: " << b_end << (m_vendor.empty() ? "N/A" : m_vendor) << line_end; + out << b_start << "Renderer: " << b_end << (m_renderer.empty() ? "N/A" : m_renderer) << line_end; + out << b_start << "GLSL version: " << b_end << (m_glsl_version.empty() ? "N/A" : m_glsl_version) << line_end; +#else out << b_start << "GL version: " << b_end << (version.empty() ? "N/A" : version) << line_end; out << b_start << "Vendor: " << b_end << (vendor.empty() ? "N/A" : vendor) << line_end; out << b_start << "Renderer: " << b_end << (renderer.empty() ? "N/A" : renderer) << line_end; out << b_start << "GLSL version: " << b_end << (glsl_version.empty() ? "N/A" : glsl_version) << line_end; +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION if (extensions) { @@ -114,6 +258,9 @@ GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas #if ENABLE_COMPRESSED_TEXTURES bool GLCanvas3DManager::s_compressed_textures_supported = false; #endif // ENABLE_COMPRESSED_TEXTURES +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION +GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info; +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION GLCanvas3DManager::GLCanvas3DManager() : m_context(nullptr) @@ -193,10 +340,16 @@ void GLCanvas3DManager::init_gl() if (!m_gl_initialized) { glewInit(); +#if !ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION m_gl_info.detect(); +#endif // !ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION const AppConfig* config = GUI::get_app_config(); m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1"); +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION + m_use_VBOs = !m_use_legacy_opengl && s_gl_info.is_version_greater_or_equal_to(2, 0); +#else m_use_VBOs = !m_use_legacy_opengl && m_gl_info.is_version_greater_or_equal_to(2, 0); +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION m_gl_initialized = true; #if ENABLE_COMPRESSED_TEXTURES if (GLEW_EXT_texture_compression_s3tc) @@ -207,10 +360,12 @@ void GLCanvas3DManager::init_gl() } } +#if !ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION std::string GLCanvas3DManager::get_gl_info(bool format_as_html, bool extensions) const { return m_gl_info.to_string(format_as_html, extensions); } +#endif // !ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION bool GLCanvas3DManager::init(wxGLCanvas* canvas) { diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index d391cd60c7..3ad30411c2 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -29,6 +29,39 @@ struct Camera; class GLCanvas3DManager { +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION +public: + class GLInfo + { + mutable bool m_detected; + + mutable std::string m_version; + mutable std::string m_glsl_version; + mutable std::string m_vendor; + mutable std::string m_renderer; + + mutable int m_max_tex_size; + mutable float m_max_anisotropy; + + public: + GLInfo(); + + const std::string& get_version() const; + const std::string& get_glsl_version() const; + const std::string& get_vendor() const; + const std::string& get_renderer() const; + + int get_max_tex_size() const; + float get_max_anisotropy() const; + + bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const; + + std::string to_string(bool format_as_html, bool extensions) const; + + private: + void detect() const; + }; +#else struct GLInfo { std::string version; @@ -43,7 +76,11 @@ class GLCanvas3DManager std::string to_string(bool format_as_html, bool extensions) const; }; +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION +private: +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION enum EMultisampleState : unsigned char { MS_Unknown, @@ -55,7 +92,11 @@ class GLCanvas3DManager CanvasesMap m_canvases; wxGLContext* m_context; +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION + static GLInfo s_gl_info; +#else GLInfo m_gl_info; +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION bool m_gl_initialized; bool m_use_legacy_opengl; bool m_use_VBOs; @@ -75,7 +116,9 @@ public: unsigned int count() const; void init_gl(); +#if !ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION std::string get_gl_info(bool format_as_html, bool extensions) const; +#endif // !ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION bool init(wxGLCanvas* canvas); @@ -88,6 +131,10 @@ public: static wxGLCanvas* create_wxglcanvas(wxWindow *parent); +#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION + static const GLInfo& get_gl_info() { return s_gl_info; } +#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION + private: CanvasesMap::iterator do_get_canvas(wxGLCanvas* canvas); CanvasesMap::const_iterator do_get_canvas(wxGLCanvas* canvas) const; diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index f7d2edfe7b..2216a14f24 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -18,6 +18,10 @@ #include "libslic3r/Utils.hpp" +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#include +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + namespace Slic3r { namespace GUI { @@ -380,6 +384,10 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps) bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + auto start_time = std::chrono::high_resolution_clock::now(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f); if (image == nullptr) { @@ -426,6 +434,11 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns #else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); #endif // ENABLE_COMPRESSED_TEXTURES +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + auto end_time = std::chrono::high_resolution_clock::now(); + std::cout << "texture level 0 to GPU in: " << std::chrono::duration_cast(end_time - start_time).count() << std::endl; + start_time = end_time; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (use_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards @@ -468,6 +481,11 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns nsvgDeleteRasterizer(rast); nsvgDelete(image); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + end_time = std::chrono::high_resolution_clock::now(); + std::cout << "mipmaps to GPU in: " << std::chrono::duration_cast(end_time - start_time).count() << std::endl; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + return true; } From e0da08906b62911be2f8128dc249e0862e4a5165 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 27 May 2019 09:20:48 +0200 Subject: [PATCH 013/627] Disabled debug render statistics dialog --- src/libslic3r/Technologies.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 6822582c86..d7e5aed768 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -12,7 +12,7 @@ // Renders a small sphere in the center of the bounding box of the current selection when no gizmo is active #define ENABLE_RENDER_SELECTION_CENTER 0 // Shows an imgui dialog with render related data -#define ENABLE_RENDER_STATISTICS 1 +#define ENABLE_RENDER_STATISTICS 0 //==================== From dd70dd6e101dcadec70e0bd2e6ae7069ec5f3ad5 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 27 May 2019 13:07:37 +0200 Subject: [PATCH 014/627] Implemented LayerItem for ObjectList --- resources/icons/row.png | Bin 0 -> 1923 bytes resources/icons/table.png | Bin 0 -> 465 bytes src/slic3r/GUI/GUI_ObjectList.cpp | 28 +++++- src/slic3r/GUI/GUI_ObjectList.hpp | 4 +- src/slic3r/GUI/Plater.cpp | 4 + src/slic3r/GUI/wxExtensions.cpp | 142 ++++++++++++++++++++++++------ src/slic3r/GUI/wxExtensions.hpp | 18 ++-- 7 files changed, 156 insertions(+), 40 deletions(-) create mode 100644 resources/icons/row.png create mode 100644 resources/icons/table.png diff --git a/resources/icons/row.png b/resources/icons/row.png new file mode 100644 index 0000000000000000000000000000000000000000..18a6034fd597baffa886ed65145183a5627310d5 GIT binary patch literal 1923 zcmV-}2YmR6P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+SONCmh2`B{bv2UMA8^>9>^_xpz(Zwiv zHGSLq3H|aI5Zvz1_kI(~wxin+U54kwW9Cucdb$m`J;B*OTejJ4{I%@@ zf>8zb*c#iI_`uhXSr{I0`E;)}#d9y*CP-YE^8KLAk%;OFC3F=|K z5w8Mb0b>(YG>U3={5Vr{c}XN_K%`<^x0tPRH8)C&*R&>g~Qf<`)#!5J8{j=*>r z3}8Xq!P%kYoE^*!&W>oYDa3*`l0i~Wj#K!Y^M|GRS;c!vrK}pckmGR4kTeUIs5#m$l@Jfwcz(V#zdB%*-p;@{ z&0L$;Nq;}I3(mEZ$j$}FlujvxM;AT$`>r%UQuwY!nm(5in*!(hIsNi&ud3RXZ)pp$ zSG2QV!WNGn>(dHd>xiq}wqoX{+DVpvQ7++aLD#g6OX>b-w4wFj`cYopZmnHz27ov& z5EDbYVJOR^3q<=2g57eW1;#aGgHH#V8;toH4K8`Jl}d&3@=}pfYw|Zxy9G?+ zsK^?gJuWRdZ^M>r;TGyMwx;$WbzHnR+oX2uy5DFDarF!UONb2Jf_vlM#f{w;Bs8QE z+5XuPwKw%8geQmw^0j~X#Wyz1pz{?)dT67m@{lr6*c*tjg=V1mlmY3}l6LO{%-17B z-RS~5Z{`bWI1M~?$8|MV7PDs-OD04cvU^A~ZUHMPBAWzlulxR?oMS80WF^jEN+huL zrk<8h(j|rBsl?qxB1`(FEp_E1tdZCxcFuPg?hb=b7$G7r2DyY7ZOt=`a3**ihxJbI z9tZqVy5MX8VWE+}=pQ=-$N7Y_OZi@ z=tF5w2o#apMis&FL3ii|c!_OOF@z@JT9LNYQDc2QFIw3yVCUxp#&_7Q3pMKNafE2* zJDmIk=j||hL$Obh31bl6A21yL`M$%Dhfx36arDtpD7D^Vd@h<53F|1d^8f$=eMv+?R5;6}lfP>eaS+8nyPOg{@Cs48h+uV9gzJ*R ze?TEk3T?&Uk3t2p5eq?xjgf;uuCc!qDe^a@NFkU)61#ALG>U?O&gG~L@2O~EF6Js96?B2zL=jM9-h43{auge5rD+;k#$uJ=f*m}I4TM;NkL?DUCb0n2FpujP_3yERzEr4X!YfPEZ{sG~-EP|Z%oVWl0002ov JPDHLkV1lKIrrrPm literal 0 HcmV?d00001 diff --git a/resources/icons/table.png b/resources/icons/table.png new file mode 100644 index 0000000000000000000000000000000000000000..3bc0bd32fceb21d70368f7842a00a53d6369ba48 GIT binary patch literal 465 zcmV;?0WSWDP)zJNu3H-P zO&@UpeyZQXi7jKe-Hk?r-sue;aDce_XqkvXP+W#F_*ot`jB?BS93Uw71|U^ZjLH`yP%FO7U<6!nLCG} z$SDlW(menu_); @@ -1301,7 +1307,11 @@ void ObjectList::create_object_popupmenu(wxMenu *menu) append_menu_item_scale_selection_to_fit_print_volume(menu); // Split object to parts - m_menu_item_split = append_menu_item_split(menu); + append_menu_item_split(menu); + menu->AppendSeparator(); + + // Layers Editing for object + append_menu_item_layers_editing(menu); menu->AppendSeparator(); // rest of a object_menu will be added later in: @@ -1330,7 +1340,7 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) append_menu_item_fix_through_netfabb(menu); append_menu_item_export_stl(menu); - m_menu_item_split_part = append_menu_item_split(menu); + append_menu_item_split(menu); // Append change part type menu->AppendSeparator(); @@ -1774,6 +1784,20 @@ void ObjectList::split() changed_object(obj_idx); } +void ObjectList::layers_editing() +{ + const auto item = GetSelection(); + const int obj_idx = get_selected_obj_idx(); + if (!item || obj_idx < 0) + return; + + wxDataViewItem layers_item = m_objects_model->GetItemByType(item, itLayerRoot); + if (!layers_item.IsOk()) + layers_item = m_objects_model->AddLayersRoot(item); + + select_item(layers_item); +} + bool ObjectList::get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume) { auto obj_idx = get_selected_obj_idx(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 166606e2ee..076ab5f13d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -119,8 +119,6 @@ class ObjectList : public wxDataViewCtrl MenuWithSeparators m_menu_part; MenuWithSeparators m_menu_sla_object; MenuWithSeparators m_menu_instance; - wxMenuItem* m_menu_item_split { nullptr }; - wxMenuItem* m_menu_item_split_part { nullptr }; wxMenuItem* m_menu_item_settings { nullptr }; wxMenuItem* m_menu_item_split_instances { nullptr }; @@ -199,6 +197,7 @@ public: wxMenu* append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type); void append_menu_items_add_volume(wxMenu* menu); wxMenuItem* append_menu_item_split(wxMenu* menu); + wxMenuItem* append_menu_item_layers_editing(wxMenu* menu); wxMenuItem* append_menu_item_settings(wxMenu* menu); wxMenuItem* append_menu_item_change_type(wxMenu* menu); wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent); @@ -226,6 +225,7 @@ public: void del_instances_from_object(const int obj_idx); bool del_subobject_from_object(const int obj_idx, const int idx, const int type); void split(); + void layers_editing(); bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); bool is_splittable(); bool selected_instances_of_same_object(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 64c698a9bc..c0f2672042 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3066,6 +3066,10 @@ bool Plater::priv::complit_init_object_menu() [this]() { return can_split() && wxGetApp().get_mode() > comSimple; }, q); object_menu.AppendSeparator(); + // Layers Editing for object + sidebar->obj_list()->append_menu_item_layers_editing(&object_menu); + object_menu.AppendSeparator(); + // "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume() return true; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 76ba853dc3..988d4a8ea4 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -437,27 +437,44 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent m_type(type), m_extruder(wxEmptyString) { - if (type == itSettings) { + if (type == itSettings) m_name = "Settings to modified"; - } - else if (type == itInstanceRoot) { + else if (type == itInstanceRoot) m_name = _(L("Instances")); -#ifdef __WXGTK__ - m_container = true; -#endif //__WXGTK__ - } - else if (type == itInstance) { + else if (type == itInstance) + { m_idx = parent->GetChildCount(); m_name = wxString::Format(_(L("Instance %d")), m_idx + 1); set_action_icon(); } + else if (type == itLayerRoot) + { + m_bmp = create_scaled_bitmap(nullptr, "table.png"); // FIXME: pass window ptr + m_name = _(L("Layers")); + } + else if (type == itLayer) + { + m_idx = parent->GetChildCount(); + m_name = wxString::Format(_(L("Layer %d")), m_idx + 1); + m_bmp = create_scaled_bitmap(nullptr, "row.png"); // FIXME: pass window ptr + + set_action_icon(); + } + +#ifdef __WXGTK__ + // it's necessary on GTK because of control have to know if this item will be container + // in another case you couldn't to add subitem for this item + // it will be produce "segmentation fault" + if (type & (itInstanceRoot | itLayerRoot | itLayer)) + m_container = true; +#endif //__WXGTK__ } void ObjectDataViewModelNode::set_action_icon() { - m_action_icon_name = m_type == itObject ? "advanced_plus" : - m_type == itVolume ? "cog" : "set_separate_obj"; + m_action_icon_name = m_type & itObject ? "advanced_plus" : + m_type & (itVolume | itLayer) ? "cog" : /*m_type & itInstance*/ "set_separate_obj"; m_action_icon = create_scaled_bitmap(nullptr, m_action_icon_name); // FIXME: pass window ptr } @@ -619,36 +636,62 @@ wxDataViewItem ObjectDataViewModel::AddSettingsChild(const wxDataViewItem &paren return child; } -int get_istances_root_idx(ObjectDataViewModelNode *parent_node) +static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType root_type) { - // because of istance_root is a last item of the object - const int inst_root_idx = parent_node->GetChildCount()-1; + // because of istance_root and layers_root are at the end of the list, so + // start locking from the end + for (int root_idx = parent_node->GetChildCount() - 1; root_idx >= 0; root_idx--) + { + // if there is SettingsItem or VolumeItem, then RootItems don't exist in current ObjectItem + if (parent_node->GetNthChild(root_idx)->GetType() & (itSettings | itVolume)) + break; + if (parent_node->GetNthChild(root_idx)->GetType() & root_type) + return root_idx; + } - if (inst_root_idx < 0 || parent_node->GetNthChild(inst_root_idx)->GetType() == itInstanceRoot) - return inst_root_idx; - return -1; } +/* return values: + * true => root_node is created and added to the parent_root + * false => root node alredy exists +*/ +static bool append_root_node(ObjectDataViewModelNode *parent_node, + ObjectDataViewModelNode **root_node, + const ItemType root_type) +{ + const int inst_root_id = get_root_idx(parent_node, root_type); + + *root_node = inst_root_id < 0 ? + new ObjectDataViewModelNode(parent_node, root_type) : + parent_node->GetNthChild(inst_root_id); + + if (inst_root_id < 0) { + if ((root_type&itInstanceRoot) || + (root_type&itLayerRoot) && get_root_idx(parent_node, itInstanceRoot)<0) + parent_node->Append(*root_node); + else if (root_type&itLayerRoot) + parent_node->Insert(*root_node, unsigned int(get_root_idx(parent_node, itInstanceRoot))); + return true; + } + + return false; +} + wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &parent_item, size_t num) { ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); if (!parent_node) return wxDataViewItem(0); - // Check and create/get instances root node - const int inst_root_id = get_istances_root_idx(parent_node); + // get InstanceRoot node + ObjectDataViewModelNode *inst_root_node { nullptr }; - ObjectDataViewModelNode *inst_root_node = inst_root_id < 0 ? - new ObjectDataViewModelNode(parent_node, itInstanceRoot) : - parent_node->GetNthChild(inst_root_id); + const bool appended = append_root_node(parent_node, &inst_root_node, itInstanceRoot); const wxDataViewItem inst_root_item((void*)inst_root_node); + if (!inst_root_node) return wxDataViewItem(0); - if (inst_root_id < 0) { - parent_node->Append(inst_root_node); - // notify control - ItemAdded(parent_item, inst_root_item); -// if (num == 1) num++; - } + if (appended) + ItemAdded(parent_item, inst_root_item);// notify control // Add instance nodes ObjectDataViewModelNode *instance_node = nullptr; @@ -665,6 +708,47 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &paren return wxDataViewItem((void*)instance_node); } +wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_item) +{ + ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); + if (!parent_node) return wxDataViewItem(0); + + // get LayerRoot node + ObjectDataViewModelNode *layer_root_node{ nullptr }; + const bool appended = append_root_node(parent_node, &layer_root_node, itLayerRoot); + if (!layer_root_node) return wxDataViewItem(0); + + const wxDataViewItem layer_root_item((void*)layer_root_node); + + if (appended) + ItemAdded(parent_item, layer_root_item);// notify control + + return wxDataViewItem((void*)layer_root_item); +} + +wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item) +{ + ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); + if (!parent_node) return wxDataViewItem(0); + + // get LayerRoot node + const int root_idx = get_root_idx(parent_node, itLayerRoot); + if (root_idx < 0) return wxDataViewItem(0); + ObjectDataViewModelNode *layer_root_node = parent_node->GetNthChild(root_idx); + + const wxDataViewItem layer_root_item((void*)layer_root_node); + + // Add layer node + ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, itLayer); + layer_root_node->Append(layer_node); + + // notify control + const wxDataViewItem instance_item((void*)layer_node); + ItemAdded(layer_root_item, instance_item); + + return wxDataViewItem((void*)layer_node); +} + wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) { auto ret_item = wxDataViewItem(0); @@ -817,7 +901,7 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); if (!parent_node) return ret_item; - const int inst_root_id = get_istances_root_idx(parent_node); + const int inst_root_id = get_root_idx(parent_node, itInstanceRoot); if (inst_root_id < 0) return ret_item; wxDataViewItemArray items; @@ -2573,7 +2657,7 @@ ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 10*/) : m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first));; #endif // __WXOSX__ - m_mode_btns.back()->Bind(wxEVT_BUTTON, std::bind(modebtnfn, std::placeholders::_1, m_mode_btns.size() - 1)); + m_mode_btns.back()->Bind(wxEVT_BUTTON, std::bind(modebtnfn, std::placeholders::_1, int(m_mode_btns.size() - 1))); Add(m_mode_btns.back()); } } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 78fb7be55b..424f7832a1 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -159,12 +159,14 @@ DECLARE_VARIANT_OBJECT(DataViewBitmapText) // ---------------------------------------------------------------------------- enum ItemType { - itUndef = 0, - itObject = 1, - itVolume = 2, - itInstanceRoot = 4, - itInstance = 8, - itSettings = 16 + itUndef = 0, + itObject = 1, + itVolume = 2, + itInstanceRoot = 4, + itInstance = 8, + itSettings = 16, + itLayerRoot = 32, + itLayer = 64, }; class ObjectDataViewModelNode; @@ -348,7 +350,7 @@ public: } // Set action icons for node - void set_action_icon(); + void set_action_icon(); void update_settings_digest_bitmaps(); bool update_settings_digest(const std::vector& categories); @@ -388,6 +390,8 @@ public: const bool create_frst_child = true); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); + wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); + wxDataViewItem AddLayersChild(const wxDataViewItem &parent_item); wxDataViewItem Delete(const wxDataViewItem &item); wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); void DeleteAll(); From 9d19e3d2a72742fd3398174b01be7e1ad583407b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 27 May 2019 16:13:24 +0200 Subject: [PATCH 015/627] Improved Delete() Add() ans Select() functions for Layer(s)Item --- src/slic3r/GUI/GUI_ObjectList.cpp | 19 ++++--- src/slic3r/GUI/wxExtensions.cpp | 84 ++++++++++++++++++++----------- 2 files changed, 67 insertions(+), 36 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 5e14459e25..e180f1dfcd 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1648,6 +1648,8 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) del_settings_from_config(); else if (type == itInstanceRoot && obj_idx != -1) del_instances_from_object(obj_idx); + else if ((type & itLayerRoot) && obj_idx != -1) + /*del_layers_from_object(obj_idx)*/; else if (idx == -1) return; else if (!del_subobject_from_object(obj_idx, idx, type)) @@ -1728,6 +1730,8 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con } object->delete_instance(idx); } + else if (type == itLayer) { + } else return false; @@ -1791,9 +1795,11 @@ void ObjectList::layers_editing() if (!item || obj_idx < 0) return; - wxDataViewItem layers_item = m_objects_model->GetItemByType(item, itLayerRoot); + wxDataViewItem obj_item = m_objects_model->GetTopParent(item); + + wxDataViewItem layers_item = m_objects_model->GetItemByType(obj_item, itLayerRoot); if (!layers_item.IsOk()) - layers_item = m_objects_model->AddLayersRoot(item); + layers_item = m_objects_model->AddLayersRoot(obj_item); select_item(layers_item); } @@ -2171,7 +2177,8 @@ void ObjectList::update_selections() m_selection_mode = smInstance; // We doesn't update selection if SettingsItem for the current object/part is selected - if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) +// if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) + if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) & (itSettings | itLayerRoot | itLayer)) { const auto item = GetSelection(); if (selection.is_single_full_object() && @@ -2294,8 +2301,8 @@ void ObjectList::update_selections_on_canvas() auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool as_single_selection) { const ItemType& type = m_objects_model->GetItemType(item); - if ( type == itInstanceRoot || m_objects_model->GetParent(item) == wxDataViewItem(0) ) { - wxDataViewItem obj_item = type == itInstanceRoot ? m_objects_model->GetParent(item) : item; + if ( type == itLayerRoot || m_objects_model->GetParent(item) == wxDataViewItem(0) ) { + wxDataViewItem obj_item = type == itLayerRoot ? m_objects_model->GetParent(item) : item; selection.add_object(m_objects_model->GetIdByItem(obj_item), as_single_selection); return; } @@ -2317,7 +2324,7 @@ void ObjectList::update_selections_on_canvas() if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); - if (m_objects_model->GetItemType(item) & (itSettings|itInstanceRoot)) + if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, true); else add_to_selection(item, selection, instance_idx, true); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 988d4a8ea4..7849f66dd1 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -540,6 +540,22 @@ void ObjectDataViewModelNode::SetIdx(const int& idx) // ObjectDataViewModel // ---------------------------------------------------------------------------- +static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType root_type) +{ + // because of istance_root and layers_root are at the end of the list, so + // start locking from the end + for (int root_idx = parent_node->GetChildCount() - 1; root_idx >= 0; root_idx--) + { + // if there is SettingsItem or VolumeItem, then RootItems don't exist in current ObjectItem + if (parent_node->GetNthChild(root_idx)->GetType() & (itSettings | itVolume)) + break; + if (parent_node->GetNthChild(root_idx)->GetType() & root_type) + return root_idx; + } + + return -1; +} + ObjectDataViewModel::ObjectDataViewModel() { m_bitmap_cache = new Slic3r::GUI::BitmapCache; @@ -584,10 +600,10 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); - // because of istance_root is a last item of the object - int insert_position = root->GetChildCount() - 1; - if (insert_position < 0 || root->GetNthChild(insert_position)->m_type != itInstanceRoot) - insert_position = -1; + // get insertion position according to the existed Layers and/or Instances Items + int insert_position = get_root_idx(root, itLayerRoot); + if (insert_position < 0) + insert_position = get_root_idx(root, itInstanceRoot); const bool obj_errors = root->m_bmp.IsOk(); @@ -603,7 +619,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent ItemAdded(parent_item, child); root->m_volumes_cnt++; - if (insert_position > 0) insert_position++; + if (insert_position >= 0) insert_position++; } const auto node = new ObjectDataViewModelNode(root, name, GetVolumeIcon(volume_type, has_errors), extruder_str, root->m_volumes_cnt); @@ -636,22 +652,6 @@ wxDataViewItem ObjectDataViewModel::AddSettingsChild(const wxDataViewItem &paren return child; } -static int get_root_idx(ObjectDataViewModelNode *parent_node, const ItemType root_type) -{ - // because of istance_root and layers_root are at the end of the list, so - // start locking from the end - for (int root_idx = parent_node->GetChildCount() - 1; root_idx >= 0; root_idx--) - { - // if there is SettingsItem or VolumeItem, then RootItems don't exist in current ObjectItem - if (parent_node->GetNthChild(root_idx)->GetType() & (itSettings | itVolume)) - break; - if (parent_node->GetNthChild(root_idx)->GetType() & root_type) - return root_idx; - } - - return -1; -} - /* return values: * true => root_node is created and added to the parent_root * false => root node alredy exists @@ -723,6 +723,8 @@ wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_i if (appended) ItemAdded(parent_item, layer_root_item);// notify control + AddLayersChild(layer_root_item); + return wxDataViewItem((void*)layer_root_item); } @@ -732,9 +734,15 @@ wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_ if (!parent_node) return wxDataViewItem(0); // get LayerRoot node - const int root_idx = get_root_idx(parent_node, itLayerRoot); - if (root_idx < 0) return wxDataViewItem(0); - ObjectDataViewModelNode *layer_root_node = parent_node->GetNthChild(root_idx); + ObjectDataViewModelNode *layer_root_node; + + if (parent_node->GetType() & itLayerRoot) + layer_root_node = parent_node; + else { + const int root_idx = get_root_idx(parent_node, itLayerRoot); + if (root_idx < 0) return wxDataViewItem(0); + layer_root_node = parent_node->GetNthChild(root_idx); + } const wxDataViewItem layer_root_item((void*)layer_root_node); @@ -763,9 +771,9 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ // thus removing the node from it doesn't result in freeing it if (node_parent) { - if (node->m_type == itInstanceRoot) + if (node->m_type & (itInstanceRoot|itLayerRoot)) { - for (int i = node->GetChildCount() - 1; i > 0; i--) + for (int i = node->GetChildCount() - 1; i >= (node->m_type & itInstanceRoot ? 1 : 0); i--) Delete(wxDataViewItem(node->GetNthChild(i))); return parent; } @@ -774,7 +782,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) auto idx = node->GetIdx(); - if (node->m_type == itVolume) { + if (node->m_type & (itVolume|itLayer)) { node_parent->m_volumes_cnt--; DeleteSettings(item); } @@ -810,6 +818,22 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) delete node_parent; ret_item = wxDataViewItem(obj_node); +#ifndef __WXGTK__ + if (obj_node->GetChildCount() == 0) + obj_node->m_container = false; +#endif //__WXGTK__ + ItemDeleted(ret_item, wxDataViewItem(node_parent)); + return ret_item; + } + + // if there was last layer item, delete this one and layers root item + if (node_parent->GetChildCount() == 0 && node_parent->m_type == itLayerRoot) + { + ObjectDataViewModelNode *obj_node = node_parent->GetParent(); + obj_node->GetChildren().Remove(node_parent); + delete node_parent; + ret_item = wxDataViewItem(obj_node); + #ifndef __WXGTK__ if (obj_node->GetChildCount() == 0) obj_node->m_container = false; @@ -819,7 +843,7 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) } // if there is last volume item after deleting, delete this last volume too - if (node_parent->GetChildCount() <= 3) + if (node_parent->GetChildCount() <= 3) // 3??? #ys_FIXME { int vol_cnt = 0; int vol_idx = 0; @@ -1120,7 +1144,7 @@ void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type type = itUndef; ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - if (!node || node->GetIdx() <-1 || node->GetIdx() ==-1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot))) + if (!node || node->GetIdx() <-1 || node->GetIdx() == -1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot | itLayerRoot/* | itLayer*/))) return; idx = node->GetIdx(); @@ -1128,7 +1152,7 @@ void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type ObjectDataViewModelNode *parent_node = node->GetParent(); if (!parent_node) return; - if (type == itInstance) + if (type & (itInstance | itLayer)) parent_node = node->GetParent()->GetParent(); if (!parent_node || parent_node->m_type != itObject) { type = itUndef; return; } From 886da08f89f8d9a4a2149c2784305b18a24ac5f3 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 28 May 2019 12:53:16 +0200 Subject: [PATCH 016/627] Texture compression set as an option --- src/slic3r/GUI/3DBed.cpp | 4 ++ src/slic3r/GUI/GLCanvas3D.cpp | 36 +++++++++++-- src/slic3r/GUI/GLCanvas3D.hpp | 8 +++ src/slic3r/GUI/GLTexture.cpp | 64 +++++++++++++++-------- src/slic3r/GUI/GLTexture.hpp | 19 +++++++ src/slic3r/GUI/GLToolbar.cpp | 8 +++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 8 +++ src/slic3r/GUI/ImGuiWrapper.cpp | 10 +++- src/slic3r/GUI/ImGuiWrapper.hpp | 4 ++ 9 files changed, 133 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 2f20a65d90..4c4e600e8e 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -517,7 +517,11 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) { +#if ENABLE_COMPRESSED_TEXTURES + if (!m_texture.load_from_svg_file(filename, true, true, max_tex_size)) +#else if (!m_texture.load_from_svg_file(filename, true, max_tex_size)) +#endif // ENABLE_COMPRESSED_TEXTURES { render_custom(); return; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f64ed41a4a..f7d685502a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -405,7 +405,11 @@ void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas if (m_tooltip_texture.get_id() == 0) { std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png"; +#if ENABLE_COMPRESSED_TEXTURES + if (!m_tooltip_texture.load_from_file(filename, false, true)) +#else if (!m_tooltip_texture.load_from_file(filename, false)) +#endif // ENABLE_COMPRESSED_TEXTURES return; } @@ -437,7 +441,11 @@ void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) co if (m_reset_texture.get_id() == 0) { std::string filename = resources_dir() + "/icons/variable_layer_height_reset.png"; +#if ENABLE_COMPRESSED_TEXTURES + if (!m_reset_texture.load_from_file(filename, false, true)) +#else if (!m_reset_texture.load_from_file(filename, false)) +#endif // ENABLE_COMPRESSED_TEXTURES return; } @@ -729,7 +737,11 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool } } +#if ENABLE_COMPRESSED_TEXTURES + generate(text, canvas, true, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...) +#else _generate(text, canvas, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...) +#endif // ENABLE_COMPRESSED_TEXTURES // save information for rescaling m_msg_text = text; @@ -790,7 +802,11 @@ static void msw_disable_cleartype(wxFont &font) } #endif /* __WXMSW__ */ +#if ENABLE_COMPRESSED_TEXTURES +bool GLCanvas3D::WarningTexture::generate(const std::string& msg_utf8, const GLCanvas3D& canvas, bool compress, bool red_colored/* = false*/) +#else bool GLCanvas3D::WarningTexture::_generate(const std::string& msg_utf8, const GLCanvas3D& canvas, const bool red_colored/* = false*/) +#endif // ENABLE_COMPRESSED_TEXTURES { reset(); @@ -865,7 +881,7 @@ bool GLCanvas3D::WarningTexture::_generate(const std::string& msg_utf8, const GL glsafe(::glGenTextures(1, &m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id)); #if ENABLE_COMPRESSED_TEXTURES - if (GLEW_EXT_texture_compression_s3tc) + if (compress && GLEW_EXT_texture_compression_s3tc) glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); @@ -922,7 +938,11 @@ void GLCanvas3D::WarningTexture::msw_rescale(const GLCanvas3D& canvas) if (m_msg_text.empty()) return; +#if ENABLE_COMPRESSED_TEXTURES + generate(m_msg_text, canvas, true, m_is_colored_red); +#else _generate(m_msg_text, canvas, m_is_colored_red); +#endif // ENABLE_COMPRESSED_TEXTURES } const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 }; @@ -965,7 +985,11 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePrevie } } +#if ENABLE_COMPRESSED_TEXTURES +bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas, bool compress) +#else bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas) +#endif // ENABLE_COMPRESSED_TEXTURES { reset(); @@ -1155,7 +1179,7 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c glsafe(::glGenTextures(1, &m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id)); #if ENABLE_COMPRESSED_TEXTURES - if (GLEW_EXT_texture_compression_s3tc) + if (compress && GLEW_EXT_texture_compression_s3tc) glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); @@ -1714,9 +1738,9 @@ void GLCanvas3D::render() imgui.text(" ms"); #if ENABLE_COMPRESSED_TEXTURES ImGui::Separator(); - imgui.text("Textures: "); + imgui.text("Compressed textures: "); ImGui::SameLine(); - imgui.text(GLCanvas3DManager::are_compressed_textures_supported() ? "compressed" : "uncompressed"); + imgui.text(GLCanvas3DManager::are_compressed_textures_supported() ? "supported" : "not supported"); #endif // ENABLE_COMPRESSED_TEXTURES #if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION imgui.text("Max texture size: "); @@ -5698,7 +5722,11 @@ std::vector GLCanvas3D::_parse_colors(const std::vector& col void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector& tool_colors) { +#if ENABLE_COMPRESSED_TEXTURES + m_legend_texture.generate(preview_data, tool_colors, *this, true); +#else m_legend_texture.generate(preview_data, tool_colors, *this); +#endif // ENABLE_COMPRESSED_TEXTURES } void GLCanvas3D::_set_warning_texture(WarningTexture::Warning warning, bool state) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 51a36cf69f..6d77a507f1 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -374,7 +374,11 @@ class GLCanvas3D std::vector m_warnings; // Generates the texture with given text. +#if ENABLE_COMPRESSED_TEXTURES + bool generate(const std::string& msg, const GLCanvas3D& canvas, bool compress, bool red_colored = false); +#else bool _generate(const std::string& msg, const GLCanvas3D& canvas, const bool red_colored = false); +#endif // ENABLE_COMPRESSED_TEXTURES }; class LegendTexture : public GUI::GLTexture @@ -397,7 +401,11 @@ class GLCanvas3D void fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas, std::vector>& cp_legend_values); +#if ENABLE_COMPRESSED_TEXTURES + bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas, bool compress); +#else bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas); +#endif // ENABLE_COMPRESSED_TEXTURES void render(const GLCanvas3D& canvas) const; }; diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 2216a14f24..bdd7ee6248 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -18,10 +18,6 @@ #include "libslic3r/Utils.hpp" -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#include -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - namespace Slic3r { namespace GUI { @@ -40,7 +36,11 @@ GLTexture::~GLTexture() reset(); } +#if ENABLE_COMPRESSED_TEXTURES +bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, bool compress) +#else bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps) +#endif // ENABLE_COMPRESSED_TEXTURES { reset(); @@ -48,12 +48,20 @@ bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps) return false; if (boost::algorithm::iends_with(filename, ".png")) +#if ENABLE_COMPRESSED_TEXTURES + return load_from_png(filename, use_mipmaps, compress); +#else return load_from_png(filename, use_mipmaps); +#endif // ENABLE_COMPRESSED_TEXTURES else return false; } +#if ENABLE_COMPRESSED_TEXTURES +bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, unsigned int max_size_px) +#else bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px) +#endif // ENABLE_COMPRESSED_TEXTURES { reset(); @@ -61,12 +69,20 @@ bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps return false; if (boost::algorithm::iends_with(filename, ".svg")) +#if ENABLE_COMPRESSED_TEXTURES + return load_from_svg(filename, use_mipmaps, compress, max_size_px); +#else return load_from_svg(filename, use_mipmaps, max_size_px); +#endif // ENABLE_COMPRESSED_TEXTURES else return false; } +#if ENABLE_COMPRESSED_TEXTURES +bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px, bool compress) +#else bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px) +#endif // ENABLE_COMPRESSED_TEXTURES { reset(); @@ -183,7 +199,7 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector(end_time - start_time).count() << std::endl; - start_time = end_time; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (use_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards @@ -455,7 +478,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns nsvgRasterize(rast, image, 0, 0, scale, data.data(), lod_w, lod_h, lod_w * 4); #if ENABLE_COMPRESSED_TEXTURES - if (GLEW_EXT_texture_compression_s3tc) + if (compress && GLEW_EXT_texture_compression_s3tc) glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); else glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); @@ -481,11 +504,6 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns nsvgDeleteRasterizer(rast); nsvgDelete(image); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - end_time = std::chrono::high_resolution_clock::now(); - std::cout << "mipmaps to GPU in: " << std::chrono::duration_cast(end_time - start_time).count() << std::endl; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - return true; } diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index e00b3a3bea..b474f7b053 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -38,8 +38,13 @@ namespace GUI { GLTexture(); virtual ~GLTexture(); +#if ENABLE_COMPRESSED_TEXTURES + bool load_from_file(const std::string& filename, bool use_mipmaps, bool compress); + bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, unsigned int max_size_px); +#else bool load_from_file(const std::string& filename, bool use_mipmaps); bool load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); +#endif // ENABLE_COMPRESSED_TEXTURES // meanings of states: (std::pair) // first field (int): // 0 -> no changes @@ -48,7 +53,11 @@ namespace GUI { // second field (bool): // false -> no changes // true -> add background color +#if ENABLE_COMPRESSED_TEXTURES + bool load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px, bool compress); +#else bool load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px); +#endif // ENABLE_COMPRESSED_TEXTURES void reset(); unsigned int get_id() const { return m_id; } @@ -61,10 +70,20 @@ namespace GUI { static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs); protected: +#if ENABLE_COMPRESSED_TEXTURES + unsigned int generate_mipmaps(wxImage& image, bool compress); +#else unsigned int generate_mipmaps(wxImage& image); +#endif // ENABLE_COMPRESSED_TEXTURES + private: +#if ENABLE_COMPRESSED_TEXTURES + bool load_from_png(const std::string& filename, bool use_mipmaps, bool compress); + bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, unsigned int max_size_px); +#else bool load_from_png(const std::string& filename, bool use_mipmaps); bool load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); +#endif // ENABLE_COMPRESSED_TEXTURES }; } // namespace GUI diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 00cbdfec71..813a319b80 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -194,7 +194,11 @@ bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const Bac #endif // ENABLE_SVG_ICONS if (!background_texture.filename.empty()) +#if ENABLE_COMPRESSED_TEXTURES + res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, true); +#else res = m_background_texture.texture.load_from_file(path + background_texture.filename, false); +#endif // ENABLE_COMPRESSED_TEXTURES if (res) m_background_texture.metadata = background_texture; @@ -1338,7 +1342,11 @@ bool GLToolbar::generate_icons_texture() const states.push_back(std::make_pair(1, true)); } +#if ENABLE_COMPRESSED_TEXTURES + bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_layout.icons_size * m_layout.scale), true); +#else bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_layout.icons_size * m_layout.scale)); +#endif // ENABLE_COMPRESSED_TEXTURES if (res) m_icons_texture_dirty = false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 1006d2bd1e..c254f5796d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -65,7 +65,11 @@ bool GLGizmosManager::init(GLCanvas3D& parent) if (!m_background_texture.metadata.filename.empty()) { +#if ENABLE_COMPRESSED_TEXTURES + if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false, true)) +#else if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false)) +#endif // ENABLE_COMPRESSED_TEXTURES { reset(); return false; @@ -1160,7 +1164,11 @@ bool GLGizmosManager::generate_icons_texture() const states.push_back(std::make_pair(0, false)); states.push_back(std::make_pair(0, true)); +#if ENABLE_COMPRESSED_TEXTURES + bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_overlay_icons_size * m_overlay_scale), true); +#else bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_overlay_icons_size * m_overlay_scale)); +#endif // ENABLE_COMPRESSED_TEXTURES if (res) m_icons_texture_dirty = false; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 9202c0ac38..7267da2956 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -206,7 +206,11 @@ void ImGuiWrapper::new_frame() } if (m_font_texture == 0) { +#if ENABLE_COMPRESSED_TEXTURES + init_font(true); +#else init_font(); +#endif // ENABLE_COMPRESSED_TEXTURES } ImGui::NewFrame(); @@ -383,7 +387,11 @@ bool ImGuiWrapper::want_any_input() const return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; } +#if ENABLE_COMPRESSED_TEXTURES +void ImGuiWrapper::init_font(bool compress) +#else void ImGuiWrapper::init_font() +#endif // ENABLE_COMPRESSED_TEXTURES { destroy_font(); @@ -413,7 +421,7 @@ void ImGuiWrapper::init_font() glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); #if ENABLE_COMPRESSED_TEXTURES - if (GLEW_EXT_texture_compression_s3tc) + if (compress && GLEW_EXT_texture_compression_s3tc) glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 37ef90ff35..a1e4048ac6 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -77,7 +77,11 @@ public: bool want_any_input() const; private: +#if ENABLE_COMPRESSED_TEXTURES + void init_font(bool compress); +#else void init_font(); +#endif // ENABLE_COMPRESSED_TEXTURES void init_input(); void init_style(); void render_draw_data(ImDrawData *draw_data); From 8012499206ba2a4fa1b667ce47e4254b5f378aca Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 28 May 2019 15:21:34 +0200 Subject: [PATCH 017/627] Application of anisotropy to textures moved into GLTexture methods --- src/slic3r/GUI/3DBed.cpp | 8 +++++++- src/slic3r/GUI/GLTexture.cpp | 13 ++++++++++--- src/slic3r/GUI/GLTexture.hpp | 4 ++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 4c4e600e8e..91275f4e62 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -494,16 +494,20 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const std::string model_path = resources_dir() + "/models/" + key; #if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION +#if !ENABLE_COMPRESSED_TEXTURES // use anisotropic filter if graphic card allows GLfloat max_anisotropy = GLCanvas3DManager::get_gl_info().get_max_anisotropy(); +#endif // !ENABLE_COMPRESSED_TEXTURES // use higher resolution images if graphic card and opengl version allow GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); #else +#if !ENABLE_COMPRESSED_TEXTURES // use anisotropic filter if graphic card allows GLfloat max_anisotropy = 0.0f; if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy)); +#endif // !ENABLE_COMPRESSED_TEXTURES // use higher resolution images if graphic card allows GLint max_tex_size; @@ -518,7 +522,7 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) { #if ENABLE_COMPRESSED_TEXTURES - if (!m_texture.load_from_svg_file(filename, true, true, max_tex_size)) + if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size)) #else if (!m_texture.load_from_svg_file(filename, true, max_tex_size)) #endif // ENABLE_COMPRESSED_TEXTURES @@ -527,12 +531,14 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const return; } +#if !ENABLE_COMPRESSED_TEXTURES if (max_anisotropy > 0.0f) { glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.get_id())); glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); } +#endif // !ENABLE_COMPRESSED_TEXTURES } if (!bottom) diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index bdd7ee6248..9c1e1f22ef 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -58,7 +58,7 @@ bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps) } #if ENABLE_COMPRESSED_TEXTURES -bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, unsigned int max_size_px) +bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) #else bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px) #endif // ENABLE_COMPRESSED_TEXTURES @@ -70,7 +70,7 @@ bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps if (boost::algorithm::iends_with(filename, ".svg")) #if ENABLE_COMPRESSED_TEXTURES - return load_from_svg(filename, use_mipmaps, compress, max_size_px); + return load_from_svg(filename, use_mipmaps, compress, apply_anisotropy, max_size_px); #else return load_from_svg(filename, use_mipmaps, max_size_px); #endif // ENABLE_COMPRESSED_TEXTURES @@ -411,7 +411,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps) } #if ENABLE_COMPRESSED_TEXTURES -bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, unsigned int max_size_px) +bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) #else bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px) #endif // ENABLE_COMPRESSED_TEXTURES @@ -455,6 +455,13 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns glsafe(::glGenTextures(1, &m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_id)); #if ENABLE_COMPRESSED_TEXTURES + if (apply_anisotropy) + { + GLfloat max_anisotropy = GLCanvas3DManager::get_gl_info().get_max_anisotropy(); + if (max_anisotropy > 1.0f) + glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); + } + if (compress && GLEW_EXT_texture_compression_s3tc) glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); else diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index b474f7b053..032d19121f 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -40,7 +40,7 @@ namespace GUI { #if ENABLE_COMPRESSED_TEXTURES bool load_from_file(const std::string& filename, bool use_mipmaps, bool compress); - bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, unsigned int max_size_px); + bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); #else bool load_from_file(const std::string& filename, bool use_mipmaps); bool load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); @@ -79,7 +79,7 @@ namespace GUI { private: #if ENABLE_COMPRESSED_TEXTURES bool load_from_png(const std::string& filename, bool use_mipmaps, bool compress); - bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, unsigned int max_size_px); + bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); #else bool load_from_png(const std::string& filename, bool use_mipmaps); bool load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); From 765d4264ae9380a549bd84bd8d79d776858d8816 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 28 May 2019 16:38:04 +0200 Subject: [PATCH 018/627] Implemented ObjectLayers class + some code refactoring --- src/slic3r/GUI/GUI_App.cpp | 5 + src/slic3r/GUI/GUI_App.hpp | 1 + src/slic3r/GUI/GUI_ObjectLayers.cpp | 150 ++++++++++++++++++++++++++ src/slic3r/GUI/GUI_ObjectLayers.hpp | 36 +++++++ src/slic3r/GUI/GUI_ObjectList.cpp | 29 ++++- src/slic3r/GUI/GUI_ObjectList.hpp | 12 ++- src/slic3r/GUI/GUI_ObjectSettings.cpp | 4 +- src/slic3r/GUI/Plater.cpp | 13 +++ src/slic3r/GUI/Plater.hpp | 2 + 9 files changed, 243 insertions(+), 9 deletions(-) create mode 100644 src/slic3r/GUI/GUI_ObjectLayers.cpp create mode 100644 src/slic3r/GUI/GUI_ObjectLayers.hpp diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b75b946e6f..405de3ae99 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -924,6 +924,11 @@ ObjectList* GUI_App::obj_list() return sidebar().obj_list(); } +ObjectLayers* GUI_App::obj_layers() +{ + return sidebar().obj_layers(); +} + Plater* GUI_App::plater() { return plater_; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index b70f0dc160..6751218249 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -155,6 +155,7 @@ public: ObjectManipulation* obj_manipul(); ObjectSettings* obj_settings(); ObjectList* obj_list(); + ObjectLayers* obj_layers(); Plater* plater(); std::vector *model_objects(); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp new file mode 100644 index 0000000000..31a17bbf65 --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -0,0 +1,150 @@ +#include "GUI_ObjectLayers.hpp" +#include "GUI_ObjectList.hpp" + +#include "OptionsGroup.hpp" +#include "PresetBundle.hpp" +#include "libslic3r/Model.hpp" + +#include + +#include "I18N.hpp" + +#include + +namespace Slic3r +{ +namespace GUI +{ + +ObjectLayers::ObjectLayers(wxWindow* parent) : + OG_Settings(parent, true) +{ + m_og->label_width = 0; + m_og->set_grid_vgap(5); + + // Legend for object layers + Line line = Line{ "", "" }; + + ConfigOptionDef def; + def.label = ""; + def.gui_type = "legend"; + def.type = coString; + def.width = field_width; + + for (const std::string axis : { "Min Z", "Max Z", "Layer height" }) { + def.set_default_value(new ConfigOptionString{ axis }); + std::string label = boost::algorithm::replace_all_copy(axis, " ", "_"); + boost::algorithm::to_lower(label); + line.append_option(Option(def, label + "_legend")); + } + + m_og->append_line(line); + + m_bmp_delete = ScalableBitmap(parent, "cross"); + m_bmp_add = ScalableBitmap(parent, "add_copies"); +} + +void ObjectLayers::update_layers_list() +{ + ObjectList* objects_ctrl = wxGetApp().obj_list(); + if (objects_ctrl->multiple_selection()) return; + + const auto item = objects_ctrl->GetSelection(); + if (!item) return; + + const int obj_idx = objects_ctrl->get_selected_obj_idx(); + if (obj_idx < 0) return; + + const ItemType type = objects_ctrl->GetModel()->GetItemType(item); + if (!(type & (itLayerRoot | itLayer))) return; + + ModelObject* object = objects_ctrl->object(obj_idx); + if (!object || object->layer_height_ranges.empty()) return; + + auto grid_sizer = m_og->get_grid_sizer(); + + const int cols = grid_sizer->GetCols(); + const int rows = grid_sizer->GetRows(); + for (int idx = cols*rows-1; idx >= cols; idx--) { + grid_sizer->Remove(idx); + } + + ConfigOptionDef def; + def.label = ""; + def.gui_type = ""; + def.type = coFloat; + def.width = field_width; + + if (type & itLayerRoot) + { + auto create_btns = [this](wxWindow* parent) { + auto sizer = new wxBoxSizer(wxHORIZONTAL); + auto del_btn = new ScalableButton(parent, wxID_ANY, m_bmp_delete); + del_btn->SetToolTip(_(L("Remove layer"))); + + sizer->Add(del_btn, 0, wxRIGHT, em_unit(parent)); + + del_btn->Bind(wxEVT_BUTTON, [this](wxEvent &event) { + del_layer(); +// wxTheApp->CallAfter([this]() { +// wxWindowUpdateLocker noUpdates(m_parent); +// update_layers_list(); +// m_parent->Layout(); +// }); + }); + + auto add_btn = new ScalableButton(parent, wxID_ANY, m_bmp_add); + add_btn->SetToolTip(_(L("Add layer"))); + + sizer->Add(add_btn, 0, wxRIGHT, em_unit(parent)); + + add_btn->Bind(wxEVT_BUTTON, [this](wxEvent &event) { + add_layer(); +// wxTheApp->CallAfter([this]() { +// wxWindowUpdateLocker noUpdates(m_parent); +// update_layers_list(); +// m_parent->Layout(); +// }); + }); + + return sizer; + }; + + Line line{"",""}; + for (const auto layer : object->layer_height_ranges) + { + std::string label = (boost::format("min_z_%.2f") % layer.first.first).str(); + def.set_default_value(new ConfigOptionFloat(layer.first.first)); + line.append_option(Option(def, label)); + + label = (boost::format("max_z_%.2f") % layer.first.second).str(); + def.set_default_value(new ConfigOptionFloat(layer.first.second)); + line.append_option(Option(def, label)); + + label = (boost::format("layer_height_%.2f_%.2f") % layer.first.first % layer.first.second).str(); + def.set_default_value(new ConfigOptionFloat(layer.second)); + line.append_option(Option(def, label)); + + line.append_widget(create_btns); + } + + m_og->append_line(line); + } +} + +void ObjectLayers::UpdateAndShow(const bool show) +{ + if (show) + update_layers_list(); + + OG_Settings::UpdateAndShow(show); +} + +void ObjectLayers::msw_rescale() +{ + m_bmp_delete.msw_rescale(); + m_bmp_add.msw_rescale(); +} + +} //namespace GUI +} //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp new file mode 100644 index 0000000000..8f8b55998c --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -0,0 +1,36 @@ +#ifndef slic3r_GUI_ObjectLayers_hpp_ +#define slic3r_GUI_ObjectLayers_hpp_ + +#include "GUI_ObjectSettings.hpp" +#include "wxExtensions.hpp" + +class wxBoxSizer; + +namespace Slic3r { +class ModelObject; + +namespace GUI { +class ConfigOptionsGroup; + +class ObjectLayers : public OG_Settings +{ + ScalableBitmap m_bmp_delete; + ScalableBitmap m_bmp_add; + + int field_width {8}; + +public: + ObjectLayers(wxWindow* parent); + ~ObjectLayers() {} + + void update_layers_list(); + void add_layer() {}; + void del_layer() {}; + + void UpdateAndShow(const bool show) override; + void msw_rescale(); +}; + +}} + +#endif // slic3r_GUI_ObjectLayers_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index e180f1dfcd..080ebbe216 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" +#include "GUI_ObjectLayers.hpp" #include "GUI_App.hpp" #include "I18N.hpp" @@ -1799,7 +1800,11 @@ void ObjectList::layers_editing() wxDataViewItem layers_item = m_objects_model->GetItemByType(obj_item, itLayerRoot); if (!layers_item.IsOk()) + { + const t_layer_height_range first_range = { 0.0f, 0.2f }; + object(obj_idx)->layer_height_ranges[first_range] = 0.1f; layers_item = m_objects_model->AddLayersRoot(obj_item); + } select_item(layers_item); } @@ -1873,6 +1878,7 @@ void ObjectList::part_selection_changed() bool update_and_show_manipulations = false; bool update_and_show_settings = false; + bool update_and_show_layers = false; const auto item = GetSelection(); @@ -1898,7 +1904,8 @@ void ObjectList::part_selection_changed() auto parent = m_objects_model->GetParent(item); // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene obj_idx = m_objects_model->GetIdByItem(parent); - if (m_objects_model->GetItemType(item) == itSettings) { + const ItemType type = m_objects_model->GetItemType(item); + if (type & itSettings) { if (m_objects_model->GetParent(parent) == wxDataViewItem(0)) { og_name = _(L("Object Settings to modify")); m_config = &(*m_objects)[obj_idx]->config; @@ -1912,13 +1919,13 @@ void ObjectList::part_selection_changed() } update_and_show_settings = true; } - else if (m_objects_model->GetItemType(item) == itVolume) { + else if (type & itVolume) { og_name = _(L("Part manipulation")); volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; } - else if (m_objects_model->GetItemType(item) == itInstance) { + else if (type & itInstance) { og_name = _(L("Instance manipulation")); update_and_show_manipulations = true; @@ -1926,6 +1933,10 @@ void ObjectList::part_selection_changed() const int obj_idx_ = m_objects_model->GetObjectIdByItem(item); m_config = &(*m_objects)[obj_idx_]->config; } + else if (type & (itLayerRoot|itLayer)) { + og_name = type & itLayerRoot ? _(L("Layers Editing")) : _(L("Layer Editing")); + update_and_show_layers = true; + } } } } @@ -1944,11 +1955,15 @@ void ObjectList::part_selection_changed() if (update_and_show_settings) wxGetApp().obj_settings()->get_og()->set_name(" " + og_name + " "); + if (update_and_show_layers) + wxGetApp().obj_layers()->get_og()->set_name(" " + og_name + " "); + Sidebar& panel = wxGetApp().sidebar(); panel.Freeze(); wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); + wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers); wxGetApp().sidebar().show_info_sizer(); panel.Layout(); @@ -2946,5 +2961,13 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const wxGetApp().plater()->update(); } +ModelObject* ObjectList::object(const int obj_idx) const +{ + if (obj_idx < 0) + return nullptr; + + return (*m_objects)[obj_idx]; +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 076ab5f13d..764aac1e89 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -122,6 +122,10 @@ class ObjectList : public wxDataViewCtrl wxMenuItem* m_menu_item_settings { nullptr }; wxMenuItem* m_menu_item_split_instances { nullptr }; + ObjectDataViewModel *m_objects_model{ nullptr }; + DynamicPrintConfig *m_config {nullptr}; + std::vector *m_objects{ nullptr }; + std::vector m_bmp_vector; int m_selected_object_id = -1; @@ -151,11 +155,11 @@ public: std::map CATEGORY_ICON; - ObjectDataViewModel *m_objects_model{ nullptr }; - DynamicPrintConfig *m_config {nullptr}; - - std::vector *m_objects{ nullptr }; + ObjectDataViewModel* GetModel() const { return m_objects_model; } + DynamicPrintConfig* config() const { return m_config; } + std::vector* objects() const { return m_objects; } + ModelObject* object(const int obj_idx) const ; void create_objects_ctrl(); void create_popup_menus(); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 4107e872a3..a4aa7dec2e 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -68,8 +68,8 @@ void ObjectSettings::update_settings_list() m_settings_list_sizer->Clear(true); auto objects_ctrl = wxGetApp().obj_list(); - auto objects_model = wxGetApp().obj_list()->m_objects_model; - auto config = wxGetApp().obj_list()->m_config; + auto objects_model = wxGetApp().obj_list()->GetModel(); + auto config = wxGetApp().obj_list()->config(); const auto item = objects_ctrl->GetSelection(); if (item && !objects_ctrl->multiple_selection() && diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c0f2672042..88abe9b835 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -43,6 +43,7 @@ #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" +#include "GUI_ObjectLayers.hpp" #include "GUI_Utils.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" @@ -611,6 +612,7 @@ struct Sidebar::priv ObjectList *object_list; ObjectManipulation *object_manipulation; ObjectSettings *object_settings; + ObjectLayers *object_layers; ObjectInfo *object_info; SlicedInfo *sliced_info; @@ -729,6 +731,11 @@ Sidebar::Sidebar(Plater *parent) p->object_settings = new ObjectSettings(p->scrolled); p->object_settings->Hide(); p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); + + // Object Layers + p->object_layers = new ObjectLayers(p->scrolled); + p->object_layers->Hide(); + p->sizer_params->Add(p->object_layers->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); // Info boxes p->object_info = new ObjectInfo(p->scrolled); @@ -922,6 +929,7 @@ void Sidebar::msw_rescale() p->object_list->msw_rescale(); p->object_manipulation->msw_rescale(); p->object_settings->msw_rescale(); + p->object_layers->msw_rescale(); p->object_info->msw_rescale(); @@ -943,6 +951,11 @@ ObjectSettings* Sidebar::obj_settings() return p->object_settings; } +ObjectLayers* Sidebar::obj_layers() +{ + return p->object_layers; +} + wxScrolledWindow* Sidebar::scrolled_panel() { return p->scrolled; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 16c9cbe649..3e50797a63 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -33,6 +33,7 @@ class MainFrame; class ConfigOptionsGroup; class ObjectManipulation; class ObjectSettings; +class ObjectLayers; class ObjectList; class GLCanvas3D; @@ -93,6 +94,7 @@ public: ObjectManipulation* obj_manipul(); ObjectList* obj_list(); ObjectSettings* obj_settings(); + ObjectLayers* obj_layers(); wxScrolledWindow* scrolled_panel(); wxPanel* presets_panel(); From bf56d79354cd74d645ea3e030c476c41d9c649eb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 30 May 2019 12:41:16 +0200 Subject: [PATCH 019/627] Layers sizer updating --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 173 +++++++++++++++++----------- src/slic3r/GUI/GUI_ObjectLayers.hpp | 9 +- src/slic3r/GUI/GUI_ObjectList.cpp | 4 + src/slic3r/GUI/OptionsGroup.cpp | 11 ++ src/slic3r/GUI/OptionsGroup.hpp | 2 + 5 files changed, 128 insertions(+), 71 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 31a17bbf65..80aa6bccac 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -14,13 +14,17 @@ namespace Slic3r { namespace GUI -{ +{ + +typedef std::map t_layer_height_ranges; + +#define field_width 8 ObjectLayers::ObjectLayers(wxWindow* parent) : OG_Settings(parent, true) { - m_og->label_width = 0; - m_og->set_grid_vgap(5); + m_og->label_width = 1; +// m_og->set_grid_vgap(5); // Legend for object layers Line line = Line{ "", "" }; @@ -36,6 +40,8 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : std::string label = boost::algorithm::replace_all_copy(axis, " ", "_"); boost::algorithm::to_lower(label); line.append_option(Option(def, label + "_legend")); + + m_legends.push_back(label + "_legend"); } m_og->append_line(line); @@ -44,6 +50,83 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : m_bmp_add = ScalableBitmap(parent, "add_copies"); } +static Line create_new_layer(const t_layer_height_ranges::value_type& layer) +{ + Line line = Line{ "", "" }; + ConfigOptionDef def; + def.label = ""; + def.gui_type = ""; + def.type = coFloat; + def.width = field_width; + + std::string label = (boost::format("min_z_%.2f") % layer.first.first).str(); + def.set_default_value(new ConfigOptionFloat(layer.first.first)); + line.append_option(Option(def, label)); + + label = (boost::format("max_z_%.2f") % layer.first.second).str(); + def.set_default_value(new ConfigOptionFloat(layer.first.second)); + line.append_option(Option(def, label)); + + label = (boost::format("layer_height_%.2f_%.2f") % layer.first.first % layer.first.second).str(); + def.set_default_value(new ConfigOptionFloat(layer.second)); + line.append_option(Option(def, label)); + + return line; +} + +void ObjectLayers::create_layers_list() +{ + auto create_btns = [this](wxWindow* parent) { + auto sizer = new wxBoxSizer(wxHORIZONTAL); + + auto del_btn = new ScalableButton(parent, wxID_ANY, m_bmp_delete); + del_btn->SetToolTip(_(L("Remove layer"))); + + sizer->Add(del_btn, 0, wxRIGHT, em_unit(parent)); + + del_btn->Bind(wxEVT_BUTTON, [this](wxEvent &event) { + del_layer(); +// wxTheApp->CallAfter([this]() { +// wxWindowUpdateLocker noUpdates(m_parent); +// update_layers_list(); +// m_parent->Layout(); +// }); + }); + + auto add_btn = new ScalableButton(parent, wxID_ANY, m_bmp_add); + add_btn->SetToolTip(_(L("Add layer"))); + + sizer->Add(add_btn, 0, wxRIGHT, em_unit(parent)); + + add_btn->Bind(wxEVT_BUTTON, [this](wxEvent &event) { + add_layer(); +// wxTheApp->CallAfter([this]() { +// wxWindowUpdateLocker noUpdates(m_parent); +// update_layers_list(); +// m_parent->Layout(); +// }); + }); + + return sizer; + }; + + for (const auto layer : m_object->layer_height_ranges) + { + Line line = create_new_layer(layer); + line.append_widget(create_btns); + m_og->append_line(line); + } +} + +void ObjectLayers::create_layer() +{ + for (const auto layer : m_object->layer_height_ranges) + { + m_og->append_line(create_new_layer(layer)); + break; + } +} + void ObjectLayers::update_layers_list() { ObjectList* objects_ctrl = wxGetApp().obj_list(); @@ -58,78 +141,32 @@ void ObjectLayers::update_layers_list() const ItemType type = objects_ctrl->GetModel()->GetItemType(item); if (!(type & (itLayerRoot | itLayer))) return; - ModelObject* object = objects_ctrl->object(obj_idx); - if (!object || object->layer_height_ranges.empty()) return; + m_object = objects_ctrl->object(obj_idx); + if (!m_object || m_object->layer_height_ranges.empty()) return; + + // Delete all controls from options group except of the legends auto grid_sizer = m_og->get_grid_sizer(); - - const int cols = grid_sizer->GetCols(); - const int rows = grid_sizer->GetRows(); + const int cols = grid_sizer->GetEffectiveColsCount(); + const int rows = grid_sizer->GetEffectiveRowsCount(); for (int idx = cols*rows-1; idx >= cols; idx--) { - grid_sizer->Remove(idx); + wxSizerItem* t = grid_sizer->GetItem(idx); + if (t->IsSizer()) + t->GetSizer()->Clear(true); + grid_sizer->Remove(idx); } - ConfigOptionDef def; - def.label = ""; - def.gui_type = ""; - def.type = coFloat; - def.width = field_width; + m_og->clear_fields_except_of(m_legends); + + + // Add new control according to the selected item if (type & itLayerRoot) - { - auto create_btns = [this](wxWindow* parent) { - auto sizer = new wxBoxSizer(wxHORIZONTAL); - auto del_btn = new ScalableButton(parent, wxID_ANY, m_bmp_delete); - del_btn->SetToolTip(_(L("Remove layer"))); - - sizer->Add(del_btn, 0, wxRIGHT, em_unit(parent)); - - del_btn->Bind(wxEVT_BUTTON, [this](wxEvent &event) { - del_layer(); -// wxTheApp->CallAfter([this]() { -// wxWindowUpdateLocker noUpdates(m_parent); -// update_layers_list(); -// m_parent->Layout(); -// }); - }); - - auto add_btn = new ScalableButton(parent, wxID_ANY, m_bmp_add); - add_btn->SetToolTip(_(L("Add layer"))); - - sizer->Add(add_btn, 0, wxRIGHT, em_unit(parent)); - - add_btn->Bind(wxEVT_BUTTON, [this](wxEvent &event) { - add_layer(); -// wxTheApp->CallAfter([this]() { -// wxWindowUpdateLocker noUpdates(m_parent); -// update_layers_list(); -// m_parent->Layout(); -// }); - }); - - return sizer; - }; - - Line line{"",""}; - for (const auto layer : object->layer_height_ranges) - { - std::string label = (boost::format("min_z_%.2f") % layer.first.first).str(); - def.set_default_value(new ConfigOptionFloat(layer.first.first)); - line.append_option(Option(def, label)); - - label = (boost::format("max_z_%.2f") % layer.first.second).str(); - def.set_default_value(new ConfigOptionFloat(layer.first.second)); - line.append_option(Option(def, label)); - - label = (boost::format("layer_height_%.2f_%.2f") % layer.first.first % layer.first.second).str(); - def.set_default_value(new ConfigOptionFloat(layer.second)); - line.append_option(Option(def, label)); - - line.append_widget(create_btns); - } - - m_og->append_line(line); - } + create_layers_list(); + else + create_layer(); + + m_parent->Layout(); } void ObjectLayers::UpdateAndShow(const bool show) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 8f8b55998c..13a3da911d 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -14,15 +14,18 @@ class ConfigOptionsGroup; class ObjectLayers : public OG_Settings { - ScalableBitmap m_bmp_delete; - ScalableBitmap m_bmp_add; + ScalableBitmap m_bmp_delete; + ScalableBitmap m_bmp_add; + ModelObject* m_object {nullptr}; - int field_width {8}; + std::vector m_legends; public: ObjectLayers(wxWindow* parent); ~ObjectLayers() {} + void create_layers_list(); + void create_layer(); void update_layers_list(); void add_layer() {}; void del_layer() {}; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 080ebbe216..00eb3e7de0 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1803,6 +1803,10 @@ void ObjectList::layers_editing() { const t_layer_height_range first_range = { 0.0f, 0.2f }; object(obj_idx)->layer_height_ranges[first_range] = 0.1f; + + const t_layer_height_range second_range = { 0.2f, 0.4f }; + object(obj_idx)->layer_height_ranges[second_range] = 0.05f; + layers_item = m_objects_model->AddLayersRoot(obj_item); } diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 2ac6b00af6..67feefa3f5 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -320,6 +320,17 @@ Line OptionsGroup::create_single_option_line(const Option& option) const { return retval; } +void OptionsGroup::clear_fields_except_of(const std::vector left_fields) +{ + auto it = m_fields.begin(); + while (it != m_fields.end()) { + if (std::find(left_fields.begin(), left_fields.end(), it->first) == left_fields.end()) + it = m_fields.erase(it); + else + it++; + } +} + void OptionsGroup::on_set_focus(const std::string& opt_key) { if (m_set_focus != nullptr) diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 73b2c5110f..422a5c2a28 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -160,6 +160,8 @@ public: m_show_modified_btns = show; } + void clear_fields_except_of(const std::vector left_fields); + OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false, column_t extra_clmn = nullptr) : m_parent(_parent), title(title), From 080274c638de48ed771873d1936a8e375dd5196c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 30 May 2019 13:08:05 +0200 Subject: [PATCH 020/627] Added missed files to the CMakeLists.txt --- src/slic3r/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 570e23baa6..13f563fd03 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -81,6 +81,8 @@ set(SLIC3R_GUI_SOURCES GUI/GUI_ObjectManipulation.hpp GUI/GUI_ObjectSettings.cpp GUI/GUI_ObjectSettings.hpp + GUI/GUI_ObjectLayers.cpp + GUI/GUI_ObjectLayers.hpp GUI/LambdaObjectDialog.cpp GUI/LambdaObjectDialog.hpp GUI/Tab.cpp From a516f76f94447e82eed066f022deedf769f07b42 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 30 May 2019 14:41:16 +0200 Subject: [PATCH 021/627] Improved layer sizer + fixed build under OSX and Linux --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 17 ++++---- src/slic3r/GUI/GUI_ObjectLayers.hpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 16 +++++--- src/slic3r/GUI/wxExtensions.cpp | 62 +++++++++++++++++++---------- src/slic3r/GUI/wxExtensions.hpp | 8 +++- 5 files changed, 70 insertions(+), 35 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 80aa6bccac..f472605303 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -24,7 +24,7 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : OG_Settings(parent, true) { m_og->label_width = 1; -// m_og->set_grid_vgap(5); + m_og->set_grid_vgap(5); // Legend for object layers Line line = Line{ "", "" }; @@ -118,13 +118,16 @@ void ObjectLayers::create_layers_list() } } -void ObjectLayers::create_layer() +void ObjectLayers::create_layer(int id) { - for (const auto layer : m_object->layer_height_ranges) - { - m_og->append_line(create_new_layer(layer)); - break; + t_layer_height_ranges::iterator layer_range = m_object->layer_height_ranges.begin(); + + while (id > 0 && layer_range != m_object->layer_height_ranges.end()) { + layer_range++; + id--; } + + m_og->append_line(create_new_layer(*layer_range)); } void ObjectLayers::update_layers_list() @@ -164,7 +167,7 @@ void ObjectLayers::update_layers_list() if (type & itLayerRoot) create_layers_list(); else - create_layer(); + create_layer(objects_ctrl->GetModel()->GetLayerIdByItem(item)); m_parent->Layout(); } diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 13a3da911d..b9e9efb140 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -25,7 +25,7 @@ public: ~ObjectLayers() {} void create_layers_list(); - void create_layer(); + void create_layer(int id); void update_layers_list(); void add_layer() {}; void del_layer() {}; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 00eb3e7de0..934ad34fb0 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1801,15 +1801,21 @@ void ObjectList::layers_editing() wxDataViewItem layers_item = m_objects_model->GetItemByType(obj_item, itLayerRoot); if (!layers_item.IsOk()) { - const t_layer_height_range first_range = { 0.0f, 0.2f }; - object(obj_idx)->layer_height_ranges[first_range] = 0.1f; - - const t_layer_height_range second_range = { 0.2f, 0.4f }; - object(obj_idx)->layer_height_ranges[second_range] = 0.05f; + // --->>>--- Just for testing + object(obj_idx)->layer_height_ranges[{ 0.0f, 0.2f }] = 0.1f; + object(obj_idx)->layer_height_ranges[{ 0.2f, 0.4f }] = 0.05f; + object(obj_idx)->layer_height_ranges[{ 0.4f, 0.6f }] = 0.2f; + // ---<<<--- Just for testing layers_item = m_objects_model->AddLayersRoot(obj_item); } + for (const auto range : object(obj_idx)->layer_height_ranges) + { + const std::string label = (boost::format("(%.2f-%.2f)") % range.first.first % range.first.second).str(); + m_objects_model->AddLayersChild(layers_item, label); + } + select_item(layers_item); } diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 7849f66dd1..989a97676e 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -453,24 +453,39 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent m_bmp = create_scaled_bitmap(nullptr, "table.png"); // FIXME: pass window ptr m_name = _(L("Layers")); } - else if (type == itLayer) - { - m_idx = parent->GetChildCount(); - m_name = wxString::Format(_(L("Layer %d")), m_idx + 1); - m_bmp = create_scaled_bitmap(nullptr, "row.png"); // FIXME: pass window ptr - - set_action_icon(); - } #ifdef __WXGTK__ // it's necessary on GTK because of control have to know if this item will be container // in another case you couldn't to add subitem for this item // it will be produce "segmentation fault" - if (type & (itInstanceRoot | itLayerRoot | itLayer)) + if (type & (itInstanceRoot | itLayerRoot)) m_container = true; #endif //__WXGTK__ } +ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const wxString& label_range, + const wxString& extruder, + const int idx /*= -1 */) : + m_parent(parent), + m_type(itLayer), + m_idx(idx), + m_extruder(extruder) +{ + m_idx = parent->GetChildCount(); + m_name = wxString::Format(_(L("Layer %s")), label_range); + m_bmp = create_scaled_bitmap(nullptr, "row.png"); // FIXME: pass window ptr + +#ifdef __WXGTK__ + // it's necessary on GTK because of control have to know if this item will be container + // in another case you couldn't to add subitem for this item + // it will be produce "segmentation fault" + m_container = true; +#endif //__WXGTK__ + + set_action_icon(); +} + void ObjectDataViewModelNode::set_action_icon() { m_action_icon_name = m_type & itObject ? "advanced_plus" : @@ -671,7 +686,7 @@ static bool append_root_node(ObjectDataViewModelNode *parent_node, (root_type&itLayerRoot) && get_root_idx(parent_node, itInstanceRoot)<0) parent_node->Append(*root_node); else if (root_type&itLayerRoot) - parent_node->Insert(*root_node, unsigned int(get_root_idx(parent_node, itInstanceRoot))); + parent_node->Insert(*root_node, static_cast(get_root_idx(parent_node, itInstanceRoot))); return true; } @@ -723,38 +738,38 @@ wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_i if (appended) ItemAdded(parent_item, layer_root_item);// notify control - AddLayersChild(layer_root_item); - - return wxDataViewItem((void*)layer_root_item); + return layer_root_item; } -wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item) +wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item, const std::string& label_range) { ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); if (!parent_node) return wxDataViewItem(0); // get LayerRoot node ObjectDataViewModelNode *layer_root_node; + wxDataViewItem layer_root_item; - if (parent_node->GetType() & itLayerRoot) + if (parent_node->GetType() & itLayerRoot) { layer_root_node = parent_node; + layer_root_item = parent_item; + } else { const int root_idx = get_root_idx(parent_node, itLayerRoot); if (root_idx < 0) return wxDataViewItem(0); layer_root_node = parent_node->GetNthChild(root_idx); + layer_root_item = wxDataViewItem((void*)layer_root_node); } - const wxDataViewItem layer_root_item((void*)layer_root_node); - // Add layer node - ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, itLayer); + ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, label_range); layer_root_node->Append(layer_node); // notify control - const wxDataViewItem instance_item((void*)layer_node); - ItemAdded(layer_root_item, instance_item); + const wxDataViewItem layer_item((void*)layer_node); + ItemAdded(layer_root_item, layer_item); - return wxDataViewItem((void*)layer_node); + return layer_item; } wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) @@ -1138,6 +1153,11 @@ int ObjectDataViewModel::GetInstanceIdByItem(const wxDataViewItem& item) const return GetIdByItemAndType(item, itInstance); } +int ObjectDataViewModel::GetLayerIdByItem(const wxDataViewItem& item) const +{ + return GetIdByItemAndType(item, itLayer); +} + void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx) { wxASSERT(item.IsOk()); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 424f7832a1..b3d8ebc18b 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -231,6 +231,11 @@ public: set_action_icon(); } + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const wxString& label_range, + const wxString& extruder = wxEmptyString, + const int idx = -1 ); + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); ~ObjectDataViewModelNode() @@ -391,7 +396,7 @@ public: wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); - wxDataViewItem AddLayersChild(const wxDataViewItem &parent_item); + wxDataViewItem AddLayersChild(const wxDataViewItem &parent_item, const std::string& label_range); wxDataViewItem Delete(const wxDataViewItem &item); wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); void DeleteAll(); @@ -406,6 +411,7 @@ public: int GetObjectIdByItem(const wxDataViewItem& item) const; int GetVolumeIdByItem(const wxDataViewItem& item) const; int GetInstanceIdByItem(const wxDataViewItem& item) const; + int GetLayerIdByItem(const wxDataViewItem& item) const; void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx); int GetRowByItem(const wxDataViewItem& item) const; bool IsEmpty() { return m_objects.empty(); } From e531d224e8b4649ec1a16e58e2d060cc1be271d9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 30 May 2019 16:53:17 +0200 Subject: [PATCH 022/627] Implemented delete_layers_from_object() --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 45 +++++++++++++++++++++++------ src/slic3r/GUI/GUI_ObjectList.hpp | 1 + src/slic3r/GUI/wxExtensions.cpp | 7 +++-- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index f472605303..191a653863 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -46,7 +46,7 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : m_og->append_line(line); - m_bmp_delete = ScalableBitmap(parent, "cross"); + m_bmp_delete = ScalableBitmap(parent, "remove_copies"/*"cross"*/); m_bmp_add = ScalableBitmap(parent, "add_copies"); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 934ad34fb0..8043ee565c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1141,7 +1141,7 @@ wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu) wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu) { return append_menu_item(menu, wxID_ANY, _(L("Edit Layers")), "", - [this](wxCommandEvent&) { layers_editing(); }, "table.png", menu); + [this](wxCommandEvent&) { layers_editing(); }, "layers", menu); } wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_) @@ -1650,7 +1650,7 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) else if (type == itInstanceRoot && obj_idx != -1) del_instances_from_object(obj_idx); else if ((type & itLayerRoot) && obj_idx != -1) - /*del_layers_from_object(obj_idx)*/; + del_layers_from_object(obj_idx); else if (idx == -1) return; else if (!del_subobject_from_object(obj_idx, idx, type)) @@ -1692,6 +1692,13 @@ void ObjectList::del_instances_from_object(const int obj_idx) changed_object(obj_idx); } +void ObjectList::del_layers_from_object(const int obj_idx) +{ + object(obj_idx)->layer_height_ranges.clear(); // ? #ys_FIXME + + changed_object(obj_idx); +} + bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) { if (obj_idx == 1000) @@ -1732,6 +1739,13 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con object->delete_instance(idx); } else if (type == itLayer) { + t_layer_height_ranges::iterator layer_range = object->layer_height_ranges.begin(); + int id = idx; + while (id > 0 && layer_range != object->layer_height_ranges.end()) { + layer_range++; + id--; + } + object->layer_height_ranges.erase(layer_range); } else return false; @@ -1808,12 +1822,12 @@ void ObjectList::layers_editing() // ---<<<--- Just for testing layers_item = m_objects_model->AddLayersRoot(obj_item); - } - for (const auto range : object(obj_idx)->layer_height_ranges) - { - const std::string label = (boost::format("(%.2f-%.2f)") % range.first.first % range.first.second).str(); - m_objects_model->AddLayersChild(layers_item, label); + for (const auto range : object(obj_idx)->layer_height_ranges) + { + const std::string label = (boost::format(" %.2f-%.2f ") % range.first.first % range.first.second).str(); + m_objects_model->AddLayersChild(layers_item, label); + } } select_item(layers_item); @@ -2168,9 +2182,22 @@ void ObjectList::remove() if (m_objects_model->GetParent(item) == wxDataViewItem(0)) delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); else { - if (sels.size() == 1) - select_item(m_objects_model->GetParent(item)); +// if (sels.size() == 1) +// select_item(m_objects_model->GetParent(item)); + wxDataViewItem parent = m_objects_model->GetParent(item); + if (sels.size() == 1) { + if (!(m_objects_model->GetItemType(item) & itLayer)) { + select_item(parent); + parent = wxDataViewItem(0); + } + else if (m_objects_model->GetChildren(parent, wxDataViewItemArray()) == 1) + parent = m_objects_model->GetTopParent(item); + } + del_subobject_item(item); + + if (sels.size() == 1 && parent) + select_item(parent); } } } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 764aac1e89..6963805e99 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -227,6 +227,7 @@ public: void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(); void del_instances_from_object(const int obj_idx); + void del_layers_from_object(const int obj_idx); bool del_subobject_from_object(const int obj_idx, const int idx, const int type); void split(); void layers_editing(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 989a97676e..4511ab8acb 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -450,7 +450,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } else if (type == itLayerRoot) { - m_bmp = create_scaled_bitmap(nullptr, "table.png"); // FIXME: pass window ptr + m_bmp = create_scaled_bitmap(nullptr, "layers"); // FIXME: pass window ptr m_name = _(L("Layers")); } @@ -473,8 +473,9 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent m_extruder(extruder) { m_idx = parent->GetChildCount(); - m_name = wxString::Format(_(L("Layer %s")), label_range); - m_bmp = create_scaled_bitmap(nullptr, "row.png"); // FIXME: pass window ptr +// m_name = wxString::Format(_(L("Layer %s (mm)")), label_range); + m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; + m_bmp = create_scaled_bitmap(nullptr, "layers_white"); // FIXME: pass window ptr #ifdef __WXGTK__ // it's necessary on GTK because of control have to know if this item will be container From 38641ef5784f8607f6be77e3242970684fed3810 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 31 May 2019 10:54:52 +0200 Subject: [PATCH 023/627] ObjectLayers::del_layer_range() -> delete a layers range using "Del" button from ObjectLayers sizer --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 61 ++++++++++++----------------- src/slic3r/GUI/GUI_ObjectLayers.hpp | 2 - src/slic3r/GUI/GUI_ObjectList.cpp | 35 ++++++++++++++++- src/slic3r/GUI/GUI_ObjectList.hpp | 2 + src/slic3r/GUI/wxExtensions.cpp | 25 +++++++++--- src/slic3r/GUI/wxExtensions.hpp | 3 ++ 6 files changed, 84 insertions(+), 44 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 191a653863..5ff650e867 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -16,8 +16,6 @@ namespace Slic3r namespace GUI { -typedef std::map t_layer_height_ranges; - #define field_width 8 ObjectLayers::ObjectLayers(wxWindow* parent) : @@ -76,42 +74,32 @@ static Line create_new_layer(const t_layer_height_ranges::value_type& layer) void ObjectLayers::create_layers_list() { - auto create_btns = [this](wxWindow* parent) { - auto sizer = new wxBoxSizer(wxHORIZONTAL); - - auto del_btn = new ScalableButton(parent, wxID_ANY, m_bmp_delete); - del_btn->SetToolTip(_(L("Remove layer"))); - - sizer->Add(del_btn, 0, wxRIGHT, em_unit(parent)); - - del_btn->Bind(wxEVT_BUTTON, [this](wxEvent &event) { - del_layer(); -// wxTheApp->CallAfter([this]() { -// wxWindowUpdateLocker noUpdates(m_parent); -// update_layers_list(); -// m_parent->Layout(); -// }); - }); - - auto add_btn = new ScalableButton(parent, wxID_ANY, m_bmp_add); - add_btn->SetToolTip(_(L("Add layer"))); - - sizer->Add(add_btn, 0, wxRIGHT, em_unit(parent)); - - add_btn->Bind(wxEVT_BUTTON, [this](wxEvent &event) { - add_layer(); -// wxTheApp->CallAfter([this]() { -// wxWindowUpdateLocker noUpdates(m_parent); -// update_layers_list(); -// m_parent->Layout(); -// }); - }); - - return sizer; - }; - for (const auto layer : m_object->layer_height_ranges) { + auto create_btns = [this, layer](wxWindow* parent) { + auto sizer = new wxBoxSizer(wxHORIZONTAL); + + auto del_btn = new ScalableButton(parent, wxID_ANY, m_bmp_delete); + del_btn->SetToolTip(_(L("Remove layer"))); + + sizer->Add(del_btn, 0, wxRIGHT, em_unit(parent)); + + del_btn->Bind(wxEVT_BUTTON, [this, layer](wxEvent &event) { + wxGetApp().obj_list()->del_layer_range(layer.first); + }); + + auto add_btn = new ScalableButton(parent, wxID_ANY, m_bmp_add); + add_btn->SetToolTip(_(L("Add layer"))); + + sizer->Add(add_btn, 0, wxRIGHT, em_unit(parent)); + + add_btn->Bind(wxEVT_BUTTON, [this, layer](wxEvent &event) { + wxGetApp().obj_list()->add_layer_range(layer.first); + }); + + return sizer; + }; + Line line = create_new_layer(layer); line.append_widget(create_btns); m_og->append_line(line); @@ -122,6 +110,7 @@ void ObjectLayers::create_layer(int id) { t_layer_height_ranges::iterator layer_range = m_object->layer_height_ranges.begin(); + // May be not a best solution #ys_FIXME while (id > 0 && layer_range != m_object->layer_height_ranges.end()) { layer_range++; id--; diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index b9e9efb140..0b209d523a 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -27,8 +27,6 @@ public: void create_layers_list(); void create_layer(int id); void update_layers_list(); - void add_layer() {}; - void del_layer() {}; void UpdateAndShow(const bool show) override; void msw_rescale(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 8043ee565c..cb815fc721 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1812,7 +1812,7 @@ void ObjectList::layers_editing() wxDataViewItem obj_item = m_objects_model->GetTopParent(item); - wxDataViewItem layers_item = m_objects_model->GetItemByType(obj_item, itLayerRoot); + wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(obj_item); if (!layers_item.IsOk()) { // --->>>--- Just for testing @@ -2202,6 +2202,39 @@ void ObjectList::remove() } } +void ObjectList::del_layer_range(const std::pair& range) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + + t_layer_height_ranges& ranges = object(obj_idx)->layer_height_ranges; + + wxDataViewItem selectable_item = GetSelection(); + int layer_idx = 0; + + if (ranges.size() == 1) + selectable_item = m_objects_model->GetParent(selectable_item); + else { + // May be not a best solution #ys_FIXME + t_layer_height_ranges::iterator layer_selected = ranges.find(range); + t_layer_height_ranges::iterator it = ranges.begin(); + while (it != layer_selected) { + it++; + layer_idx++; + } + } + + wxDataViewItem layer_item = m_objects_model->GetItemByLayerId(obj_idx, layer_idx); + del_subobject_item(layer_item); + + select_item(selectable_item); +} + +void ObjectList::add_layer_range(const std::pair& range) +{ + +} + void ObjectList::init_objects() { m_objects = wxGetApp().model_objects(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 6963805e99..3883691375 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -270,6 +270,8 @@ public: // Remove objects/sub-object from the list void remove(); + void del_layer_range(const std::pair& range); + void add_layer_range(const std::pair& range); void init_objects(); bool multiple_selection() const ; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 4511ab8acb..dbe19dff5b 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1098,25 +1098,35 @@ wxDataViewItem ObjectDataViewModel::GetItemByVolumeId(int obj_idx, int volume_id return wxDataViewItem(0); } -wxDataViewItem ObjectDataViewModel::GetItemByInstanceId(int obj_idx, int inst_idx) +wxDataViewItem ObjectDataViewModel::GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type) { if (obj_idx >= m_objects.size() || obj_idx < 0) { printf("Error! Out of objects range.\n"); return wxDataViewItem(0); } - auto instances_item = GetInstanceRootItem(wxDataViewItem(m_objects[obj_idx])); - if (!instances_item) + auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), parent_type); + if (!item) return wxDataViewItem(0); - auto parent = (ObjectDataViewModelNode*)instances_item.GetID();; + auto parent = (ObjectDataViewModelNode*)item.GetID();; for (size_t i = 0; i < parent->GetChildCount(); i++) - if (parent->GetNthChild(i)->m_idx == inst_idx) + if (parent->GetNthChild(i)->m_idx == sub_obj_idx) return wxDataViewItem(parent->GetNthChild(i)); return wxDataViewItem(0); } +wxDataViewItem ObjectDataViewModel::GetItemByInstanceId(int obj_idx, int inst_idx) +{ + return GetItemById(obj_idx, inst_idx, itInstanceRoot); +} + +wxDataViewItem ObjectDataViewModel::GetItemByLayerId(int obj_idx, int layer_idx) +{ + return GetItemById(obj_idx, layer_idx, itLayerRoot); +} + int ObjectDataViewModel::GetIdByItem(const wxDataViewItem& item) const { wxASSERT(item.IsOk()); @@ -1447,6 +1457,11 @@ wxDataViewItem ObjectDataViewModel::GetInstanceRootItem(const wxDataViewItem &it return GetItemByType(item, itInstanceRoot); } +wxDataViewItem ObjectDataViewModel::GetLayerRootItem(const wxDataViewItem &item) const +{ + return GetItemByType(item, itLayerRoot); +} + bool ObjectDataViewModel::IsSettingsItem(const wxDataViewItem &item) const { if (!item.IsOk()) diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index b3d8ebc18b..23150a9152 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -404,8 +404,10 @@ public: void DeleteVolumeChildren(wxDataViewItem& parent); void DeleteSettings(const wxDataViewItem& parent); wxDataViewItem GetItemById(int obj_idx); + wxDataViewItem GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type); wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); + wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx); int GetIdByItem(const wxDataViewItem& item) const; int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const; int GetObjectIdByItem(const wxDataViewItem& item) const; @@ -460,6 +462,7 @@ public: ItemType type) const; wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const; wxDataViewItem GetInstanceRootItem(const wxDataViewItem &item) const; + wxDataViewItem GetLayerRootItem(const wxDataViewItem &item) const; bool IsSettingsItem(const wxDataViewItem &item) const; void UpdateSettingsDigest( const wxDataViewItem &item, const std::vector& categories); From f0baefb6ff92cdc05575b62d562e0453909a4348 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 31 May 2019 15:25:02 +0200 Subject: [PATCH 024/627] Asynchronous texture compression on CPU --- src/slic3r/GUI/3DBed.cpp | 4 + src/slic3r/GUI/GLTexture.cpp | 164 +++++- src/slic3r/GUI/GLTexture.hpp | 46 ++ src/stb_dxt/stb_dxt.h | 1049 ++++++++++++++++++++++++++++++++++ 4 files changed, 1256 insertions(+), 7 deletions(-) create mode 100644 src/stb_dxt/stb_dxt.h diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 91275f4e62..e6589e5481 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -540,6 +540,10 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const } #endif // !ENABLE_COMPRESSED_TEXTURES } +#if ENABLE_COMPRESSED_TEXTURES + else if (m_texture.unsent_compressed_data_available()) + m_texture.send_compressed_data_to_gpu(); +#endif // ENABLE_COMPRESSED_TEXTURES if (!bottom) { diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 9c1e1f22ef..cc6bdc715a 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -12,6 +12,12 @@ #include #include +#if ENABLE_COMPRESSED_TEXTURES +#include + +#define STB_DXT_IMPLEMENTATION +#include "stb_dxt/stb_dxt.h" +#endif // ENABLE_COMPRESSED_TEXTURES #include "nanosvg/nanosvg.h" #include "nanosvg/nanosvgrast.h" @@ -21,6 +27,86 @@ namespace Slic3r { namespace GUI { +#if ENABLE_COMPRESSED_TEXTURES +void GLTexture::Compressor::reset() +{ + // force compression completion, if any + m_abort_compressing = true; + // wait for compression completion, if any + while (m_is_compressing) {} + + m_levels.clear(); +} + +void GLTexture::Compressor::add_level(unsigned int w, unsigned int h, const std::vector& data) +{ + m_levels.emplace_back(w, h, data); +} + +void GLTexture::Compressor::start_compressing() +{ + m_is_compressing = true; + m_abort_compressing = false; + std::thread t(&GLTexture::Compressor::compress, this); + t.detach(); +} + +bool GLTexture::Compressor::unsent_compressed_data_available() const +{ + for (const Level& level : m_levels) + { + if (!level.sent_to_gpu && (level.compressed_data.size() > 0)) + return true; + } + + return false; +} + +void GLTexture::Compressor::send_compressed_data_to_gpu() +{ + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.m_id)); + for (int i = 0; i < (int)m_levels.size(); ++i) + { + Level& level = m_levels[i]; + if (!level.sent_to_gpu && (level.compressed_data.size() > 0)) + { + glsafe(::glCompressedTexSubImage2D(GL_TEXTURE_2D, (GLint)i, 0, 0, (GLsizei)level.w, (GLsizei)level.h, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)level.compressed_data.size(), (const GLvoid*)level.compressed_data.data())); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (i > 0) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR)); + level.sent_to_gpu = true; + // we are done with the compressed data, we can discard it + level.compressed_data.clear(); + } + } + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); +} + +void GLTexture::Compressor::compress() +{ + // reference: https://github.com/Cyan4973/RygsDXTc + + for (Level& level : m_levels) + { + if (m_abort_compressing) + break; + + // stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4, + // crashes if doing so, so we start with twice the required size + level.compressed_data = std::vector(level.w * level.h * 2, 0); + int compressed_size = 0; + rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size); + level.compressed_data.resize(compressed_size); + + // we are done with the source data, we can discard it + level.src_data.clear(); + } + + m_is_compressing = false; + m_abort_compressing = false; +} +#endif // ENABLE_COMPRESSED_TEXTURES + GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } }; GLTexture::GLTexture() @@ -28,6 +114,9 @@ GLTexture::GLTexture() , m_width(0) , m_height(0) , m_source("") +#if ENABLE_COMPRESSED_TEXTURES + , m_compressor(*this) +#endif // ENABLE_COMPRESSED_TEXTURES { } @@ -249,8 +338,23 @@ void GLTexture::reset() m_width = 0; m_height = 0; m_source = ""; +#if ENABLE_COMPRESSED_TEXTURES + m_compressor.reset(); +#endif // ENABLE_COMPRESSED_TEXTURES } +#if ENABLE_COMPRESSED_TEXTURES +bool GLTexture::unsent_compressed_data_available() const +{ + return m_compressor.unsent_compressed_data_available(); +} + +void GLTexture::send_compressed_data_to_gpu() +{ + m_compressor.send_compressed_data_to_gpu(); +} +#endif // ENABLE_COMPRESSED_TEXTURES + void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top) { render_sub_texture(tex_id, left, right, bottom, top, FullTextureUVs); @@ -416,6 +520,8 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px) #endif // ENABLE_COMPRESSED_TEXTURES { + bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc; + NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f); if (image == nullptr) { @@ -428,6 +534,22 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns m_width = (int)(scale * image->width); m_height = (int)(scale * image->height); + +#if ENABLE_COMPRESSED_TEXTURES + if (compression_enabled) + { + // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 + int width_rem = m_width % 4; + int height_rem = m_height % 4; + + if (width_rem != 0) + m_width += (4 - width_rem); + + if (height_rem != 0) + m_height += (4 - height_rem); + } +#endif // ENABLE_COMPRESSED_TEXTURES + int n_pixels = m_width * m_height; if (n_pixels <= 0) @@ -462,8 +584,13 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); } - if (compress && GLEW_EXT_texture_compression_s3tc) - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + if (compression_enabled) + { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); + } else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); #else @@ -475,7 +602,8 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns int lod_w = m_width; int lod_h = m_height; GLint level = 0; - while ((lod_w > 1) || (lod_h > 1)) + // we do not need to generate all levels down to 1x1 + while ((lod_w > 64) || (lod_h > 64)) { ++level; @@ -483,10 +611,19 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns lod_h = std::max(lod_h / 2, 1); scale /= 2.0f; +#if ENABLE_COMPRESSED_TEXTURES + data.resize(lod_w * lod_h * 4); +#endif // ENABLE_COMPRESSED_TEXTURES + nsvgRasterize(rast, image, 0, 0, scale, data.data(), lod_w, lod_h, lod_w * 4); #if ENABLE_COMPRESSED_TEXTURES - if (compress && GLEW_EXT_texture_compression_s3tc) - glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + if (compression_enabled) + { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data); + } else glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); #else @@ -494,8 +631,15 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns #endif // ENABLE_COMPRESSED_TEXTURES } - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); +#if ENABLE_COMPRESSED_TEXTURES + if (!compression_enabled) + { +#endif // ENABLE_COMPRESSED_TEXTURES + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); +#if ENABLE_COMPRESSED_TEXTURES + } +#endif // ENABLE_COMPRESSED_TEXTURES } else { @@ -508,6 +652,12 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns m_source = filename; +#if ENABLE_COMPRESSED_TEXTURES + if (compression_enabled) + // start asynchronous compression + m_compressor.start_compressing(); +#endif // ENABLE_COMPRESSED_TEXTURES + nsvgDeleteRasterizer(rast); nsvgDelete(image); diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index 032d19121f..f5cf13a455 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -11,6 +11,42 @@ namespace GUI { class GLTexture { +#if ENABLE_COMPRESSED_TEXTURES + class Compressor + { + struct Level + { + unsigned int w; + unsigned int h; + std::vector src_data; + std::vector compressed_data; + bool sent_to_gpu; + + Level(unsigned int w, unsigned int h, const std::vector& data) : w(w), h(h), src_data(data), sent_to_gpu(false) {} + }; + + GLTexture& m_texture; + std::vector m_levels; + bool m_is_compressing; + bool m_abort_compressing; + + public: + explicit Compressor(GLTexture& texture) : m_texture(texture), m_is_compressing(false), m_abort_compressing(false) {} + + void reset(); + + void add_level(unsigned int w, unsigned int h, const std::vector& data); + + void start_compressing(); + + bool unsent_compressed_data_available() const; + void send_compressed_data_to_gpu(); + + private: + void compress(); + }; +#endif // ENABLE_COMPRESSED_TEXTURES + public: struct UV { @@ -33,6 +69,9 @@ namespace GUI { int m_width; int m_height; std::string m_source; +#if ENABLE_COMPRESSED_TEXTURES + Compressor m_compressor; +#endif // ENABLE_COMPRESSED_TEXTURES public: GLTexture(); @@ -66,6 +105,11 @@ namespace GUI { const std::string& get_source() const { return m_source; } +#if ENABLE_COMPRESSED_TEXTURES + bool unsent_compressed_data_available() const; + void send_compressed_data_to_gpu(); +#endif // ENABLE_COMPRESSED_TEXTURES + static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs); @@ -80,6 +124,8 @@ namespace GUI { #if ENABLE_COMPRESSED_TEXTURES bool load_from_png(const std::string& filename, bool use_mipmaps, bool compress); bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); + + friend class Compressor; #else bool load_from_png(const std::string& filename, bool use_mipmaps); bool load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); diff --git a/src/stb_dxt/stb_dxt.h b/src/stb_dxt/stb_dxt.h new file mode 100644 index 0000000000..db19110f43 --- /dev/null +++ b/src/stb_dxt/stb_dxt.h @@ -0,0 +1,1049 @@ +// stb_dxt.h - Real-Time DXT1/DXT5 compressor +// Based on original by fabian "ryg" giesen v1.04 +// Custom version, modified by Yann Collet +// +/* + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - RygsDXTc source repository : http://code.google.com/p/rygsdxtc/ + +*/ +// use '#define STB_DXT_IMPLEMENTATION' before including to create the implementation +// +// USAGE: +// call stb_compress_dxt_block() for every block (you must pad) +// source should be a 4x4 block of RGBA data in row-major order; +// A is ignored if you specify alpha=0; you can turn on dithering +// and "high quality" using mode. +// +// version history: +// v1.06 - (cyan) implement Fabian Giesen's comments +// v1.05 - (cyan) speed optimizations +// v1.04 - (ryg) default to no rounding bias for lerped colors (as per S3TC/DX10 spec); +// single color match fix (allow for inexact color interpolation); +// optimal DXT5 index finder; "high quality" mode that runs multiple refinement steps. +// v1.03 - (stb) endianness support +// v1.02 - (stb) fix alpha encoding bug +// v1.01 - (stb) fix bug converting to RGB that messed up quality, thanks ryg & cbloom +// v1.00 - (stb) first release + +#ifndef STB_INCLUDE_STB_DXT_H +#define STB_INCLUDE_STB_DXT_H + + +//******************************************************************* +// Enable custom Optimisations +// Comment this define if you want to revert to ryg's original code +#define NEW_OPTIMISATIONS +//******************************************************************* + +// compression mode (bitflags) +#define STB_DXT_NORMAL 0 +#define STB_DXT_DITHER 1 // use dithering. dubious win. never use for normal maps and the like! +#define STB_DXT_HIGHQUAL 2 // high quality mode, does two refinement steps instead of 1. ~30-40% slower. + +// The original signature has been modified by adding the parameter compressed_size which returns +// the size in bytes of the compressed data contained into dst +void rygCompress(unsigned char *dst, unsigned char *src, int w, int h, int isDxt5, int& compressed_size); + +// TODO remove these, not working properly.. +void rygCompressYCoCg( unsigned char *dst, unsigned char *src, int w, int h ); +void linearize( unsigned char * dst, const unsigned char * src, int n ); + +void stb_compress_dxt_block(unsigned char *dest, const unsigned char *src, int alpha, int mode); +#define STB_COMPRESS_DXT_BLOCK + +#ifdef STB_DXT_IMPLEMENTATION + +// configuration options for DXT encoder. set them in the project/makefile or just define +// them at the top. + +// STB_DXT_USE_ROUNDING_BIAS +// use a rounding bias during color interpolation. this is closer to what "ideal" +// interpolation would do but doesn't match the S3TC/DX10 spec. old versions (pre-1.03) +// implicitly had this turned on. +// +// in case you're targeting a specific type of hardware (e.g. console programmers): +// NVidia and Intel GPUs (as of 2010) as well as DX9 ref use DXT decoders that are closer +// to STB_DXT_USE_ROUNDING_BIAS. AMD/ATI, S3 and DX10 ref are closer to rounding with no bias. +// you also see "(a*5 + b*3) / 8" on some old GPU designs. +// #define STB_DXT_USE_ROUNDING_BIAS + +#include +#include +#include +#include // memset +#include +#include +#include + + +static unsigned char stb__Expand5[32]; +static unsigned char stb__Expand6[64]; +static unsigned char stb__OMatch5[256][2]; +static unsigned char stb__OMatch6[256][2]; +static unsigned char stb__QuantRBTab[256+16]; +static unsigned char stb__QuantGTab[256+16]; + +static int stb__Mul8Bit(int a, int b) +{ + int t = a*b + 128; + return (t + (t >> 8)) >> 8; +} + +static void stb__From16Bit(unsigned char *out, unsigned short v) +{ + int rv = (v & 0xf800) >> 11; + int gv = (v & 0x07e0) >> 5; + int bv = (v & 0x001f) >> 0; + + out[0] = stb__Expand5[rv]; + out[1] = stb__Expand6[gv]; + out[2] = stb__Expand5[bv]; + out[3] = 0; +} + +static unsigned short stb__As16Bit(int r, int g, int b) +{ + return (stb__Mul8Bit(r,31) << 11) + (stb__Mul8Bit(g,63) << 5) + stb__Mul8Bit(b,31); +} + +// linear interpolation at 1/3 point between a and b, using desired rounding type +static int stb__Lerp13(int a, int b) +{ +#ifdef STB_DXT_USE_ROUNDING_BIAS + // with rounding bias + return a + stb__Mul8Bit(b-a, 0x55); +#else + // without rounding bias + // replace "/ 3" by "* 0xaaab) >> 17" if your compiler sucks or you really need every ounce of speed. + return (2*a + b) / 3; +#endif +} + +// lerp RGB color +static void stb__Lerp13RGB(unsigned char *out, unsigned char *p1, unsigned char *p2) +{ + out[0] = stb__Lerp13(p1[0], p2[0]); + out[1] = stb__Lerp13(p1[1], p2[1]); + out[2] = stb__Lerp13(p1[2], p2[2]); +} + +/****************************************************************************/ + +// compute table to reproduce constant colors as accurately as possible +static void stb__PrepareOptTable(unsigned char *Table,const unsigned char *expand,int size) +{ + int i,mn,mx; + for (i=0;i<256;i++) { + int bestErr = 256; + for (mn=0;mn> 4)]; + ep1[0] = bp[ 0] - dp[ 0]; + dp[ 4] = quant[bp[ 4] + ((7*ep1[0] + 3*ep2[2] + 5*ep2[1] + ep2[0]) >> 4)]; + ep1[1] = bp[ 4] - dp[ 4]; + dp[ 8] = quant[bp[ 8] + ((7*ep1[1] + 3*ep2[3] + 5*ep2[2] + ep2[1]) >> 4)]; + ep1[2] = bp[ 8] - dp[ 8]; + dp[12] = quant[bp[12] + ((7*ep1[2] + 5*ep2[3] + ep2[2]) >> 4)]; + ep1[3] = bp[12] - dp[12]; + bp += 16; + dp += 16; + et = ep1, ep1 = ep2, ep2 = et; // swap + } + } +} + +// The color matching function +static unsigned int stb__MatchColorsBlock(unsigned char *block, unsigned char *color,int dither) +{ + unsigned int mask = 0; + int dirr = color[0*4+0] - color[1*4+0]; + int dirg = color[0*4+1] - color[1*4+1]; + int dirb = color[0*4+2] - color[1*4+2]; + int dots[16]; + int stops[4]; + int i; + int c0Point, halfPoint, c3Point; + + for(i=0;i<16;i++) + dots[i] = block[i*4+0]*dirr + block[i*4+1]*dirg + block[i*4+2]*dirb; + + for(i=0;i<4;i++) + stops[i] = color[i*4+0]*dirr + color[i*4+1]*dirg + color[i*4+2]*dirb; + + // think of the colors as arranged on a line; project point onto that line, then choose + // next color out of available ones. we compute the crossover points for "best color in top + // half"/"best in bottom half" and then the same inside that subinterval. + // + // relying on this 1d approximation isn't always optimal in terms of euclidean distance, + // but it's very close and a lot faster. + // http://cbloomrants.blogspot.com/2008/12/12-08-08-dxtc-summary.html + + c0Point = (stops[1] + stops[3]) >> 1; + halfPoint = (stops[3] + stops[2]) >> 1; + c3Point = (stops[2] + stops[0]) >> 1; + + if(!dither) + { + // the version without dithering is straightforward + +#ifdef NEW_OPTIMISATIONS + const int indexMap[8] = { 0 << 30,2 << 30,0 << 30,2 << 30,3 << 30,3 << 30,1 << 30,1 << 30 }; + + for(int i=0;i<16;i++) + { + int dot = dots[i]; + mask >>= 2; + + int bits =( (dot < halfPoint) ? 4 : 0 ) + | ( (dot < c0Point) ? 2 : 0 ) + | ( (dot < c3Point) ? 1 : 0 ); + + mask |= indexMap[bits]; + } + +#else + for (i=15;i>=0;i--) { + int dot = dots[i]; + mask <<= 2; + + if(dot < halfPoint) + mask |= (dot < c0Point) ? 1 : 3; + else + mask |= (dot < c3Point) ? 2 : 0; + } +#endif + + } else { + // with floyd-steinberg dithering + int err[8],*ep1 = err,*ep2 = err+4; + int *dp = dots, y; + + c0Point <<= 4; + halfPoint <<= 4; + c3Point <<= 4; + for(i=0;i<8;i++) + err[i] = 0; + + for(y=0;y<4;y++) + { + int dot,lmask,step; + + dot = (dp[0] << 4) + (3*ep2[1] + 5*ep2[0]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[0] = dp[0] - stops[step]; + lmask = step; + + dot = (dp[1] << 4) + (7*ep1[0] + 3*ep2[2] + 5*ep2[1] + ep2[0]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[1] = dp[1] - stops[step]; + lmask |= step<<2; + + dot = (dp[2] << 4) + (7*ep1[1] + 3*ep2[3] + 5*ep2[2] + ep2[1]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[2] = dp[2] - stops[step]; + lmask |= step<<4; + + dot = (dp[3] << 4) + (7*ep1[2] + 5*ep2[3] + ep2[2]); + if(dot < halfPoint) + step = (dot < c0Point) ? 1 : 3; + else + step = (dot < c3Point) ? 2 : 0; + ep1[3] = dp[3] - stops[step]; + lmask |= step<<6; + + dp += 4; + mask |= lmask << (y*8); + { int *et = ep1; ep1 = ep2; ep2 = et; } // swap + } + } + + return mask; +} + +// The color optimization function. (Clever code, part 1) +static void stb__OptimizeColorsBlock(unsigned char *block, unsigned short *pmax16, unsigned short *pmin16) +{ + unsigned char *minp, *maxp; + double magn; + int v_r,v_g,v_b; + static const int nIterPower = 4; + float covf[6],vfr,vfg,vfb; + + // determine color distribution + int cov[6]; + int mu[3],min[3],max[3]; + int ch,i,iter; + + for(ch=0;ch<3;ch++) + { + const unsigned char *bp = ((const unsigned char *) block) + ch; + int muv,minv,maxv; + +#ifdef NEW_OPTIMISATIONS +# define MIN(a,b) (int)a + ( ((int)b-a) & ( ((int)b-a) >> 31 ) ) +# define MAX(a,b) (int)a + ( ((int)b-a) & ( ((int)a-b) >> 31 ) ) +# define RANGE(a,b,n) int min##n = MIN(a,b); int max##n = a+b - min##n; muv += a+b; +# define MINMAX(a,b,n) int min##n = MIN(min##a, min##b); int max##n = MAX(max##a, max##b); + + muv = 0; + RANGE(bp[0], bp[4], 1); + RANGE(bp[8], bp[12], 2); + RANGE(bp[16], bp[20], 3); + RANGE(bp[24], bp[28], 4); + RANGE(bp[32], bp[36], 5); + RANGE(bp[40], bp[44], 6); + RANGE(bp[48], bp[52], 7); + RANGE(bp[56], bp[60], 8); + + MINMAX(1,2,9); + MINMAX(3,4,10); + MINMAX(5,6,11); + MINMAX(7,8,12); + + MINMAX(9,10,13); + MINMAX(11,12,14); + + minv = MIN(min13,min14); + maxv = MAX(max13,max14); + +#else + muv = minv = maxv = bp[0]; + for(i=4;i<64;i+=4) + { + muv += bp[i]; + if (bp[i] < minv) minv = bp[i]; + else if (bp[i] > maxv) maxv = bp[i]; + } +#endif + + mu[ch] = (muv + 8) >> 4; + min[ch] = minv; + max[ch] = maxv; + } + + // determine covariance matrix + for (i=0;i<6;i++) + cov[i] = 0; + + for (i=0;i<16;i++) + { + int r = block[i*4+0] - mu[0]; + int g = block[i*4+1] - mu[1]; + int b = block[i*4+2] - mu[2]; + + cov[0] += r*r; + cov[1] += r*g; + cov[2] += r*b; + cov[3] += g*g; + cov[4] += g*b; + cov[5] += b*b; + } + + // convert covariance matrix to float, find principal axis via power iter + for(i=0;i<6;i++) + covf[i] = cov[i] / 255.0f; + + vfr = (float) (max[0] - min[0]); + vfg = (float) (max[1] - min[1]); + vfb = (float) (max[2] - min[2]); + + for(iter=0;iter magn) magn = fabs(vfg); + if (fabs(vfb) > magn) magn = fabs(vfb); + + if(magn < 4.0f) + { // too small, default to luminance + v_r = 299; // JPEG YCbCr luma coefs, scaled by 1000. + v_g = 587; + v_b = 114; + } else { + magn = 512.0 / magn; + v_r = (int) (vfr * magn); + v_g = (int) (vfg * magn); + v_b = (int) (vfb * magn); + } + + +#ifdef NEW_OPTIMISATIONS + // Pick colors at extreme points + int mind, maxd; + mind = maxd = block[0]*v_r + block[1]*v_g + block[2]*v_b; + minp = maxp = block; + for(i=1;i<16;i++) + { + int dot = block[i*4+0]*v_r + block[i*4+1]*v_g + block[i*4+2]*v_b; + + if (dot < mind) { + mind = dot; + minp = block+i*4; + continue; + } + + if (dot > maxd) { + maxd = dot; + maxp = block+i*4; + } + } +#else + int mind = 0x7fffffff,maxd = -0x7fffffff; + // Pick colors at extreme points + for(i=0;i<16;i++) + { + int dot = block[i*4+0]*v_r + block[i*4+1]*v_g + block[i*4+2]*v_b; + + if (dot < mind) { + mind = dot; + minp = block+i*4; + } + + if (dot > maxd) { + maxd = dot; + maxp = block+i*4; + } + } +#endif + + *pmax16 = stb__As16Bit(maxp[0],maxp[1],maxp[2]); + *pmin16 = stb__As16Bit(minp[0],minp[1],minp[2]); +} + +inline static int stb__sclamp(float y, int p0, int p1) +{ + int x = (int) y; + +#ifdef NEW_OPTIMISATIONS + x = x>p1 ? p1 : x; + return x p1) return p1; + return x; +#endif +} + +// The refinement function. (Clever code, part 2) +// Tries to optimize colors to suit block contents better. +// (By solving a least squares system via normal equations+Cramer's rule) +static int stb__RefineBlock(unsigned char *block, unsigned short *pmax16, unsigned short *pmin16, unsigned int mask) +{ + static const int w1Tab[4] = { 3,0,2,1 }; + static const int prods[4] = { 0x090000,0x000900,0x040102,0x010402 }; + // ^some magic to save a lot of multiplies in the accumulating loop... + // (precomputed products of weights for least squares system, accumulated inside one 32-bit register) + + float frb,fg; + unsigned short oldMin, oldMax, min16, max16; + int i, akku = 0, xx,xy,yy; + int At1_r,At1_g,At1_b; + int At2_r,At2_g,At2_b; + unsigned int cm = mask; + + oldMin = *pmin16; + oldMax = *pmax16; + + if((mask ^ (mask<<2)) < 4) // all pixels have the same index? + { + // yes, linear system would be singular; solve using optimal + // single-color match on average color + int r = 8, g = 8, b = 8; + for (i=0;i<16;++i) { + r += block[i*4+0]; + g += block[i*4+1]; + b += block[i*4+2]; + } + + r >>= 4; g >>= 4; b >>= 4; + + max16 = (stb__OMatch5[r][0]<<11) | (stb__OMatch6[g][0]<<5) | stb__OMatch5[b][0]; + min16 = (stb__OMatch5[r][1]<<11) | (stb__OMatch6[g][1]<<5) | stb__OMatch5[b][1]; + } else { + At1_r = At1_g = At1_b = 0; + At2_r = At2_g = At2_b = 0; + for (i=0;i<16;++i,cm>>=2) + { + int step = cm&3; + int w1 = w1Tab[step]; + int r = block[i*4+0]; + int g = block[i*4+1]; + int b = block[i*4+2]; + + akku += prods[step]; + At1_r += w1*r; + At1_g += w1*g; + At1_b += w1*b; + At2_r += r; + At2_g += g; + At2_b += b; + } + + At2_r = 3*At2_r - At1_r; + At2_g = 3*At2_g - At1_g; + At2_b = 3*At2_b - At1_b; + + // extract solutions and decide solvability + xx = akku >> 16; + yy = (akku >> 8) & 0xff; + xy = (akku >> 0) & 0xff; + + frb = 3.0f * 31.0f / 255.0f / (xx*yy - xy*xy); + fg = frb * 63.0f / 31.0f; + + // solve. + max16 = stb__sclamp((At1_r*yy - At2_r*xy)*frb+0.5f,0,31) << 11; + max16 |= stb__sclamp((At1_g*yy - At2_g*xy)*fg +0.5f,0,63) << 5; + max16 |= stb__sclamp((At1_b*yy - At2_b*xy)*frb+0.5f,0,31) << 0; + + min16 = stb__sclamp((At2_r*xx - At1_r*xy)*frb+0.5f,0,31) << 11; + min16 |= stb__sclamp((At2_g*xx - At1_g*xy)*fg +0.5f,0,63) << 5; + min16 |= stb__sclamp((At2_b*xx - At1_b*xy)*frb+0.5f,0,31) << 0; + } + + *pmin16 = min16; + *pmax16 = max16; + return oldMin != min16 || oldMax != max16; +} + +// Color block compression +static void stb__CompressColorBlock(unsigned char *dest, unsigned char *block, int mode) +{ + unsigned int mask; + int i; + int dither; + int refinecount; + unsigned short max16, min16; + unsigned char dblock[16*4],color[4*4]; + + dither = mode & STB_DXT_DITHER; + refinecount = (mode & STB_DXT_HIGHQUAL) ? 2 : 1; + + // check if block is constant + for (i=1;i<16;i++) + if (((unsigned int *) block)[i] != ((unsigned int *) block)[0]) + break; + + if(i == 16) + { // constant color + int r = block[0], g = block[1], b = block[2]; + mask = 0xaaaaaaaa; + max16 = (stb__OMatch5[r][0]<<11) | (stb__OMatch6[g][0]<<5) | stb__OMatch5[b][0]; + min16 = (stb__OMatch5[r][1]<<11) | (stb__OMatch6[g][1]<<5) | stb__OMatch5[b][1]; + } else + { + // first step: compute dithered version for PCA if desired + if(dither) + stb__DitherBlock(dblock,block); + + // second step: pca+map along principal axis + stb__OptimizeColorsBlock(dither ? dblock : block,&max16,&min16); + if (max16 != min16) + { + stb__EvalColors(color,max16,min16); + mask = stb__MatchColorsBlock(block,color,dither); + } else + mask = 0; + + // third step: refine (multiple times if requested) + for (i=0;i> 8); + dest[2] = (unsigned char) (min16); + dest[3] = (unsigned char) (min16 >> 8); + dest[4] = (unsigned char) (mask); + dest[5] = (unsigned char) (mask >> 8); + dest[6] = (unsigned char) (mask >> 16); + dest[7] = (unsigned char) (mask >> 24); +} + +// Alpha block compression (this is easy for a change) +static void stb__CompressAlphaBlock(unsigned char *dest,unsigned char *src,int mode) +{ + int i,dist,bias,dist4,dist2,bits,mask; + + // find min/max color + int mn,mx; + + mn = mx = src[3]; + for (i=1;i<16;i++) + { + if (src[i*4+3] < mn) mn = src[i*4+3]; + else if (src[i*4+3] > mx) mx = src[i*4+3]; + } + + // encode them + ((unsigned char *)dest)[0] = mx; + ((unsigned char *)dest)[1] = mn; + dest += 2; + +#ifdef NEW_OPTIMISATIONS + // mono-alpha shortcut + if (mn==mx) + { + *(unsigned short*)dest = 0; + dest += 2; + *(unsigned int*)dest = 0; + return; + } +#endif + + // determine bias and emit color indices + // given the choice of mx/mn, these indices are optimal: + // http://fgiesen.wordpress.com/2009/12/15/dxt5-alpha-block-index-determination/ + dist = mx-mn; + //printf("mn = %i; mx = %i; dist = %i\n", mn, mx, dist); + dist4 = dist*4; + dist2 = dist*2; + bias = (dist < 8) ? (dist - 1) : (dist/2 + 2); + bias -= mn * 7; + bits = 0, mask=0; + + for (i=0;i<16;i++) + { + int a = src[i*4+3]*7 + bias; + int ind,t; + + // select index. this is a "linear scale" lerp factor between 0 (val=min) and 7 (val=max). + t = (a >= dist4) ? -1 : 0; ind = t & 4; a -= dist4 & t; + t = (a >= dist2) ? -1 : 0; ind += t & 2; a -= dist2 & t; + ind += (a >= dist); + + // turn linear scale into DXT index (0/1 are extremal pts) + ind = -ind & 7; + ind ^= (2 > ind); + + // write index + mask |= ind << bits; + if((bits += 3) >= 8) + { + *dest++ = mask; + mask >>= 8; + bits -= 8; + } + } +} + + +static void stb__InitDXT() +{ + int i; + for(i=0;i<32;i++) + stb__Expand5[i] = (i<<3)|(i>>2); + + for(i=0;i<64;i++) + stb__Expand6[i] = (i<<2)|(i>>4); + + for(i=0;i<256+16;i++) + { + int v = i-8 < 0 ? 0 : i-8 > 255 ? 255 : i-8; + stb__QuantRBTab[i] = stb__Expand5[stb__Mul8Bit(v,31)]; + stb__QuantGTab[i] = stb__Expand6[stb__Mul8Bit(v,63)]; + } + + stb__PrepareOptTable(&stb__OMatch5[0][0],stb__Expand5,32); + stb__PrepareOptTable(&stb__OMatch6[0][0],stb__Expand6,64); +} + + +void stb_compress_dxt_block(unsigned char *dest, const unsigned char *src, int alpha, int mode) +{ + static int init=1; + if (init) + { + stb__InitDXT(); + init=0; + } + + if (alpha) + { + stb__CompressAlphaBlock(dest,(unsigned char*) src,mode); + dest += 8; + } + + stb__CompressColorBlock(dest,(unsigned char*) src,mode); +} + +int imin(int x, int y) { return (x < y) ? x : y; } + + + + + +static void extractBlock(const unsigned char *src, int x, int y, + int w, int h, unsigned char *block) +{ + int i, j; + +#ifdef NEW_OPTIMISATIONS + if ((w-x >=4) && (h-y >=4)) + { + // Full Square shortcut + src += x*4; + src += y*w*4; + for (i=0; i < 4; ++i) + { + *(unsigned int*)block = *(unsigned int*) src; block += 4; src += 4; + *(unsigned int*)block = *(unsigned int*) src; block += 4; src += 4; + *(unsigned int*)block = *(unsigned int*) src; block += 4; src += 4; + *(unsigned int*)block = *(unsigned int*) src; block += 4; + src += (w*4) - 12; + } + return; + } +#endif + + int bw = imin(w - x, 4); + int bh = imin(h - y, 4); + int bx, by; + + const int rem[] = + { + 0, 0, 0, 0, + 0, 1, 0, 1, + 0, 1, 2, 0, + 0, 1, 2, 3 + }; + + for(i = 0; i < 4; ++i) + { + by = rem[(bh - 1) * 4 + i] + y; + for(j = 0; j < 4; ++j) + { + bx = rem[(bw - 1) * 4 + j] + x; + block[(i * 4 * 4) + (j * 4) + 0] = + src[(by * (w * 4)) + (bx * 4) + 0]; + block[(i * 4 * 4) + (j * 4) + 1] = + src[(by * (w * 4)) + (bx * 4) + 1]; + block[(i * 4 * 4) + (j * 4) + 2] = + src[(by * (w * 4)) + (bx * 4) + 2]; + block[(i * 4 * 4) + (j * 4) + 3] = + src[(by * (w * 4)) + (bx * 4) + 3]; + } + } +} + + // should be a pretty optimized 0-255 clamper +inline static unsigned char clamp255( int n ) +{ + if( n > 255 ) n = 255; + if( n < 0 ) n = 0; + return n; +} + + +void rgbToYCoCgBlock( unsigned char * dst, const unsigned char * src ) +{ + // Calculate Co and Cg extents + int extents = 0; + int n = 0; + int iY, iCo, iCg; //, r, g, b; + int blockCo[16]; + int blockCg[16]; + int i; + + const unsigned char *px = src; + for(i=0;i extents) extents = -iCo; + if( iCo > extents) extents = iCo; + if(-iCg > extents) extents = -iCg; + if( iCg > extents) extents = iCg; + + blockCo[n] = iCo; + blockCg[n++] = iCg; + + px += 4; + } + + // Co = -510..510 + // Cg = -510..510 + float scaleFactor = 1.0f; + if(extents > 127) + scaleFactor = (float)extents * 4.0f / 510.0f; + + // Convert to quantized scalefactor + unsigned char scaleFactorQuantized = (unsigned char)(ceil((scaleFactor - 1.0f) * 31.0f / 3.0f)); + + // Unquantize + scaleFactor = 1.0f + (float)(scaleFactorQuantized / 31.0f) * 3.0f; + + unsigned char bVal = (unsigned char)((scaleFactorQuantized << 3) | (scaleFactorQuantized >> 2)); + + unsigned char *outPx = dst; + + n = 0; + px = src; + /* + for(i=0;i<16;i++) + { + // Calculate components + iY = ( px[0] + (px[1]<<1) + px[2] + 2 ) / 4; + iCo = ((blockCo[n] / scaleFactor) + 128); + iCg = ((blockCg[n] / scaleFactor) + 128); + + if(iCo < 0) iCo = 0; else if(iCo > 255) iCo = 255; + if(iCg < 0) iCg = 0; else if(iCg > 255) iCg = 255; + if(iY < 0) iY = 0; else if(iY > 255) iY = 255; + + px += 4; + + outPx[0] = (unsigned char)iCo; + outPx[1] = (unsigned char)iCg; + outPx[2] = bVal; + outPx[3] = (unsigned char)iY; + + outPx += 4; + }*/ + for(i=0;i<16;i++) + { + // Calculate components + int r = px[0]; + int g = (px[1] + 1) >> 1; + int b = px[2]; + int tmp = (2 + r + b) >> 2; + + // Co + iCo = clamp255( 128 + ((r - b + 1) >> 1) ); + // Y + iY = clamp255( g + tmp ); + // Cg + iCg = clamp255( 128 + g - tmp ); + + px += 4; + + outPx[0] = (unsigned char)iCo; + outPx[1] = (unsigned char)iCg; + outPx[2] = bVal; + outPx[3] = (unsigned char)iY; + + outPx += 4; + } + +} + + +void rygCompress(unsigned char *dst, unsigned char *src, int w, int h, int isDxt5, int& compressed_size) +{ + + unsigned char block[64]; + int x, y; + + unsigned char* initial_dst = dst; + + for (y = 0; y < h; y += 4) + { + for(x = 0; x < w; x += 4) + { + extractBlock(src, x, y, w, h, block); + stb_compress_dxt_block(dst, block, isDxt5, STB_DXT_NORMAL); + dst += isDxt5 ? 16 : 8; + } + } + + compressed_size = dst - initial_dst; +} + +void rygCompressYCoCg( unsigned char *dst, unsigned char *src, int w, int h ) +{ + unsigned char block[64]; + unsigned char ycocgblock[64]; + int x, y; + + for(y = 0; y < h; y += 4) + { + for(x = 0; x < w; x += 4) + { + extractBlock(src, x, y, w, h, block); + rgbToYCoCgBlock(ycocgblock,block); + stb_compress_dxt_block(dst, ycocgblock, 1, 10); + dst += 16; + } + } + +} + +static void stbgl__compress(unsigned char *p, unsigned char *rgba, int w, int h, int isDxt5) +{ + int i,j,y,y2; + int alpha = isDxt5; + + for (j=0; j < w; j += 4) { + int x=4; + for (i=0; i < h; i += 4) { + unsigned char block[16*4]; + if (i+3 >= w) x = w-i; + for (y=0; y < 4; ++y) { + if (j+y >= h) break; + memcpy(block+y*16, rgba + w*4*(j+y) + i*4, x*4); + } + if (x < 4) { + switch (x) { + case 0: assert(0); + case 1: + for (y2=0; y2 < y; ++y2) { + memcpy(block+y2*16+1*4, block+y2*16+0*4, 4); + memcpy(block+y2*16+2*4, block+y2*16+0*4, 8); + } + break; + case 2: + for (y2=0; y2 < y; ++y2) + memcpy(block+y2*16+2*4, block+y2*16+0*4, 8); + break; + case 3: + for (y2=0; y2 < y; ++y2) + memcpy(block+y2*16+3*4, block+y2*16+1*4, 4); + break; + } + } + y2 = 0; + for(; y<4; ++y,++y2) + memcpy(block+y*16, block+y2*16, 4*4); + stb_compress_dxt_block(p, block, alpha, 10); + p += alpha ? 16 : 8; + } + } + // assert(p <= end); +} + +static inline unsigned char linearize(unsigned char inByte) +{ + float srgbVal = ((float)inByte) / 255.0f; + float linearVal; + + if(srgbVal < 0.04045) + linearVal = srgbVal / 12.92f; + else + linearVal = pow( (srgbVal + 0.055f) / 1.055f, 2.4f); + + return (unsigned char)(floor(sqrt(linearVal)* 255.0 + 0.5)); +} + +void linearize( unsigned char * dst, const unsigned char * src, int n ) +{ + n*=4; + for( int i = 0; i < n; i++ ) + dst[i] = linearize(src[i]); +} + + + +#endif // STB_DXT_IMPLEMENTATION + +#endif // STB_INCLUDE_STB_DXT_H From 5f4b7a5292dce6c5e531de06d850006b81f3214c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 31 May 2019 15:29:09 +0200 Subject: [PATCH 025/627] ObjectLayers::add_layer_range() -> add a layers range using "Add" button from ObjectLayers sizer --- src/slic3r/GUI/GUI_ObjectList.cpp | 101 ++++++++++++++++++++++++++---- src/slic3r/GUI/GUI_ObjectList.hpp | 4 ++ src/slic3r/GUI/wxExtensions.cpp | 25 ++++++-- src/slic3r/GUI/wxExtensions.hpp | 8 ++- 4 files changed, 116 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index cb815fc721..88d7df6cc2 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1810,27 +1810,26 @@ void ObjectList::layers_editing() if (!item || obj_idx < 0) return; - wxDataViewItem obj_item = m_objects_model->GetTopParent(item); - + const wxDataViewItem obj_item = m_objects_model->GetTopParent(item); wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(obj_item); + + // if it doesn't exist now if (!layers_item.IsOk()) { - // --->>>--- Just for testing - object(obj_idx)->layer_height_ranges[{ 0.0f, 0.2f }] = 0.1f; - object(obj_idx)->layer_height_ranges[{ 0.2f, 0.4f }] = 0.05f; - object(obj_idx)->layer_height_ranges[{ 0.4f, 0.6f }] = 0.2f; - // ---<<<--- Just for testing - + // create LayerRoor item layers_item = m_objects_model->AddLayersRoot(obj_item); + + if (object(obj_idx)->layer_height_ranges.empty()) + object(obj_idx)->layer_height_ranges[{ 0.0f, 0.2f }] = 0.1f;// some default value + // and create Layer item(s) according to the layer_height_ranges for (const auto range : object(obj_idx)->layer_height_ranges) - { - const std::string label = (boost::format(" %.2f-%.2f ") % range.first.first % range.first.second).str(); - m_objects_model->AddLayersChild(layers_item, label); - } + add_layer_item(range.first, layers_item); } + // select LayerRoor item and expand select_item(layers_item); + Expand(layers_item); } bool ObjectList::get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume) @@ -2230,7 +2229,83 @@ void ObjectList::del_layer_range(const std::pair& range) select_item(selectable_item); } -void ObjectList::add_layer_range(const std::pair& range) +void ObjectList::add_layer_range(const t_layer_height_range& range) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + + wxDataViewItem layers_item = GetSelection(); + + t_layer_height_ranges& ranges = object(obj_idx)->layer_height_ranges; + + const t_layer_height_ranges::iterator selected_range = ranges.find(range); + const t_layer_height_ranges::iterator last_range = --ranges.end(); + + if (selected_range->first == last_range->first) + { + const t_layer_height_range new_range = { last_range->first.second, last_range->first.second + 0.2f }; + ranges[new_range] = last_range->second; + add_layer_item(new_range, layers_item); + } + else + { + int layer_idx = 0; + t_layer_height_ranges::iterator next_range = ++ranges.find(range); + + // May be not a best solution #ys_FIXME + t_layer_height_ranges::iterator it = ranges.begin(); + while (it != next_range && it != ranges.end()) { + layer_idx++; + it++; + } + + if (selected_range->first.second == next_range->first.first) + { + const coordf_t delta = (next_range->first.second - next_range->first.first); + if (delta < 0.05f) // next range devision has no mean + return; + + const coordf_t midl_layer = next_range->first.first + 0.5f * delta; + const coordf_t old_height = next_range->second; + t_layer_height_range new_range = { midl_layer, next_range->first.second }; + + // delete old layer + + wxDataViewItem layer_item = m_objects_model->GetItemByLayerId(obj_idx, layer_idx); + del_subobject_item(layer_item); + + // create new 2 layers instead of deleted one + + ranges[new_range] = old_height; + add_layer_item(new_range, layers_item, layer_idx); + + new_range = { selected_range->first.second, midl_layer }; + ranges[new_range] = selected_range->second; + add_layer_item(new_range, layers_item, layer_idx); + } + else + { + const t_layer_height_range new_range = { selected_range->first.second, next_range->first.first }; + ranges[new_range] = selected_range->second; + add_layer_item(new_range, layers_item, layer_idx); + } + } + + changed_object(obj_idx); + + // select item to update layers sizer + select_item(layers_item); +} + +void ObjectList::add_layer_item(const t_layer_height_range& range, + const wxDataViewItem layers_item, + const int layer_idx /* = -1*/) +{ + const std::string label = (boost::format(" %.2f-%.2f ") % range.first % range.second).str(); + m_objects_model->AddLayersChild(layers_item, label, layer_idx); +} + +void ObjectList::edit_layer_range(const std::pair& range) { } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 3883691375..15cddd318c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -272,6 +272,10 @@ public: void remove(); void del_layer_range(const std::pair& range); void add_layer_range(const std::pair& range); + void add_layer_item (const std::pair& range, + const wxDataViewItem layers_item, + const int layer_idx = -1); + void edit_layer_range(const std::pair& range); void init_objects(); bool multiple_selection() const ; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index dbe19dff5b..adc2e6d076 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -465,14 +465,22 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const wxString& label_range, - const wxString& extruder, - const int idx /*= -1 */) : + const int idx /*= -1 */, + const wxString& extruder) : m_parent(parent), m_type(itLayer), m_idx(idx), m_extruder(extruder) { - m_idx = parent->GetChildCount(); + const int children_cnt = parent->GetChildCount(); + if (idx < 0) + m_idx = children_cnt; + else + { + // update indexes for another Laeyr Nodes + for (int i = m_idx; i < children_cnt; i++) + parent->GetNthChild(i)->SetIdx(i + 1); + } // m_name = wxString::Format(_(L("Layer %s (mm)")), label_range); m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; m_bmp = create_scaled_bitmap(nullptr, "layers_white"); // FIXME: pass window ptr @@ -742,7 +750,9 @@ wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_i return layer_root_item; } -wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item, const std::string& label_range) +wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item, + const std::string& label_range, + const int index /* = -1*/) { ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); if (!parent_node) return wxDataViewItem(0); @@ -763,8 +773,11 @@ wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_ } // Add layer node - ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, label_range); - layer_root_node->Append(layer_node); + ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, label_range, index); + if (index < 0) + layer_root_node->Append(layer_node); + else + layer_root_node->Insert(layer_node, index); // notify control const wxDataViewItem layer_item((void*)layer_node); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 23150a9152..775d89a3b0 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -233,8 +233,8 @@ public: ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const wxString& label_range, - const wxString& extruder = wxEmptyString, - const int idx = -1 ); + const int idx = -1, + const wxString& extruder = wxEmptyString ); ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); @@ -396,7 +396,9 @@ public: wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); - wxDataViewItem AddLayersChild(const wxDataViewItem &parent_item, const std::string& label_range); + wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item, + const std::string& label_range, + const int index = -1); wxDataViewItem Delete(const wxDataViewItem &item); wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); void DeleteAll(); From e09207e27e28d37e1873fe4f1c63bbbad3faf6d5 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 31 May 2019 15:36:38 +0200 Subject: [PATCH 026/627] Fixed OSX and Linux build --- src/slic3r/GUI/GUI_ObjectList.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 88d7df6cc2..d9e1e8e82f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2189,8 +2189,11 @@ void ObjectList::remove() select_item(parent); parent = wxDataViewItem(0); } - else if (m_objects_model->GetChildren(parent, wxDataViewItemArray()) == 1) - parent = m_objects_model->GetTopParent(item); + else { + wxDataViewItemArray children; + if (m_objects_model->GetChildren(parent, children) == 1) + parent = m_objects_model->GetTopParent(item); + } } del_subobject_item(item); From e6af0d3dc4f85e42437dc5fd18d6b1cb528db251 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Sun, 2 Jun 2019 11:01:51 +0200 Subject: [PATCH 027/627] Temporary low-res texture shown while generating compressed data on the CPU --- src/slic3r/GUI/3DBed.cpp | 24 ++++++++++++++++++++++++ src/slic3r/GUI/3DBed.hpp | 4 ++++ src/slic3r/GUI/GLTexture.cpp | 14 +------------- src/slic3r/GUI/GLTexture.hpp | 4 ++-- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index e6589e5481..98f2d7dc32 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -522,6 +522,14 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) { #if ENABLE_COMPRESSED_TEXTURES + // generate a temporary lower resolution texture to show while no main texture levels have been compressed + if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8)) + { + render_custom(); + return; + } + + // starts generating the main texture, compression will run asynchronously if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size)) #else if (!m_texture.load_from_svg_file(filename, true, max_tex_size)) @@ -542,7 +550,14 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const } #if ENABLE_COMPRESSED_TEXTURES else if (m_texture.unsent_compressed_data_available()) + { + // sends to gpu the already available compressed levels of the main texture m_texture.send_compressed_data_to_gpu(); + + // the temporary texture is not needed anymore, reset it + if (m_temp_texture.get_id() != 0) + m_temp_texture.reset(); + } #endif // ENABLE_COMPRESSED_TEXTURES if (!bottom) @@ -616,7 +631,16 @@ void Bed3D::render_prusa_shader(bool transparent) const GLint position_id = m_shader.get_attrib_location("v_position"); GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords"); +#if ENABLE_COMPRESSED_TEXTURES + // show the temporary texture while no compressed data is available + GLuint tex_id = (GLuint)m_temp_texture.get_id(); + if (tex_id == 0) + tex_id = (GLuint)m_texture.get_id(); + + glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); +#else glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_texture.get_id())); +#endif // ENABLE_COMPRESSED_TEXTURES glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); if (position_id != -1) diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index e60cdf94e1..054b746e5e 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -91,6 +91,10 @@ private: GeometryBuffer m_gridlines; #if ENABLE_TEXTURES_FROM_SVG mutable GLTexture m_texture; +#if ENABLE_COMPRESSED_TEXTURES + // temporary texture shown until the main texture has still no levels compressed + mutable GLTexture m_temp_texture; +#endif // ENABLE_COMPRESSED_TEXTURES mutable Shader m_shader; mutable unsigned int m_vbo_id; #else diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index cc6bdc715a..469e192b59 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -343,18 +343,6 @@ void GLTexture::reset() #endif // ENABLE_COMPRESSED_TEXTURES } -#if ENABLE_COMPRESSED_TEXTURES -bool GLTexture::unsent_compressed_data_available() const -{ - return m_compressor.unsent_compressed_data_available(); -} - -void GLTexture::send_compressed_data_to_gpu() -{ - m_compressor.send_compressed_data_to_gpu(); -} -#endif // ENABLE_COMPRESSED_TEXTURES - void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top) { render_sub_texture(tex_id, left, right, bottom, top, FullTextureUVs); @@ -603,7 +591,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns int lod_h = m_height; GLint level = 0; // we do not need to generate all levels down to 1x1 - while ((lod_w > 64) || (lod_h > 64)) + while ((lod_w > 16) || (lod_h > 16)) { ++level; diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index f5cf13a455..ed2070a44e 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -106,8 +106,8 @@ namespace GUI { const std::string& get_source() const { return m_source; } #if ENABLE_COMPRESSED_TEXTURES - bool unsent_compressed_data_available() const; - void send_compressed_data_to_gpu(); + bool unsent_compressed_data_available() const { return m_compressor.unsent_compressed_data_available(); } + void send_compressed_data_to_gpu() { m_compressor.send_compressed_data_to_gpu(); } #endif // ENABLE_COMPRESSED_TEXTURES static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); From 6a8c7a87059c279bf7aba0a5092b5de41ff9b815 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 3 Jun 2019 13:53:30 +0200 Subject: [PATCH 028/627] Fixed race condition while compressing texture data and sending them to the GPU --- src/slic3r/GUI/GLTexture.cpp | 8 ++++++-- src/slic3r/GUI/GLTexture.hpp | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 469e192b59..78de38d549 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -55,7 +55,7 @@ bool GLTexture::Compressor::unsent_compressed_data_available() const { for (const Level& level : m_levels) { - if (!level.sent_to_gpu && (level.compressed_data.size() > 0)) + if (!level.sent_to_gpu && level.compressed) return true; } @@ -64,12 +64,14 @@ bool GLTexture::Compressor::unsent_compressed_data_available() const void GLTexture::Compressor::send_compressed_data_to_gpu() { + // this method should be called inside the main thread of Slicer or a new OpenGL context (sharing resources) would be needed + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.m_id)); for (int i = 0; i < (int)m_levels.size(); ++i) { Level& level = m_levels[i]; - if (!level.sent_to_gpu && (level.compressed_data.size() > 0)) + if (!level.sent_to_gpu && level.compressed) { glsafe(::glCompressedTexSubImage2D(GL_TEXTURE_2D, (GLint)i, 0, 0, (GLsizei)level.w, (GLsizei)level.h, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)level.compressed_data.size(), (const GLvoid*)level.compressed_data.data())); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i)); @@ -77,6 +79,7 @@ void GLTexture::Compressor::send_compressed_data_to_gpu() level.sent_to_gpu = true; // we are done with the compressed data, we can discard it level.compressed_data.clear(); + level.compressed = false; } } glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); @@ -100,6 +103,7 @@ void GLTexture::Compressor::compress() // we are done with the source data, we can discard it level.src_data.clear(); + level.compressed = true; } m_is_compressing = false; diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index ed2070a44e..d1ff366c34 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -20,9 +20,10 @@ namespace GUI { unsigned int h; std::vector src_data; std::vector compressed_data; + bool compressed; bool sent_to_gpu; - Level(unsigned int w, unsigned int h, const std::vector& data) : w(w), h(h), src_data(data), sent_to_gpu(false) {} + Level(unsigned int w, unsigned int h, const std::vector& data) : w(w), h(h), src_data(data), compressed(false), sent_to_gpu(false) {} }; GLTexture& m_texture; From 3d8bd85187674f18f2fd2fde4479eee707f33bd3 Mon Sep 17 00:00:00 2001 From: Bryan Smith Date: Fri, 8 Feb 2019 18:02:46 -0500 Subject: [PATCH 029/627] Add new [total_layer_count] placeholder everywhere [layer_num] is avalible. Missed adding [total_layer_count] to layer_gcode Revert "Add new [total_layer_count] placeholder everywhere [layer_num] is avalible." This reverts commit e29e766224a14e1f237908c3e5507ac5586ca5b7. Revert "Missed adding [total_layer_count] to layer_gcode" This reverts commit f07907a8acc450e9ae1220c6a9fe4f7c7e6d896c. Better way to add [total_layer_count] --- src/libslic3r/GCode.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 9ef2b88963..c42669de0d 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -781,6 +781,8 @@ void GCode::_do_export(Print &print, FILE *file) m_placeholder_parser.set("initial_tool", initial_extruder_id); m_placeholder_parser.set("initial_extruder", initial_extruder_id); m_placeholder_parser.set("current_extruder", initial_extruder_id); + //Set variable for total layer count so it can be used in custom gcode. + m_placeholder_parser.set("total_layer_count", m_layer_count); // Useful for sequential prints. m_placeholder_parser.set("current_object_idx", 0); // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. From 0ee0b546df7f2f002543d580c9860b14611f1af4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 3 Jun 2019 14:52:15 +0200 Subject: [PATCH 030/627] Fixed a bug with a "gear" button near the filament preset (part of #2440) --- src/slic3r/GUI/GUI_App.cpp | 9 +++++++++ src/slic3r/GUI/GUI_App.hpp | 1 + src/slic3r/GUI/Plater.cpp | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b75b946e6f..3aada45f39 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -939,6 +939,7 @@ wxNotebook* GUI_App::tab_panel() const return mainframe->m_tabpanel; } +// extruders count from selected printer preset int GUI_App::extruders_cnt() const { const Preset& preset = preset_bundle->printers.get_selected_preset(); @@ -946,6 +947,14 @@ int GUI_App::extruders_cnt() const preset.config.option("nozzle_diameter")->values.size(); } +// extruders count from edited printer preset +int GUI_App::extruders_edited_cnt() const +{ + const Preset& preset = preset_bundle->printers.get_edited_preset(); + return preset.printer_technology() == ptSLA ? 1 : + preset.config.option("nozzle_diameter")->values.size(); +} + void GUI_App::open_web_page_localized(const std::string &http_address) { wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code()); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index b70f0dc160..3f8b23e2d5 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -166,6 +166,7 @@ public: wxNotebook* tab_panel() const ; int extruders_cnt() const; + int extruders_edited_cnt() const; std::vector tabs_list; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 64c698a9bc..44f77b3f78 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -321,7 +321,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * /* In a case of a multi-material printing, for editing another Filament Preset * it's needed to select this preset for the "Filament settings" Tab */ - if (preset_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_cnt() > 1) + if (preset_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) { const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data(); From 8376d142670a804647292709495fd9645bf05510 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 3 Jun 2019 15:27:46 +0200 Subject: [PATCH 031/627] Fix for opening issues with archive files. --- src/libslic3r/CMakeLists.txt | 2 ++ src/libslic3r/Format/3mf.cpp | 54 ++++++++-------------------- src/libslic3r/Format/AMF.cpp | 51 +++++++------------------- src/libslic3r/Format/PRUS.cpp | 13 +++---- src/libslic3r/Zipper.cpp | 18 ++-------- src/libslic3r/miniz_extension.cpp | 59 +++++++++++++++++++++++++++++++ src/libslic3r/miniz_extension.hpp | 16 +++++++++ 7 files changed, 113 insertions(+), 100 deletions(-) create mode 100644 src/libslic3r/miniz_extension.cpp create mode 100644 src/libslic3r/miniz_extension.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6a4fe3c289..833b1ae629 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -163,6 +163,8 @@ add_library(libslic3r STATIC MTUtils.hpp Zipper.hpp Zipper.cpp + miniz_extension.hpp + miniz_extension.cpp SLA/SLABoilerPlate.hpp SLA/SLABasePool.hpp SLA/SLABasePool.cpp diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 7104391134..38b34c4620 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include "miniz_extension.hpp" // VERSION NUMBERS // 0 : .3mf, files saved by older slic3r or other applications. No version definition in them. @@ -188,24 +188,6 @@ bool is_valid_object_type(const std::string& type) namespace Slic3r { -namespace { -void close_archive_reader(mz_zip_archive *arch) { - if (arch) { - FILE *f = mz_zip_get_cfile(arch); - mz_zip_reader_end(arch); - if (f) fclose(f); - } -} - -void close_archive_writer(mz_zip_archive *arch) { - if (arch) { - FILE *f = mz_zip_get_cfile(arch); - mz_zip_writer_end(arch); - if (f) fclose(f); - } -} -} - // Base class with error messages management class _3MF_Base { @@ -522,10 +504,7 @@ void close_archive_writer(mz_zip_archive *arch) { mz_zip_archive archive; mz_zip_zero_struct(&archive); - FILE *f = boost::nowide::fopen(filename.c_str(), "rb"); - auto res = mz_bool(f != nullptr && mz_zip_reader_init_cfile(&archive, f, -1, 0)); - if (res == 0) - { + if (!open_zip_reader(&archive, filename)) { add_error("Unable to open the file"); return false; } @@ -549,7 +528,7 @@ void close_archive_writer(mz_zip_archive *arch) { // valid model name -> extract model if (!_extract_model_from_archive(archive, stat)) { - close_archive_reader(&archive); + close_zip_reader(&archive); add_error("Archive does not contain a valid model"); return false; } @@ -585,7 +564,7 @@ void close_archive_writer(mz_zip_archive *arch) { // extract slic3r model config file if (!_extract_model_config_from_archive(archive, stat, model)) { - close_archive_reader(&archive); + close_zip_reader(&archive); add_error("Archive does not contain a valid model config"); return false; } @@ -593,7 +572,7 @@ void close_archive_writer(mz_zip_archive *arch) { } } - close_archive_reader(&archive); + close_zip_reader(&archive); for (const IdToModelObjectMap::value_type& object : m_objects) { @@ -1659,10 +1638,7 @@ void close_archive_writer(mz_zip_archive *arch) { mz_zip_archive archive; mz_zip_zero_struct(&archive); - FILE *f = boost::nowide::fopen(filename.c_str(), "wb"); - auto res = mz_bool(f != nullptr && mz_zip_writer_init_cfile(&archive, f, 0)); - if (res == 0) - { + if (!open_zip_writer(&archive, filename)) { add_error("Unable to open the file"); return false; } @@ -1671,7 +1647,7 @@ void close_archive_writer(mz_zip_archive *arch) { // The content of this file is the same for each PrusaSlicer 3mf. if (!_add_content_types_file_to_archive(archive)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } @@ -1681,7 +1657,7 @@ void close_archive_writer(mz_zip_archive *arch) { // The relationshis file contains a reference to the geometry file "3D/3dmodel.model", the name was chosen to be compatible with CURA. if (!_add_relationships_file_to_archive(archive)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } @@ -1691,7 +1667,7 @@ void close_archive_writer(mz_zip_archive *arch) { IdToObjectDataMap objects_data; if (!_add_model_file_to_archive(archive, model, objects_data)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } @@ -1701,7 +1677,7 @@ void close_archive_writer(mz_zip_archive *arch) { // The index differes from the index of an object ID of an object instance of a 3MF file! if (!_add_layer_height_profile_file_to_archive(archive, model)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } @@ -1711,7 +1687,7 @@ void close_archive_writer(mz_zip_archive *arch) { // The index differes from the index of an object ID of an object instance of a 3MF file! if (!_add_sla_support_points_file_to_archive(archive, model)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } @@ -1722,7 +1698,7 @@ void close_archive_writer(mz_zip_archive *arch) { { if (!_add_print_config_file_to_archive(archive, *config)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } @@ -1734,20 +1710,20 @@ void close_archive_writer(mz_zip_archive *arch) { // is stored here as well. if (!_add_model_config_file_to_archive(archive, model, objects_data)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); return false; } if (!mz_zip_writer_finalize_archive(&archive)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(filename); add_error("Unable to finalize the archive"); return false; } - close_archive_writer(&archive); + close_zip_writer(&archive); return true; } diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index c4b6901a80..d26b5f3ed9 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include "miniz_extension.hpp" #if 0 // Enable debugging and assert in this file. @@ -712,37 +712,19 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) return result; } -namespace { -void close_archive_reader(mz_zip_archive *arch) { - if (arch) { - FILE *f = mz_zip_get_cfile(arch); - mz_zip_reader_end(arch); - if (f) fclose(f); - } -} - -void close_archive_writer(mz_zip_archive *arch) { - if (arch) { - FILE *f = mz_zip_get_cfile(arch); - mz_zip_writer_end(arch); - if (f) fclose(f); - } -} -} - bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, Model* model, unsigned int& version) { if (stat.m_uncomp_size == 0) { printf("Found invalid size\n"); - close_archive_reader(&archive); + close_zip_reader(&archive); return false; } XML_Parser parser = XML_ParserCreate(nullptr); // encoding if (!parser) { printf("Couldn't allocate memory for parser\n"); - close_archive_reader(&archive); + close_zip_reader(&archive); return false; } @@ -755,7 +737,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi if (parser_buffer == nullptr) { printf("Unable to create buffer\n"); - close_archive_reader(&archive); + close_zip_reader(&archive); return false; } @@ -763,14 +745,14 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi if (res == 0) { printf("Error while reading model data to buffer\n"); - close_archive_reader(&archive); + close_zip_reader(&archive); return false; } if (!XML_ParseBuffer(parser, (int)stat.m_uncomp_size, 1)) { printf("Error (%s) while parsing xml file at line %d\n", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); - close_archive_reader(&archive); + close_zip_reader(&archive); return false; } @@ -792,10 +774,7 @@ bool load_amf_archive(const char *path, DynamicPrintConfig *config, Model *model mz_zip_archive archive; mz_zip_zero_struct(&archive); - FILE * f = boost::nowide::fopen(path, "rb"); - auto res = mz_bool(f == nullptr ? MZ_FALSE : MZ_TRUE); - res = res && mz_zip_reader_init_cfile(&archive, f, -1, 0); - if (res == MZ_FALSE) + if (!open_zip_reader(&archive, path)) { printf("Unable to init zip reader\n"); return false; @@ -813,7 +792,7 @@ bool load_amf_archive(const char *path, DynamicPrintConfig *config, Model *model { if (!extract_model_from_archive(archive, stat, config, model, version)) { - close_archive_reader(&archive); + close_zip_reader(&archive); printf("Archive does not contain a valid model"); return false; } @@ -834,7 +813,7 @@ bool load_amf_archive(const char *path, DynamicPrintConfig *config, Model *model } #endif // forward compatibility - close_archive_reader(&archive); + close_zip_reader(&archive); return true; } @@ -874,10 +853,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) mz_zip_archive archive; mz_zip_zero_struct(&archive); - FILE *f = boost::nowide::fopen(export_path.c_str(), "wb"); - auto res = mz_bool(f != nullptr && mz_zip_writer_init_cfile(&archive, f, 0)); - if (res == 0) - return false; + if (!open_zip_writer(&archive, export_path)) return false; std::stringstream stream; // https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 @@ -1039,20 +1015,19 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) if (!mz_zip_writer_add_mem(&archive, internal_amf_filename.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - close_archive_writer(&archive); - if (f) fclose(f); + close_zip_writer(&archive); boost::filesystem::remove(export_path); return false; } if (!mz_zip_writer_finalize_archive(&archive)) { - close_archive_writer(&archive); + close_zip_writer(&archive); boost::filesystem::remove(export_path); return false; } - close_archive_writer(&archive); + close_zip_writer(&archive); return true; } diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index 802a8304c9..502cac6e9e 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include "miniz_extension.hpp" #include @@ -300,11 +300,10 @@ bool load_prus(const char *path, Model *model) mz_zip_archive archive; mz_zip_zero_struct(&archive); - FILE *f = boost::nowide::fopen(path, "rb"); - auto res = mz_bool(f != nullptr && mz_zip_reader_init_cfile(&archive, f, -1, 0)); size_t n_models_initial = model->objects.size(); + mz_bool res = MZ_FALSE; try { - if (res == MZ_FALSE) + if (!open_zip_reader(&archive, path)) throw std::runtime_error(std::string("Unable to init zip reader for ") + path); std::vector scene_xml_data; // For grouping multiple STLs into a single ModelObject for multi-material prints. @@ -329,13 +328,11 @@ bool load_prus(const char *path, Model *model) } } } catch (std::exception &ex) { - mz_zip_reader_end(&archive); - if(f) fclose(f); + close_zip_reader(&archive); throw ex; } - mz_zip_reader_end(&archive); - if(f) fclose(f); + close_zip_reader(&archive); return model->objects.size() > n_models_initial; } diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index e821c29689..348be49ccb 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -1,9 +1,8 @@ #include #include "Zipper.hpp" -#include +#include "miniz_extension.hpp" #include -#include #include "I18N.hpp" //! macro used to mark string used at localization, @@ -124,16 +123,9 @@ Zipper::Zipper(const std::string &zipfname, e_compression compression) memset(&m_impl->arch, 0, sizeof(m_impl->arch)); - FILE *f = boost::nowide::fopen(zipfname.c_str(), "wb"); - - if (f == nullptr) { - m_impl->arch.m_last_error = MZ_ZIP_FILE_OPEN_FAILED; + if (!open_zip_writer(&m_impl->arch, zipfname)) { m_impl->blow_up(); } - - // Initialize the archive data - if(!mz_zip_writer_init_cfile(&m_impl->arch, f, 0)) - m_impl->blow_up(); } Zipper::~Zipper() @@ -148,13 +140,9 @@ Zipper::~Zipper() BOOST_LOG_TRIVIAL(error) << m_impl->formatted_errorstr(); } - FILE *f = mz_zip_get_cfile(&m_impl->arch); - // The file should be closed no matter what... - if(!mz_zip_writer_end(&m_impl->arch)) + if(!close_zip_writer(&m_impl->arch)) BOOST_LOG_TRIVIAL(error) << m_impl->formatted_errorstr(); - - if(f != nullptr) fclose(f); } Zipper::Zipper(Zipper &&m): diff --git a/src/libslic3r/miniz_extension.cpp b/src/libslic3r/miniz_extension.cpp new file mode 100644 index 0000000000..e87b1a552e --- /dev/null +++ b/src/libslic3r/miniz_extension.cpp @@ -0,0 +1,59 @@ +#include "miniz_extension.hpp" + +#if defined(_MSC_VER) || defined(__MINGW64__) +#include "boost/nowide/cstdio.hpp" +#endif + +namespace Slic3r { + +namespace { +bool open_zip(mz_zip_archive *zip, const char *fname, bool isread) +{ + if (!zip) return false; + const char *mode = isread ? "rb" : "wb"; + + FILE *f = nullptr; +#if defined(_MSC_VER) || defined(__MINGW64__) + f = boost::nowide::fopen(fname, mode); +#elif defined(__GNUC__) && defined(_LARGEFILE64_SOURCE) + f = fopen64(fname, mode); +#else + f = fopen(fname, mode); +#endif + + if (!f) { + zip->m_last_error = MZ_ZIP_FILE_OPEN_FAILED; + return false; + } + + return isread ? mz_zip_reader_init_cfile(zip, f, 0, 0) + : mz_zip_writer_init_cfile(zip, f, 0); +} + +bool close_zip(mz_zip_archive *zip, bool isread) +{ + bool ret = false; + if (zip) { + FILE *f = mz_zip_get_cfile(zip); + ret = bool(isread ? mz_zip_reader_end(zip) + : mz_zip_writer_end(zip)); + if (f) fclose(f); + } + return ret; +} +} + +bool open_zip_reader(mz_zip_archive *zip, const std::string &fname) +{ + return open_zip(zip, fname.c_str(), true); +} + +bool open_zip_writer(mz_zip_archive *zip, const std::string &fname) +{ + return open_zip(zip, fname.c_str(), false); +} + +bool close_zip_reader(mz_zip_archive *zip) { return close_zip(zip, true); } +bool close_zip_writer(mz_zip_archive *zip) { return close_zip(zip, false); } + +} diff --git a/src/libslic3r/miniz_extension.hpp b/src/libslic3r/miniz_extension.hpp new file mode 100644 index 0000000000..8d0967cbcc --- /dev/null +++ b/src/libslic3r/miniz_extension.hpp @@ -0,0 +1,16 @@ +#ifndef MINIZ_EXTENSION_HPP +#define MINIZ_EXTENSION_HPP + +#include +#include + +namespace Slic3r { + +bool open_zip_reader(mz_zip_archive *zip, const std::string &fname_utf8); +bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8); +bool close_zip_reader(mz_zip_archive *zip); +bool close_zip_writer(mz_zip_archive *zip); + +} + +#endif // MINIZ_EXTENSION_HPP From 51b18fddeb1b22310171e27b8e606cc0d621f17d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 3 Jun 2019 15:35:21 +0200 Subject: [PATCH 032/627] Changed data types --- src/libslic3r/Model.hpp | 2 ++ src/libslic3r/Slicing.hpp | 3 ++ src/slic3r/GUI/GUI_ObjectLayers.cpp | 37 ++++++++++++++---------- src/slic3r/GUI/GUI_ObjectLayers.hpp | 1 + src/slic3r/GUI/GUI_ObjectList.cpp | 45 ++++++++++++++++------------- src/slic3r/GUI/GUI_ObjectList.hpp | 1 + 6 files changed, 54 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 41bf5bd4be..d3066f33f1 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -179,6 +179,8 @@ public: DynamicPrintConfig config; // Variation of a layer thickness for spans of Z coordinates. t_layer_height_ranges layer_height_ranges; + // Variation of a layer thickness for spans of Z coordinates. + t_layer_config_ranges layer_config_ranges; // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. // The pairs of are packed into a 1D array. std::vector layer_height_profile; diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index fa5a12f9ce..c3278dc0d8 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -11,6 +11,8 @@ #include "libslic3r.h" #include "Utils.hpp" +#include "PrintConfig.hpp" + namespace Slic3r { @@ -129,6 +131,7 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters typedef std::pair t_layer_height_range; typedef std::map t_layer_height_ranges; +typedef std::map t_layer_config_ranges; extern std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 5ff650e867..8dae64f3cb 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -23,6 +23,8 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : { m_og->label_width = 1; m_og->set_grid_vgap(5); + + m_og->m_on_change = std::bind(&ObjectLayers::on_change, this, std::placeholders::_1, std::placeholders::_2); // Legend for object layers Line line = Line{ "", "" }; @@ -33,9 +35,9 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : def.type = coString; def.width = field_width; - for (const std::string axis : { "Min Z", "Max Z", "Layer height" }) { - def.set_default_value(new ConfigOptionString{ axis }); - std::string label = boost::algorithm::replace_all_copy(axis, " ", "_"); + for (const std::string col : { "Min Z", "Max Z", "Layer height" }) { + def.set_default_value(new ConfigOptionString{ col }); + std::string label = boost::algorithm::replace_all_copy(col, " ", "_"); boost::algorithm::to_lower(label); line.append_option(Option(def, label + "_legend")); @@ -48,7 +50,7 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : m_bmp_add = ScalableBitmap(parent, "add_copies"); } -static Line create_new_layer(const t_layer_height_ranges::value_type& layer) +static Line create_new_layer(const t_layer_config_ranges::value_type& layer, const int idx) { Line line = Line{ "", "" }; ConfigOptionDef def; @@ -57,16 +59,16 @@ static Line create_new_layer(const t_layer_height_ranges::value_type& layer) def.type = coFloat; def.width = field_width; - std::string label = (boost::format("min_z_%.2f") % layer.first.first).str(); + std::string label = (boost::format("min_z_%d") % idx).str(); def.set_default_value(new ConfigOptionFloat(layer.first.first)); line.append_option(Option(def, label)); - label = (boost::format("max_z_%.2f") % layer.first.second).str(); + label = (boost::format("max_z_%d") % idx).str(); def.set_default_value(new ConfigOptionFloat(layer.first.second)); line.append_option(Option(def, label)); - label = (boost::format("layer_height_%.2f_%.2f") % layer.first.first % layer.first.second).str(); - def.set_default_value(new ConfigOptionFloat(layer.second)); + label = (boost::format("layer_height_%d") % idx).str(); + def.set_default_value(new ConfigOptionFloat(layer.second.option("layer_height")->getFloat())); line.append_option(Option(def, label)); return line; @@ -74,7 +76,7 @@ static Line create_new_layer(const t_layer_height_ranges::value_type& layer) void ObjectLayers::create_layers_list() { - for (const auto layer : m_object->layer_height_ranges) + for (const auto layer : m_object->layer_config_ranges) { auto create_btns = [this, layer](wxWindow* parent) { auto sizer = new wxBoxSizer(wxHORIZONTAL); @@ -100,7 +102,7 @@ void ObjectLayers::create_layers_list() return sizer; }; - Line line = create_new_layer(layer); + Line line = create_new_layer(layer, m_og->get_grid_sizer()->GetEffectiveRowsCount()-1); line.append_widget(create_btns); m_og->append_line(line); } @@ -108,15 +110,15 @@ void ObjectLayers::create_layers_list() void ObjectLayers::create_layer(int id) { - t_layer_height_ranges::iterator layer_range = m_object->layer_height_ranges.begin(); + t_layer_config_ranges::iterator layer_range = m_object->layer_config_ranges.begin(); // May be not a best solution #ys_FIXME - while (id > 0 && layer_range != m_object->layer_height_ranges.end()) { - layer_range++; + while (id > 0 && layer_range != m_object->layer_config_ranges.end()) { + ++layer_range; id--; } - m_og->append_line(create_new_layer(*layer_range)); + m_og->append_line(create_new_layer(*layer_range, m_og->get_grid_sizer()->GetEffectiveRowsCount()-1)); } void ObjectLayers::update_layers_list() @@ -134,7 +136,7 @@ void ObjectLayers::update_layers_list() if (!(type & (itLayerRoot | itLayer))) return; m_object = objects_ctrl->object(obj_idx); - if (!m_object || m_object->layer_height_ranges.empty()) return; + if (!m_object || m_object->layer_config_ranges.empty()) return; // Delete all controls from options group except of the legends @@ -175,5 +177,10 @@ void ObjectLayers::msw_rescale() m_bmp_add.msw_rescale(); } +void ObjectLayers::on_change(t_config_option_key opt_key, const boost::any& value) +{ + +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 0b209d523a..6280a7554f 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -30,6 +30,7 @@ public: void UpdateAndShow(const bool show) override; void msw_rescale(); + void on_change(t_config_option_key opt_key, const boost::any& value); }; }} diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d9e1e8e82f..9b826f5d8f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -700,10 +700,11 @@ void ObjectList::show_context_menu() if (item) { const ItemType type = m_objects_model->GetItemType(item); - if (!(type & (itObject | itVolume | itInstance))) + if (!(type & (itObject | itVolume | itLayer | itInstance))) return; wxMenu* menu = type & itInstance ? &m_menu_instance : + type & itLayer ? &m_menu_layer : m_objects_model->GetParent(item) != wxDataViewItem(0) ? &m_menu_part : printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; @@ -1694,7 +1695,7 @@ void ObjectList::del_instances_from_object(const int obj_idx) void ObjectList::del_layers_from_object(const int obj_idx) { - object(obj_idx)->layer_height_ranges.clear(); // ? #ys_FIXME + object(obj_idx)->layer_config_ranges.clear(); changed_object(obj_idx); } @@ -1739,13 +1740,13 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con object->delete_instance(idx); } else if (type == itLayer) { - t_layer_height_ranges::iterator layer_range = object->layer_height_ranges.begin(); + t_layer_config_ranges::iterator layer_range = object->layer_config_ranges.begin(); int id = idx; - while (id > 0 && layer_range != object->layer_height_ranges.end()) { + while (id > 0 && layer_range != object->layer_config_ranges.end()) { layer_range++; id--; } - object->layer_height_ranges.erase(layer_range); + object->layer_config_ranges.erase(layer_range); } else return false; @@ -1816,14 +1817,16 @@ void ObjectList::layers_editing() // if it doesn't exist now if (!layers_item.IsOk()) { - // create LayerRoor item + // create LayerRoot item layers_item = m_objects_model->AddLayersRoot(obj_item); - - if (object(obj_idx)->layer_height_ranges.empty()) - object(obj_idx)->layer_height_ranges[{ 0.0f, 0.2f }] = 0.1f;// some default value - // and create Layer item(s) according to the layer_height_ranges - for (const auto range : object(obj_idx)->layer_height_ranges) + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + if (ranges.empty()) + ranges[{ 0.0f, 0.2f }] = *DynamicPrintConfig::new_from_defaults_keys({"layer_height"});// some default value + + // and create Layer item(s) according to the layer_config_ranges + for (const auto range : ranges) add_layer_item(range.first, layers_item); } @@ -2239,10 +2242,10 @@ void ObjectList::add_layer_range(const t_layer_height_range& range) wxDataViewItem layers_item = GetSelection(); - t_layer_height_ranges& ranges = object(obj_idx)->layer_height_ranges; + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; - const t_layer_height_ranges::iterator selected_range = ranges.find(range); - const t_layer_height_ranges::iterator last_range = --ranges.end(); + const t_layer_config_ranges::iterator selected_range = ranges.find(range); + const t_layer_config_ranges::iterator last_range = --ranges.end(); if (selected_range->first == last_range->first) { @@ -2253,13 +2256,13 @@ void ObjectList::add_layer_range(const t_layer_height_range& range) else { int layer_idx = 0; - t_layer_height_ranges::iterator next_range = ++ranges.find(range); + t_layer_config_ranges::iterator next_range = ++ranges.find(range); // May be not a best solution #ys_FIXME - t_layer_height_ranges::iterator it = ranges.begin(); + t_layer_config_ranges::iterator it = ranges.begin(); while (it != next_range && it != ranges.end()) { layer_idx++; - it++; + ++it; } if (selected_range->first.second == next_range->first.first) @@ -2269,7 +2272,8 @@ void ObjectList::add_layer_range(const t_layer_height_range& range) return; const coordf_t midl_layer = next_range->first.first + 0.5f * delta; - const coordf_t old_height = next_range->second; + // #ys_FIXME May be it should be copied just a "layer_height" option + const /*coordf_t*/auto old_config = next_range->second; t_layer_height_range new_range = { midl_layer, next_range->first.second }; // delete old layer @@ -2279,7 +2283,7 @@ void ObjectList::add_layer_range(const t_layer_height_range& range) // create new 2 layers instead of deleted one - ranges[new_range] = old_height; + ranges[new_range] = old_config; add_layer_item(new_range, layers_item, layer_idx); new_range = { selected_range->first.second, midl_layer }; @@ -2996,7 +3000,8 @@ void ObjectList::msw_rescale() for (MenuWithSeparators* menu : { &m_menu_object, &m_menu_part, &m_menu_sla_object, - &m_menu_instance }) + &m_menu_instance, + &m_menu_layer }) msw_rescale_menu(menu); Layout(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 15cddd318c..5b4fd4c49b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -119,6 +119,7 @@ class ObjectList : public wxDataViewCtrl MenuWithSeparators m_menu_part; MenuWithSeparators m_menu_sla_object; MenuWithSeparators m_menu_instance; + MenuWithSeparators m_menu_layer; wxMenuItem* m_menu_item_settings { nullptr }; wxMenuItem* m_menu_item_split_instances { nullptr }; From 050e312037720920b149780df4998c053248e864 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 3 Jun 2019 16:01:02 +0200 Subject: [PATCH 033/627] Fixed the github issue template to indicate that 3MFs cannot be attached directly and need to be zipped Fixes "Can't upload .3mf files to GitHub for issues #2372" --- .github/ISSUE_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 3744315ae3..dbf3c76dc3 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -24,3 +24,4 @@ _Is this a new feature request?_ #### Project File (.3MF) where problem occurs _Upload a PrusaSlicer Project File (.3MF) (`Plater -> Export plate as 3MF` for Slic3r PE 1.41.2 and older, `File -> Save` / `Save Project` for PrusaSlicer, Slic3r PE 1.42.0-alpha and newer)_ +_Images (PNG, GIF, JPEG), PDFs or text files could be drag & dropped to the issue directly, while all other files need to be zipped first (.zip, .gz)_ From 053a0af374024f416523a41f8ffd2ae34c1562c2 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 21 May 2019 13:02:47 +0200 Subject: [PATCH 034/627] Add missing header --- src/libslic3r/Zipper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index 348be49ccb..7e69f0bb4d 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -1,3 +1,4 @@ +#include #include #include "Zipper.hpp" From 3c8024bdddc0c6c76c41a8a08eb40a4eb8e95a77 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 3 Jun 2019 16:59:27 +0200 Subject: [PATCH 035/627] BonjourDialog: Fix dialog flags --- src/slic3r/GUI/BonjourDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index ec6b2f0c95..1885dda7be 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -52,7 +52,7 @@ struct LifetimeGuard }; BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech) - : wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxRESIZE_BORDER) + : wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) , list(new wxListView(this, wxID_ANY)) , replies(new ReplySet) , label(new wxStaticText(this, wxID_ANY, "")) From 7a5d3de1c4497efb2d0efa3b6abea6bfdcbac198 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 4 Jun 2019 09:26:33 +0200 Subject: [PATCH 036/627] Move cstdint to Zipper header. The declarations already contain specific integers. --- src/libslic3r/Zipper.cpp | 1 - src/libslic3r/Zipper.hpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index 7e69f0bb4d..348be49ccb 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -1,4 +1,3 @@ -#include #include #include "Zipper.hpp" diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp index 7d95ffdac7..a574de9596 100644 --- a/src/libslic3r/Zipper.hpp +++ b/src/libslic3r/Zipper.hpp @@ -1,6 +1,7 @@ #ifndef ZIPPER_HPP #define ZIPPER_HPP +#include #include #include From 79a89c4c8fd5b09374ba981fea7c3a3da6711236 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 4 Jun 2019 11:51:25 +0200 Subject: [PATCH 037/627] Some code review for avoid use of OptionsGroup --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 145 ++++++++++++++-------------- src/slic3r/GUI/GUI_ObjectLayers.hpp | 6 +- 2 files changed, 77 insertions(+), 74 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 8dae64f3cb..1bd57e0d8b 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -21,90 +21,100 @@ namespace GUI ObjectLayers::ObjectLayers(wxWindow* parent) : OG_Settings(parent, true) { - m_og->label_width = 1; - m_og->set_grid_vgap(5); + m_grid_sizer = new wxFlexGridSizer(3, 5, 5); // "Min Z", "Max Z", "Layer height" & buttons sizer + m_grid_sizer->SetFlexibleDirection(wxHORIZONTAL); - m_og->m_on_change = std::bind(&ObjectLayers::on_change, this, std::placeholders::_1, std::placeholders::_2); - // Legend for object layers - Line line = Line{ "", "" }; - - ConfigOptionDef def; - def.label = ""; - def.gui_type = "legend"; - def.type = coString; - def.width = field_width; - for (const std::string col : { "Min Z", "Max Z", "Layer height" }) { - def.set_default_value(new ConfigOptionString{ col }); - std::string label = boost::algorithm::replace_all_copy(col, " ", "_"); - boost::algorithm::to_lower(label); - line.append_option(Option(def, label + "_legend")); + auto temp = new wxStaticText(m_parent, wxID_ANY, _(L(col)), wxDefaultPosition, /*size*/wxDefaultSize, wxST_ELLIPSIZE_MIDDLE); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + temp->SetBackgroundStyle(wxBG_STYLE_PAINT); + temp->SetFont(wxGetApp().bold_font()); - m_legends.push_back(label + "_legend"); + m_grid_sizer->Add(temp); } - m_og->append_line(line); + m_og->sizer->Clear(true); + m_og->sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); m_bmp_delete = ScalableBitmap(parent, "remove_copies"/*"cross"*/); m_bmp_add = ScalableBitmap(parent, "add_copies"); } -static Line create_new_layer(const t_layer_config_ranges::value_type& layer, const int idx) +wxSizer* ObjectLayers::create_layer_without_buttons(const t_layer_config_ranges::value_type& layer) { - Line line = Line{ "", "" }; - ConfigOptionDef def; - def.label = ""; - def.gui_type = ""; - def.type = coFloat; - def.width = field_width; + auto size = wxSize(field_width * em_unit(m_parent), wxDefaultCoord); - std::string label = (boost::format("min_z_%d") % idx).str(); - def.set_default_value(new ConfigOptionFloat(layer.first.first)); - line.append_option(Option(def, label)); + // Add control for the "Min Z" + wxString text_value = double_to_string(layer.first.first); + auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, wxTE_PROCESS_ENTER); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - label = (boost::format("max_z_%d") % idx).str(); - def.set_default_value(new ConfigOptionFloat(layer.first.second)); - line.append_option(Option(def, label)); + temp->Bind(wxEVT_TEXT_ENTER, ([this, temp](wxEvent& e) + { - label = (boost::format("layer_height_%d") % idx).str(); - def.set_default_value(new ConfigOptionFloat(layer.second.option("layer_height")->getFloat())); - line.append_option(Option(def, label)); + }), temp->GetId()); - return line; + temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e) + { + + }), temp->GetId()); + + + temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event) + { + // select all text using Ctrl+A + if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) + temp->SetSelection(-1, -1); //select all + event.Skip(); + })); + + m_grid_sizer->Add(temp); + + // Add control for the "Max Z" + text_value = double_to_string(layer.first.second); + temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, wxTE_PROCESS_ENTER); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + + m_grid_sizer->Add(temp); + + // Add control for the "Layer height" + auto sizer = new wxBoxSizer(wxHORIZONTAL); + + text_value = double_to_string(layer.second.option("layer_height")->getFloat()); + temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, wxTE_PROCESS_ENTER); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + sizer->Add(temp); + + m_grid_sizer->Add(sizer); + + return sizer; } void ObjectLayers::create_layers_list() { for (const auto layer : m_object->layer_config_ranges) { - auto create_btns = [this, layer](wxWindow* parent) { - auto sizer = new wxBoxSizer(wxHORIZONTAL); + auto sizer = create_layer_without_buttons(layer); - auto del_btn = new ScalableButton(parent, wxID_ANY, m_bmp_delete); - del_btn->SetToolTip(_(L("Remove layer"))); + wxWindow* parent = m_parent; + auto del_btn = new ScalableButton(parent, wxID_ANY, m_bmp_delete); + del_btn->SetToolTip(_(L("Remove layer"))); - sizer->Add(del_btn, 0, wxRIGHT, em_unit(parent)); + sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, em_unit(parent)); - del_btn->Bind(wxEVT_BUTTON, [this, layer](wxEvent &event) { - wxGetApp().obj_list()->del_layer_range(layer.first); - }); + del_btn->Bind(wxEVT_BUTTON, [this, layer](wxEvent &event) { + wxGetApp().obj_list()->del_layer_range(layer.first); + }); - auto add_btn = new ScalableButton(parent, wxID_ANY, m_bmp_add); - add_btn->SetToolTip(_(L("Add layer"))); + auto add_btn = new ScalableButton(parent, wxID_ANY, m_bmp_add); + add_btn->SetToolTip(_(L("Add layer"))); - sizer->Add(add_btn, 0, wxRIGHT, em_unit(parent)); + sizer->Add(add_btn, 0, wxRIGHT, em_unit(parent)); - add_btn->Bind(wxEVT_BUTTON, [this, layer](wxEvent &event) { - wxGetApp().obj_list()->add_layer_range(layer.first); - }); - - return sizer; - }; - - Line line = create_new_layer(layer, m_og->get_grid_sizer()->GetEffectiveRowsCount()-1); - line.append_widget(create_btns); - m_og->append_line(line); + add_btn->Bind(wxEVT_BUTTON, [this, layer](wxEvent &event) { + wxGetApp().obj_list()->add_layer_range(layer.first); + }); } } @@ -117,8 +127,8 @@ void ObjectLayers::create_layer(int id) ++layer_range; id--; } - - m_og->append_line(create_new_layer(*layer_range, m_og->get_grid_sizer()->GetEffectiveRowsCount()-1)); + + create_layer_without_buttons(*layer_range); } void ObjectLayers::update_layers_list() @@ -140,19 +150,17 @@ void ObjectLayers::update_layers_list() // Delete all controls from options group except of the legends - auto grid_sizer = m_og->get_grid_sizer(); - const int cols = grid_sizer->GetEffectiveColsCount(); - const int rows = grid_sizer->GetEffectiveRowsCount(); + const int cols = m_grid_sizer->GetEffectiveColsCount(); + const int rows = m_grid_sizer->GetEffectiveRowsCount(); for (int idx = cols*rows-1; idx >= cols; idx--) { - wxSizerItem* t = grid_sizer->GetItem(idx); + wxSizerItem* t = m_grid_sizer->GetItem(idx); if (t->IsSizer()) t->GetSizer()->Clear(true); - grid_sizer->Remove(idx); + else + t->DeleteWindows(); + m_grid_sizer->Remove(idx); } - m_og->clear_fields_except_of(m_legends); - - // Add new control according to the selected item if (type & itLayerRoot) @@ -177,10 +185,5 @@ void ObjectLayers::msw_rescale() m_bmp_add.msw_rescale(); } -void ObjectLayers::on_change(t_config_option_key opt_key, const boost::any& value) -{ - -} - } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 6280a7554f..329452950c 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -18,19 +18,19 @@ class ObjectLayers : public OG_Settings ScalableBitmap m_bmp_add; ModelObject* m_object {nullptr}; - std::vector m_legends; + wxFlexGridSizer* m_grid_sizer; public: ObjectLayers(wxWindow* parent); ~ObjectLayers() {} - void create_layers_list(); + wxSizer* create_layer_without_buttons(const std::map, DynamicPrintConfig>::value_type& layer); void create_layer(int id); + void create_layers_list(); void update_layers_list(); void UpdateAndShow(const bool show) override; void msw_rescale(); - void on_change(t_config_option_key opt_key, const boost::any& value); }; }} From 475696167884ac80cf554b9dd6bad772facd3e8c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 4 Jun 2019 15:22:29 +0200 Subject: [PATCH 038/627] Added LayerRangeEditor class for universally editing of the layer_range's parameters + Implemented layer_height editing --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 91 +++++++++++++++++++---------- src/slic3r/GUI/GUI_ObjectLayers.hpp | 16 +++++ src/slic3r/GUI/GUI_ObjectList.cpp | 8 ++- src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- 4 files changed, 85 insertions(+), 32 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 1bd57e0d8b..bba39f76a2 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -46,44 +46,21 @@ wxSizer* ObjectLayers::create_layer_without_buttons(const t_layer_config_ranges: auto size = wxSize(field_width * em_unit(m_parent), wxDefaultCoord); // Add control for the "Min Z" - wxString text_value = double_to_string(layer.first.first); - auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, wxTE_PROCESS_ENTER); - temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - - temp->Bind(wxEVT_TEXT_ENTER, ([this, temp](wxEvent& e) - { - - }), temp->GetId()); - - temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e) - { - - }), temp->GetId()); - - - temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event) - { - // select all text using Ctrl+A - if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) - temp->SetSelection(-1, -1); //select all - event.Skip(); - })); - + auto temp = new LayerRangeEditor(m_parent, double_to_string(layer.first.first), size); m_grid_sizer->Add(temp); // Add control for the "Max Z" - text_value = double_to_string(layer.first.second); - temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, wxTE_PROCESS_ENTER); - temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - + temp = new LayerRangeEditor(m_parent, double_to_string(layer.first.second), size); m_grid_sizer->Add(temp); // Add control for the "Layer height" auto sizer = new wxBoxSizer(wxHORIZONTAL); - text_value = double_to_string(layer.second.option("layer_height")->getFloat()); - temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, wxTE_PROCESS_ENTER); - temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + const wxString text_value = double_to_string(layer.second.option("layer_height")->getFloat()); + + temp = new LayerRangeEditor(m_parent, text_value, size, [temp, layer](coordf_t layer_height) { + wxGetApp().obj_list()->edit_layer_range(layer.first, layer_height); + } ); sizer->Add(temp); m_grid_sizer->Add(sizer); @@ -185,5 +162,59 @@ void ObjectLayers::msw_rescale() m_bmp_add.msw_rescale(); } +LayerRangeEditor::LayerRangeEditor( wxWindow* parent, + const wxString& value, + const wxSize& size, + std::function edit_fn + ) : + wxTextCtrl(parent, wxID_ANY, value, wxDefaultPosition, size, wxTE_PROCESS_ENTER) +{ + this->SetFont(wxGetApp().normal_font()); + + this->Bind(wxEVT_TEXT_ENTER, ([this, edit_fn](wxEvent& e) + { + edit_fn(get_value()); + m_enter_pressed = true; + }), this->GetId()); + + this->Bind(wxEVT_KILL_FOCUS, ([this, edit_fn](wxEvent& e) + { + e.Skip(); + if (!m_enter_pressed) + edit_fn(get_value()); + m_enter_pressed = false; + }), this->GetId()); + + + this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event) + { + // select all text using Ctrl+A + if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) + this->SetSelection(-1, -1); //select all + event.Skip(); + })); +} + +coordf_t LayerRangeEditor::get_value() +{ + wxString str = GetValue(); + + coordf_t layer_height; + // Replace the first occurence of comma in decimal number. + str.Replace(",", ".", false); + if (str == ".") + layer_height = 0.0; + else + { + if (!str.ToCDouble(&layer_height) || layer_height < 0.0f) + { + show_error(m_parent, _(L("Invalid numeric input."))); + SetValue(double_to_string(layer_height)); + } + } + + return layer_height; +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 329452950c..3aa3cb967c 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -12,6 +12,22 @@ class ModelObject; namespace GUI { class ConfigOptionsGroup; +class LayerRangeEditor : public wxTextCtrl +{ + bool m_enter_pressed { false }; +public: + LayerRangeEditor( wxWindow* parent, + const wxString& value = wxEmptyString, + const wxSize& size = wxDefaultSize, + std::function edit_fn = [](coordf_t) {; } + ); + ~LayerRangeEditor() {} + + +private: + coordf_t get_value(); +}; + class ObjectLayers : public OG_Settings { ScalableBitmap m_bmp_delete; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 9b826f5d8f..25df5c4fbd 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2312,9 +2312,15 @@ void ObjectList::add_layer_item(const t_layer_height_range& range, m_objects_model->AddLayersChild(layers_item, label, layer_idx); } -void ObjectList::edit_layer_range(const std::pair& range) +void ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height) { + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + DynamicPrintConfig* config = &ranges[range]; + config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); } void ObjectList::init_objects() diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 5b4fd4c49b..5e24b96590 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -276,7 +276,7 @@ public: void add_layer_item (const std::pair& range, const wxDataViewItem layers_item, const int layer_idx = -1); - void edit_layer_range(const std::pair& range); + void edit_layer_range(const std::pair& range, coordf_t layer_height); void init_objects(); bool multiple_selection() const ; From 213635f5596193008a96e1f910b8e2da507e9f9d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 4 Jun 2019 17:30:44 +0200 Subject: [PATCH 039/627] Implemented range's min/max Z editing --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 46 +++++++++++++++++------------ src/slic3r/GUI/GUI_ObjectLayers.hpp | 4 +-- src/slic3r/GUI/GUI_ObjectList.cpp | 28 ++++++++++++++++++ src/slic3r/GUI/GUI_ObjectList.hpp | 2 ++ 4 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index bba39f76a2..de1c95fc90 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -43,26 +43,31 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : wxSizer* ObjectLayers::create_layer_without_buttons(const t_layer_config_ranges::value_type& layer) { - auto size = wxSize(field_width * em_unit(m_parent), wxDefaultCoord); - // Add control for the "Min Z" - auto temp = new LayerRangeEditor(m_parent, double_to_string(layer.first.first), size); + auto temp = new LayerRangeEditor(m_parent, double_to_string(layer.first.first), + [layer](coordf_t min_z) { + wxGetApp().obj_list()->edit_layer_range(layer.first, { min_z, layer.first.second }); + }); + m_grid_sizer->Add(temp); // Add control for the "Max Z" - temp = new LayerRangeEditor(m_parent, double_to_string(layer.first.second), size); + temp = new LayerRangeEditor(m_parent, double_to_string(layer.first.second), + [layer](coordf_t max_z) { + wxGetApp().obj_list()->edit_layer_range(layer.first, { layer.first.first, max_z }); + }); + m_grid_sizer->Add(temp); // Add control for the "Layer height" - auto sizer = new wxBoxSizer(wxHORIZONTAL); - - const wxString text_value = double_to_string(layer.second.option("layer_height")->getFloat()); - - temp = new LayerRangeEditor(m_parent, text_value, size, [temp, layer](coordf_t layer_height) { + temp = new LayerRangeEditor(m_parent, + double_to_string(layer.second.option("layer_height")->getFloat()), + [layer](coordf_t layer_height) { wxGetApp().obj_list()->edit_layer_range(layer.first, layer_height); - } ); - sizer->Add(temp); + }, false ); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(temp); m_grid_sizer->Add(sizer); return sizer; @@ -164,25 +169,28 @@ void ObjectLayers::msw_rescale() LayerRangeEditor::LayerRangeEditor( wxWindow* parent, const wxString& value, - const wxSize& size, - std::function edit_fn + std::function edit_fn, + const bool deletable_after_change ) : - wxTextCtrl(parent, wxID_ANY, value, wxDefaultPosition, size, wxTE_PROCESS_ENTER) + wxTextCtrl(parent, wxID_ANY, value, wxDefaultPosition, + wxSize(field_width * em_unit(parent), wxDefaultCoord), wxTE_PROCESS_ENTER) { this->SetFont(wxGetApp().normal_font()); this->Bind(wxEVT_TEXT_ENTER, ([this, edit_fn](wxEvent& e) { - edit_fn(get_value()); m_enter_pressed = true; + edit_fn(get_value()); }), this->GetId()); - this->Bind(wxEVT_KILL_FOCUS, ([this, edit_fn](wxEvent& e) + this->Bind(wxEVT_KILL_FOCUS, ([this, edit_fn, deletable_after_change](wxEvent& e) { - e.Skip(); - if (!m_enter_pressed) + if (!deletable_after_change) + e.Skip(); + if (!m_enter_pressed) { + m_enter_pressed = false; edit_fn(get_value()); - m_enter_pressed = false; + } }), this->GetId()); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 3aa3cb967c..f34dd13141 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -18,8 +18,8 @@ class LayerRangeEditor : public wxTextCtrl public: LayerRangeEditor( wxWindow* parent, const wxString& value = wxEmptyString, - const wxSize& size = wxDefaultSize, - std::function edit_fn = [](coordf_t) {; } + std::function edit_fn = [](coordf_t) {}, + const bool deletable_after_change = true ); ~LayerRangeEditor() {} diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 25df5c4fbd..96d071d54b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2265,6 +2265,9 @@ void ObjectList::add_layer_range(const t_layer_height_range& range) ++it; } + if (selected_range->first.second > next_range->first.first) + return; // range devision has no mean + if (selected_range->first.second == next_range->first.first) { const coordf_t delta = (next_range->first.second - next_range->first.first); @@ -2323,6 +2326,31 @@ void ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t la config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); } +void ObjectList::edit_layer_range(const t_layer_height_range& range, const t_layer_height_range& new_range) +{ + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + const DynamicPrintConfig config = ranges[range]; + + ranges.erase(range); + ranges[new_range] = config; + + wxDataViewItem root_item = m_objects_model->GetLayerRootItem(m_objects_model->GetItemById(obj_idx)); + m_objects_model->DeleteChildren(root_item); + + if (root_item.IsOk()) + // create Layer item(s) according to the layer_config_ranges + for (const auto r : ranges) + add_layer_item(r.first, root_item); + + // To update(recreate) layers sizer call select_item for LayerRoot item expand + select_item(root_item); + Expand(root_item); +} + void ObjectList::init_objects() { m_objects = wxGetApp().model_objects(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 5e24b96590..c970a23a00 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -277,6 +277,8 @@ public: const wxDataViewItem layers_item, const int layer_idx = -1); void edit_layer_range(const std::pair& range, coordf_t layer_height); + void edit_layer_range(const std::pair& range, + const std::pair& new_range); void init_objects(); bool multiple_selection() const ; From 3ab886b747c4742d1662a415d683857003c0388b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 4 Jun 2019 18:25:53 +0200 Subject: [PATCH 040/627] Fix of mesh decimation (the admesh library). Fixes "Unable to save project (#2445)" --- src/admesh/connect.cpp | 474 ++++++++++++++++----------------- src/admesh/shared.cpp | 417 ++++++++++++++--------------- src/admesh/stl.h | 2 + src/admesh/util.cpp | 47 ++++ src/libslic3r/TriangleMesh.cpp | 36 ++- 5 files changed, 495 insertions(+), 481 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index fb32132194..3069251d3a 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -37,7 +37,6 @@ static void stl_match_neighbors_nearby(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_record_neighbors(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b); -static void stl_initialize_facet_check_exact(stl_file *stl); static void stl_initialize_facet_check_nearby(stl_file *stl); static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, const stl_vertex *a, const stl_vertex *b); static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, @@ -47,63 +46,90 @@ static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, stl_hash_edge *edge_a, stl_hash_edge *edge_b)); static int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b); static void stl_free_edges(stl_file *stl); -static void stl_remove_facet(stl_file *stl, int facet_number); static void stl_change_vertices(stl_file *stl, int facet_num, int vnot, stl_vertex new_vertex); static void stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b, int *facet1, int *vertex1, int *facet2, int *vertex2, stl_vertex *new_vertex1, stl_vertex *new_vertex2); -static void stl_remove_degenerate(stl_file *stl, int facet); extern int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); -static void stl_update_connects_remove_1(stl_file *stl, int facet_num); + +static inline size_t hash_size_from_nr_faces(const size_t nr_faces) +{ + // Good primes for addressing a cca. 30 bit space. + // https://planetmath.org/goodhashtableprimes + static std::vector primes{ 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 }; + // Find a prime number for 50% filling of the shared triangle edges in the mesh. + auto it = std::upper_bound(primes.begin(), primes.end(), nr_faces * 3 * 2 - 1); + return (it == primes.end()) ? primes.back() : *it; +} // This function builds the neighbors list. No modifications are made // to any of the facets. The edges are said to match only if all six // floats of the first edge matches all six floats of the second edge. void stl_check_facets_exact(stl_file *stl) { - if (stl->error) - return; + if (stl->error) + return; - stl->stats.connected_edges = 0; - stl->stats.connected_facets_1_edge = 0; - stl->stats.connected_facets_2_edge = 0; - stl->stats.connected_facets_3_edge = 0; + stl->stats.connected_edges = 0; + stl->stats.connected_facets_1_edge = 0; + stl->stats.connected_facets_2_edge = 0; + stl->stats.connected_facets_3_edge = 0; - // If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. - // Do it before the next step, as the next step stores references to the face indices in the hash tables and removing a facet - // will break the references. - for (int i = 0; i < stl->stats.number_of_facets;) { - stl_facet &facet = stl->facet_start[i]; - if (facet.vertex[0] == facet.vertex[1] || facet.vertex[1] == facet.vertex[2] || facet.vertex[0] == facet.vertex[2]) { - // Remove the degenerate facet. - facet = stl->facet_start[--stl->stats.number_of_facets]; - stl->stats.facets_removed += 1; - stl->stats.degenerate_facets += 1; - } else - ++ i; - } + // If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. + // Do it before the next step, as the next step stores references to the face indices in the hash tables and removing a facet + // will break the references. + for (uint32_t i = 0; i < stl->stats.number_of_facets;) { + stl_facet &facet = stl->facet_start[i]; + if (facet.vertex[0] == facet.vertex[1] || facet.vertex[1] == facet.vertex[2] || facet.vertex[0] == facet.vertex[2]) { + // Remove the degenerate facet. + facet = stl->facet_start[--stl->stats.number_of_facets]; + stl->stats.facets_removed += 1; + stl->stats.degenerate_facets += 1; + } else + ++ i; + } - // Connect neighbor edges. - stl_initialize_facet_check_exact(stl); - for (int i = 0; i < stl->stats.number_of_facets; i++) { - const stl_facet &facet = stl->facet_start[i]; - for (int j = 0; j < 3; j++) { - stl_hash_edge edge; - edge.facet_number = i; - edge.which_edge = j; - stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); - insert_hash_edge(stl, edge, stl_record_neighbors); - } - } - stl_free_edges(stl); + // Initialize hash table. + stl->stats.malloced = 0; + stl->stats.freed = 0; + stl->stats.collisions = 0; + stl->M = (int)hash_size_from_nr_faces(stl->stats.number_of_facets); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + // initialize neighbors list to -1 to mark unconnected edges + stl->neighbors_start[i].neighbor[0] = -1; + stl->neighbors_start[i].neighbor[1] = -1; + stl->neighbors_start[i].neighbor[2] = -1; + } + stl->heads = (stl_hash_edge**)calloc(stl->M, sizeof(*stl->heads)); + if (stl->heads == NULL) + perror("stl_initialize_facet_check_exact"); + stl->tail = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); + if (stl->tail == NULL) + perror("stl_initialize_facet_check_exact"); + stl->tail->next = stl->tail; + for (int i = 0; i < stl->M; ++ i) + stl->heads[i] = stl->tail; + + // Connect neighbor edges. + for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { + const stl_facet &facet = stl->facet_start[i]; + for (int j = 0; j < 3; ++ j) { + stl_hash_edge edge; + edge.facet_number = i; + edge.which_edge = j; + stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); + insert_hash_edge(stl, edge, stl_record_neighbors); + } + } + stl_free_edges(stl); #if 0 - printf("Number of faces: %d, number of manifold edges: %d, number of connected edges: %d, number of unconnected edges: %d\r\n", - stl->stats.number_of_facets, stl->stats.number_of_facets * 3, - stl->stats.connected_edges, stl->stats.number_of_facets * 3 - stl->stats.connected_edges); + printf("Number of faces: %d, number of manifold edges: %d, number of connected edges: %d, number of unconnected edges: %d\r\n", + stl->stats.number_of_facets, stl->stats.number_of_facets * 3, + stl->stats.connected_edges, stl->stats.number_of_facets * 3 - stl->stats.connected_edges); #endif } @@ -141,48 +167,6 @@ static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, const stl_ve } } -static inline size_t hash_size_from_nr_faces(const size_t nr_faces) -{ - // Good primes for addressing a cca. 30 bit space. - // https://planetmath.org/goodhashtableprimes - static std::vector primes{ 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 }; - // Find a prime number for 50% filling of the shared triangle edges in the mesh. - auto it = std::upper_bound(primes.begin(), primes.end(), nr_faces * 3 * 2 - 1); - return (it == primes.end()) ? primes.back() : *it; -} - -static void -stl_initialize_facet_check_exact(stl_file *stl) { - int i; - - if (stl->error) return; - - stl->stats.malloced = 0; - stl->stats.freed = 0; - stl->stats.collisions = 0; - - stl->M = hash_size_from_nr_faces(stl->stats.number_of_facets); - - for (i = 0; i < stl->stats.number_of_facets ; i++) { - /* initialize neighbors list to -1 to mark unconnected edges */ - stl->neighbors_start[i].neighbor[0] = -1; - stl->neighbors_start[i].neighbor[1] = -1; - stl->neighbors_start[i].neighbor[2] = -1; - } - - stl->heads = (stl_hash_edge**)calloc(stl->M, sizeof(*stl->heads)); - if(stl->heads == NULL) perror("stl_initialize_facet_check_exact"); - - stl->tail = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); - if(stl->tail == NULL) perror("stl_initialize_facet_check_exact"); - - stl->tail->next = stl->tail; - - for(i = 0; i < stl->M; i++) { - stl->heads[i] = stl->tail; - } -} - static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, void (*match_neighbors)(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b)) @@ -264,7 +248,7 @@ void stl_check_facets_nearby(stl_file *stl, float tolerance) stl_initialize_facet_check_nearby(stl); - for (int i = 0; i < stl->stats.number_of_facets; ++ i) { + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { //FIXME is the copy necessary? stl_facet facet = stl->facet_start[i]; for (int j = 0; j < 3; j++) { @@ -348,7 +332,7 @@ static void stl_initialize_facet_check_nearby(stl_file *stl) /* tolerance = STL_MAX((stl->stats.bounding_diameter / 500000.0), tolerance);*/ /* tolerance *= 0.5;*/ - stl->M = hash_size_from_nr_faces(stl->stats.number_of_facets); + stl->M = (int)hash_size_from_nr_faces(stl->stats.number_of_facets); stl->heads = (stl_hash_edge**)calloc(stl->M, sizeof(*stl->heads)); if(stl->heads == NULL) perror("stl_initialize_facet_check_nearby"); @@ -611,181 +595,170 @@ stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, } } -static void -stl_remove_facet(stl_file *stl, int facet_number) { - int neighbor[3]; - int vnot[3]; - int i; - int j; +static void remove_facet(stl_file *stl, int facet_number) +{ + assert(! stl->error); + ++ stl->stats.facets_removed; + /* Update list of connected edges */ + stl_neighbors &neighbors = stl->neighbors_start[facet_number]; + // Update statistics on unconnected triangle edges. + switch ((neighbors.neighbor[0] == -1) + (neighbors.neighbor[1] == -1) + (neighbors.neighbor[2] == -1)) { + case 0: // Facet has 3 neighbors + -- stl->stats.connected_facets_3_edge; + -- stl->stats.connected_facets_2_edge; + -- stl->stats.connected_facets_1_edge; + break; + case 1: // Facet has 2 neighbors + -- stl->stats.connected_facets_2_edge; + -- stl->stats.connected_facets_1_edge; + break; + case 2: // Facet has 1 neighbor + -- stl->stats.connected_facets_1_edge; + case 3: // Facet has 0 neighbors + break; + default: + assert(false); + } - if (stl->error) return; + if (facet_number == -- stl->stats.number_of_facets) + // Removing the last face is easy, just forget the last face. + return; - stl->stats.facets_removed += 1; - /* Update list of connected edges */ - j = ((stl->neighbors_start[facet_number].neighbor[0] == -1) + - (stl->neighbors_start[facet_number].neighbor[1] == -1) + - (stl->neighbors_start[facet_number].neighbor[2] == -1)); - if(j == 2) { - stl->stats.connected_facets_1_edge -= 1; - } else if(j == 1) { - stl->stats.connected_facets_2_edge -= 1; - stl->stats.connected_facets_1_edge -= 1; - } else if(j == 0) { - stl->stats.connected_facets_3_edge -= 1; - stl->stats.connected_facets_2_edge -= 1; - stl->stats.connected_facets_1_edge -= 1; - } + // Copy the face and neighborship from the last face to facet_number. + stl->facet_start[facet_number] = stl->facet_start[stl->stats.number_of_facets]; + neighbors = stl->neighbors_start[stl->stats.number_of_facets]; + // Update neighborship of faces, which used to point to the last face, now moved to facet_number. + for (int i = 0; i < 3; ++ i) + if (neighbors.neighbor[i] != -1) { + int &other_face_idx = stl->neighbors_start[neighbors.neighbor[i]].neighbor[(neighbors.which_vertex_not[i] + 1) % 3]; + if (other_face_idx != stl->stats.number_of_facets) { + printf("in remove_facet: neighbor = %d numfacets = %d this is wrong\n", other_face_idx, stl->stats.number_of_facets); + return; + } + other_face_idx = facet_number; + } +} - stl->facet_start[facet_number] = - stl->facet_start[stl->stats.number_of_facets - 1]; - /* I could reallocate at this point, but it is not really necessary. */ - stl->neighbors_start[facet_number] = - stl->neighbors_start[stl->stats.number_of_facets - 1]; - stl->stats.number_of_facets -= 1; +static void remove_degenerate(stl_file *stl, int facet) +{ + assert(! stl->error); - for(i = 0; i < 3; i++) { - neighbor[i] = stl->neighbors_start[facet_number].neighbor[i]; - vnot[i] = stl->neighbors_start[facet_number].which_vertex_not[i]; - } + // Update statistics on face connectivity. + auto stl_update_connects_remove_1 = [stl](int facet_num) { + assert(! stl->error); + //FIXME when decreasing 3_edge, should I increase 2_edge etc? + switch ((stl->neighbors_start[facet_num].neighbor[0] == -1) + (stl->neighbors_start[facet_num].neighbor[1] == -1) + (stl->neighbors_start[facet_num].neighbor[2] == -1)) { + case 0: // Facet has 3 neighbors + -- stl->stats.connected_facets_3_edge; break; + case 1: // Facet has 2 neighbors + -- stl->stats.connected_facets_2_edge; break; + case 2: // Facet has 1 neighbor + -- stl->stats.connected_facets_1_edge; break; + case 3: // Facet has 0 neighbors + break; + default: + assert(false); + } + }; - for(i = 0; i < 3; i++) { - if(neighbor[i] != -1) { - if(stl->neighbors_start[neighbor[i]].neighbor[(vnot[i] + 1)% 3] != - stl->stats.number_of_facets) { - printf("\ -in stl_remove_facet: neighbor = %d numfacets = %d this is wrong\n", - stl->neighbors_start[neighbor[i]].neighbor[(vnot[i] + 1)% 3], - stl->stats.number_of_facets); - return; - } - stl->neighbors_start[neighbor[i]].neighbor[(vnot[i] + 1)% 3] - = facet_number; - } - } + int edge_to_collapse = 0; + if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1]) { + if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { + // All 3 vertices are equal. Collapse the edge with no neighbor if it exists. + const int *nbr = stl->neighbors_start[facet].neighbor; + edge_to_collapse = (nbr[0] == -1) ? 0 : (nbr[1] == -1) ? 1 : 2; + } else { + edge_to_collapse = 0; + } + } else if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { + edge_to_collapse = 1; + } else if (stl->facet_start[facet].vertex[2] == stl->facet_start[facet].vertex[0]) { + edge_to_collapse = 2; + } else { + // No degenerate. Function shouldn't have been called. + return; + } + + int edge[3] = { (edge_to_collapse + 1) % 3, (edge_to_collapse + 2) % 3, edge_to_collapse }; + int neighbor[] = { + stl->neighbors_start[facet].neighbor[edge[0]], + stl->neighbors_start[facet].neighbor[edge[1]], + stl->neighbors_start[facet].neighbor[edge[2]] + }; + int vnot[] = { + stl->neighbors_start[facet].which_vertex_not[edge[0]], + stl->neighbors_start[facet].which_vertex_not[edge[1]], + stl->neighbors_start[facet].which_vertex_not[edge[2]] + }; + // Update statistics on edge connectivity. + if (neighbor[0] == -1) + stl_update_connects_remove_1(neighbor[1]); + if (neighbor[1] == -1) + stl_update_connects_remove_1(neighbor[0]); + + if (neighbor[0] >= 0) { + if (neighbor[1] >= 0) { + // Adjust the "flip" flag for the which_vertex_not values. + if (vnot[0] > 2) { + if (vnot[1] > 2) { + // The face to be removed has its normal flipped compared to the left & right neighbors, therefore after removing this face + // the two remaining neighbors will be oriented correctly. + vnot[0] -= 3; + vnot[1] -= 3; + } else + // One neighbor has its normal inverted compared to the face to be removed, the other is oriented equally. + // After removal, the two neighbors will have their normals flipped. + vnot[1] += 3; + } else if (vnot[1] > 2) + // One neighbor has its normal inverted compared to the face to be removed, the other is oriented equally. + // After removal, the two neighbors will have their normals flipped. + vnot[0] += 3; + } + stl->neighbors_start[neighbor[0]].neighbor[(vnot[0] + 1) % 3] = (neighbor[0] == neighbor[1]) ? -1 : neighbor[1]; + stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = vnot[1]; + } + if (neighbor[1] >= 0) { + stl->neighbors_start[neighbor[1]].neighbor[(vnot[1] + 1) % 3] = (neighbor[0] == neighbor[1]) ? -1 : neighbor[0]; + stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = vnot[0]; + } + if (neighbor[2] >= 0) { + stl_update_connects_remove_1(neighbor[2]); + stl->neighbors_start[neighbor[2]].neighbor[(vnot[2] + 1) % 3] = -1; + } + + remove_facet(stl, facet); } void stl_remove_unconnected_facets(stl_file *stl) { - /* A couple of things need to be done here. One is to remove any */ - /* completely unconnected facets (0 edges connected) since these are */ - /* useless and could be completely wrong. The second thing that needs to */ - /* be done is to remove any degenerate facets that were created during */ - /* stl_check_facets_nearby(). */ - if (stl->error) - return; + // A couple of things need to be done here. One is to remove any completely unconnected facets (0 edges connected) since these are + // useless and could be completely wrong. The second thing that needs to be done is to remove any degenerate facets that were created during + // stl_check_facets_nearby(). + if (stl->error) + return; - // remove degenerate facets - for (int i = 0; i < stl->stats.number_of_facets; ++ i) { - if(stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[1] || - stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[2] || - stl->facet_start[i].vertex[1] == stl->facet_start[i].vertex[2]) { - stl_remove_degenerate(stl, i); - i--; - } - } + // remove degenerate facets + for (uint32_t i = 0; i < stl->stats.number_of_facets;) + if (stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[1] || + stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[2] || + stl->facet_start[i].vertex[1] == stl->facet_start[i].vertex[2]) { + remove_degenerate(stl, i); +// assert(stl_validate(stl)); + } else + ++ i; - if(stl->stats.connected_facets_1_edge < stl->stats.number_of_facets) { - // remove completely unconnected facets - for (int i = 0; i < stl->stats.number_of_facets; i++) { - if (stl->neighbors_start[i].neighbor[0] == -1 && - stl->neighbors_start[i].neighbor[1] == -1 && - stl->neighbors_start[i].neighbor[2] == -1) { - // This facet is completely unconnected. Remove it. - stl_remove_facet(stl, i); - -- i; - } - } - } -} - -static void -stl_remove_degenerate(stl_file *stl, int facet) { - int edge1; - int edge2; - int edge3; - int neighbor1; - int neighbor2; - int neighbor3; - int vnot1; - int vnot2; - int vnot3; - - if (stl->error) return; - - if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1] && - stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { - /* all 3 vertices are equal. Just remove the facet. I don't think*/ - /* this is really possible, but just in case... */ - printf("removing a facet in stl_remove_degenerate\n"); - stl_remove_facet(stl, facet); - return; - } - - if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1]) { - edge1 = 1; - edge2 = 2; - edge3 = 0; - } else if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { - edge1 = 0; - edge2 = 2; - edge3 = 1; - } else if (stl->facet_start[facet].vertex[2] == stl->facet_start[facet].vertex[0]) { - edge1 = 0; - edge2 = 1; - edge3 = 2; - } else { - /* No degenerate. Function shouldn't have been called. */ - return; - } - neighbor1 = stl->neighbors_start[facet].neighbor[edge1]; - neighbor2 = stl->neighbors_start[facet].neighbor[edge2]; - - if(neighbor1 == -1) { - stl_update_connects_remove_1(stl, neighbor2); - } - if(neighbor2 == -1) { - stl_update_connects_remove_1(stl, neighbor1); - } - - - neighbor3 = stl->neighbors_start[facet].neighbor[edge3]; - vnot1 = stl->neighbors_start[facet].which_vertex_not[edge1]; - vnot2 = stl->neighbors_start[facet].which_vertex_not[edge2]; - vnot3 = stl->neighbors_start[facet].which_vertex_not[edge3]; - - if(neighbor1 >= 0){ - stl->neighbors_start[neighbor1].neighbor[(vnot1 + 1) % 3] = neighbor2; - stl->neighbors_start[neighbor1].which_vertex_not[(vnot1 + 1) % 3] = vnot2; - } - if(neighbor2 >= 0){ - stl->neighbors_start[neighbor2].neighbor[(vnot2 + 1) % 3] = neighbor1; - stl->neighbors_start[neighbor2].which_vertex_not[(vnot2 + 1) % 3] = vnot1; - } - - stl_remove_facet(stl, facet); - - if(neighbor3 >= 0) { - stl_update_connects_remove_1(stl, neighbor3); - stl->neighbors_start[neighbor3].neighbor[(vnot3 + 1) % 3] = -1; - } -} - -void -stl_update_connects_remove_1(stl_file *stl, int facet_num) { - int j; - - if (stl->error) return; - /* Update list of connected edges */ - j = ((stl->neighbors_start[facet_num].neighbor[0] == -1) + - (stl->neighbors_start[facet_num].neighbor[1] == -1) + - (stl->neighbors_start[facet_num].neighbor[2] == -1)); - if(j == 0) { /* Facet has 3 neighbors */ - stl->stats.connected_facets_3_edge -= 1; - } else if(j == 1) { /* Facet has 2 neighbors */ - stl->stats.connected_facets_2_edge -= 1; - } else if(j == 2) { /* Facet has 1 neighbor */ - stl->stats.connected_facets_1_edge -= 1; - } + if (stl->stats.connected_facets_1_edge < (int)stl->stats.number_of_facets) { + // remove completely unconnected facets + for (uint32_t i = 0; i < stl->stats.number_of_facets;) + if (stl->neighbors_start[i].neighbor[0] == -1 && + stl->neighbors_start[i].neighbor[1] == -1 && + stl->neighbors_start[i].neighbor[2] == -1) { + // This facet is completely unconnected. Remove it. + remove_facet(stl, i); + assert(stl_validate(stl)); + } else + ++ i; + } } void @@ -801,7 +774,6 @@ stl_fill_holes(stl_file *stl) { int next_edge; int pivot_vertex; int next_facet; - int i; int j; int k; @@ -809,7 +781,7 @@ stl_fill_holes(stl_file *stl) { /* Insert all unconnected edges into hash list */ stl_initialize_facet_check_nearby(stl); - for(i = 0; i < stl->stats.number_of_facets; i++) { + for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { facet = stl->facet_start[i]; for(j = 0; j < 3; j++) { if(stl->neighbors_start[i].neighbor[j] != -1) continue; @@ -822,7 +794,7 @@ stl_fill_holes(stl_file *stl) { } } - for(i = 0; i < stl->stats.number_of_facets; i++) { + for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { facet = stl->facet_start[i]; neighbors_initial[0] = stl->neighbors_start[i].neighbor[0]; neighbors_initial[1] = stl->neighbors_start[i].neighbor[1]; @@ -900,7 +872,7 @@ stl_add_facet(stl_file *stl, stl_facet *new_facet) { if (stl->error) return; stl->stats.facets_added += 1; - if(stl->stats.facets_malloced < stl->stats.number_of_facets + 1) { + if(stl->stats.facets_malloced < (int)stl->stats.number_of_facets + 1) { stl->facet_start = (stl_facet*)realloc(stl->facet_start, (sizeof(stl_facet) * (stl->stats.facets_malloced + 256))); if(stl->facet_start == NULL) perror("stl_add_facet"); diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index c8c17ccd59..2ad2709031 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -23,242 +23,239 @@ #include #include +#include + #include #include "stl.h" -void -stl_invalidate_shared_vertices(stl_file *stl) { - if (stl->error) return; +void stl_invalidate_shared_vertices(stl_file *stl) +{ + if (stl->error) + return; - if (stl->v_indices != NULL) { - free(stl->v_indices); - stl->v_indices = NULL; - } - if (stl->v_shared != NULL) { - free(stl->v_shared); - stl->v_shared = NULL; - } + if (stl->v_indices != nullptr) { + free(stl->v_indices); + stl->v_indices = nullptr; + } + if (stl->v_shared != nullptr) { + free(stl->v_shared); + stl->v_shared = nullptr; + } } -void -stl_generate_shared_vertices(stl_file *stl) { - int i; - int j; - int first_facet; - int direction; - int facet_num; - int vnot; - int next_edge; - int pivot_vertex; - int next_facet; - int reversed; +void stl_generate_shared_vertices(stl_file *stl) +{ + if (stl->error) + return; - if (stl->error) return; + /* make sure this function is idempotent and does not leak memory */ + stl_invalidate_shared_vertices(stl); - /* make sure this function is idempotent and does not leak memory */ - stl_invalidate_shared_vertices(stl); + // 3 indices to vertex per face + stl->v_indices = (v_indices_struct*)calloc(stl->stats.number_of_facets, sizeof(v_indices_struct)); + if (stl->v_indices == nullptr) + perror("stl_generate_shared_vertices"); + // Shared vertices (3D coordinates) + stl->v_shared = (stl_vertex*)calloc((stl->stats.number_of_facets / 2), sizeof(stl_vertex)); + if (stl->v_shared == nullptr) + perror("stl_generate_shared_vertices"); + stl->stats.shared_malloced = stl->stats.number_of_facets / 2; + stl->stats.shared_vertices = 0; - stl->v_indices = (v_indices_struct*) - calloc(stl->stats.number_of_facets, sizeof(v_indices_struct)); - if(stl->v_indices == NULL) perror("stl_generate_shared_vertices"); - stl->v_shared = (stl_vertex*) - calloc((stl->stats.number_of_facets / 2), sizeof(stl_vertex)); - if(stl->v_shared == NULL) perror("stl_generate_shared_vertices"); - stl->stats.shared_malloced = stl->stats.number_of_facets / 2; - stl->stats.shared_vertices = 0; + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + // vertex index -1 means no shared vertex was assigned yet. + stl->v_indices[i].vertex[0] = -1; + stl->v_indices[i].vertex[1] = -1; + stl->v_indices[i].vertex[2] = -1; + } - for(i = 0; i < stl->stats.number_of_facets; i++) { - stl->v_indices[i].vertex[0] = -1; - stl->v_indices[i].vertex[1] = -1; - stl->v_indices[i].vertex[2] = -1; - } + // A degenerate mesh may contain loops: Traversing a fan will end up in an endless loop + // while never reaching the starting face. To avoid these endless loops, traversed faces at each fan traversal + // are marked with a unique fan_traversal_stamp. + unsigned int fan_traversal_stamp = 0; + std::vector fan_traversal_facet_visited(stl->stats.number_of_facets, 0); + for (uint32_t facet_idx = 0; facet_idx < stl->stats.number_of_facets; ++ facet_idx) { + for (int j = 0; j < 3; ++ j) { + if (stl->v_indices[facet_idx].vertex[j] != -1) + // Shared vertex was already assigned. + continue; + // Create a new shared vertex. + if (stl->stats.shared_vertices == stl->stats.shared_malloced) { + stl->stats.shared_malloced += 1024; + stl->v_shared = (stl_vertex*)realloc(stl->v_shared, stl->stats.shared_malloced * sizeof(stl_vertex)); + if(stl->v_shared == nullptr) + perror("stl_generate_shared_vertices"); + } + stl->v_shared[stl->stats.shared_vertices] = stl->facet_start[facet_idx].vertex[j]; + // Traverse the fan around the j-th vertex of the i-th face, assign the newly created shared vertex index to all the neighboring triangles in the triangle fan. + int facet_in_fan_idx = facet_idx; + bool edge_direction = false; + bool traversal_reversed = false; + int vnot = (j + 2) % 3; + // Increase the + ++ fan_traversal_stamp; + for (;;) { + // Next edge on facet_in_fan_idx to be traversed. The edge is indexed by its starting vertex index. + int next_edge = 0; + // Vertex index in facet_in_fan_idx, which is being pivoted around, and which is being assigned a new shared vertex. + int pivot_vertex = 0; + if (vnot > 2) { + // The edge of facet_in_fan_idx opposite to vnot is equally oriented, therefore + // the neighboring facet is flipped. + if (! edge_direction) { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } else { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot % 3; + } + edge_direction = ! edge_direction; + } else { + // The neighboring facet is correctly oriented. + if (! edge_direction) { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot; + } else { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } + } + stl->v_indices[facet_in_fan_idx].vertex[pivot_vertex] = stl->stats.shared_vertices; + fan_traversal_facet_visited[facet_in_fan_idx] = fan_traversal_stamp; - for(i = 0; i < stl->stats.number_of_facets; i++) { - first_facet = i; - for(j = 0; j < 3; j++) { - if(stl->v_indices[i].vertex[j] != -1) { - continue; - } - if(stl->stats.shared_vertices == stl->stats.shared_malloced) { - stl->stats.shared_malloced += 1024; - stl->v_shared = (stl_vertex*)realloc(stl->v_shared, - stl->stats.shared_malloced * sizeof(stl_vertex)); - if(stl->v_shared == NULL) perror("stl_generate_shared_vertices"); - } + // next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge! + int next_facet = stl->neighbors_start[facet_in_fan_idx].neighbor[next_edge]; + if (next_facet == -1) { + // No neighbor going in the current direction. + if (traversal_reversed) { + // Went to one limit, then turned back and reached the other limit. Quit the fan traversal. + break; + } else { + // Reached the first limit. Now try to reverse and traverse up to the other limit. + edge_direction = true; + vnot = (j + 1) % 3; + traversal_reversed = true; + facet_in_fan_idx = facet_idx; + } + } else if (next_facet == facet_idx) { + // Traversed a closed fan all around. +// assert(! traversal_reversed); + break; + } else if (next_facet >= (int)stl->stats.number_of_facets) { + // The mesh is not valid! + // assert(false); + break; + } else if (fan_traversal_facet_visited[next_facet] == fan_traversal_stamp) { + // Traversed a closed fan all around, but did not reach the starting face. + // This indicates an invalid geometry (non-manifold). + //assert(false); + break; + } else { + // Continue traversal. + // next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge! + vnot = stl->neighbors_start[facet_in_fan_idx].which_vertex_not[next_edge]; + facet_in_fan_idx = next_facet; + } + } - stl->v_shared[stl->stats.shared_vertices] = - stl->facet_start[i].vertex[j]; - - direction = 0; - reversed = 0; - facet_num = i; - vnot = (j + 2) % 3; - - for(;;) { - if(vnot > 2) { - if(direction == 0) { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - direction = 1; - } else { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot % 3; - direction = 0; - } - } else { - if(direction == 0) { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot; - } else { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - } - } - stl->v_indices[facet_num].vertex[pivot_vertex] = - stl->stats.shared_vertices; - - next_facet = stl->neighbors_start[facet_num].neighbor[next_edge]; - if(next_facet == -1) { - if(reversed) { - break; - } else { - direction = 1; - vnot = (j + 1) % 3; - reversed = 1; - facet_num = first_facet; - } - } else if(next_facet != first_facet) { - vnot = stl->neighbors_start[facet_num]. - which_vertex_not[next_edge]; - facet_num = next_facet; - } else { - break; - } - } - stl->stats.shared_vertices += 1; - } - } + ++ stl->stats.shared_vertices; + } + } } -void -stl_write_off(stl_file *stl, const char *file) { - int i; - FILE *fp; - char *error_msg; +void stl_write_off(stl_file *stl, const char *file) +{ + if (stl->error) + return; - if (stl->error) return; + /* Open the file */ + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == nullptr) { + char *error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ + sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); + perror(error_msg); + free(error_msg); + stl->error = 1; + return; + } - /* Open the file */ - fp = boost::nowide::fopen(file, "w"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } - - fprintf(fp, "OFF\n"); - fprintf(fp, "%d %d 0\n", - stl->stats.shared_vertices, stl->stats.number_of_facets); - - for(i = 0; i < stl->stats.shared_vertices; i++) { - fprintf(fp, "\t%f %f %f\n", - stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); - } - for(i = 0; i < stl->stats.number_of_facets; i++) { - fprintf(fp, "\t3 %d %d %d\n", stl->v_indices[i].vertex[0], - stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); - } - fclose(fp); + fprintf(fp, "OFF\n"); + fprintf(fp, "%d %d 0\n", stl->stats.shared_vertices, stl->stats.number_of_facets); + for (int i = 0; i < stl->stats.shared_vertices; ++ i) + fprintf(fp, "\t%f %f %f\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + fprintf(fp, "\t3 %d %d %d\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); + fclose(fp); } -void -stl_write_vrml(stl_file *stl, const char *file) { - int i; - FILE *fp; - char *error_msg; +void stl_write_vrml(stl_file *stl, const char *file) +{ + if (stl->error) + return; - if (stl->error) return; + /* Open the file */ + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == nullptr) { + char *error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ + sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); + perror(error_msg); + free(error_msg); + stl->error = 1; + return; + } - /* Open the file */ - fp = boost::nowide::fopen(file, "w"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } + fprintf(fp, "#VRML V1.0 ascii\n\n"); + fprintf(fp, "Separator {\n"); + fprintf(fp, "\tDEF STLShape ShapeHints {\n"); + fprintf(fp, "\t\tvertexOrdering COUNTERCLOCKWISE\n"); + fprintf(fp, "\t\tfaceType CONVEX\n"); + fprintf(fp, "\t\tshapeType SOLID\n"); + fprintf(fp, "\t\tcreaseAngle 0.0\n"); + fprintf(fp, "\t}\n"); + fprintf(fp, "\tDEF STLModel Separator {\n"); + fprintf(fp, "\t\tDEF STLColor Material {\n"); + fprintf(fp, "\t\t\temissiveColor 0.700000 0.700000 0.000000\n"); + fprintf(fp, "\t\t}\n"); + fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n"); + fprintf(fp, "\t\t\tpoint [\n"); - fprintf(fp, "#VRML V1.0 ascii\n\n"); - fprintf(fp, "Separator {\n"); - fprintf(fp, "\tDEF STLShape ShapeHints {\n"); - fprintf(fp, "\t\tvertexOrdering COUNTERCLOCKWISE\n"); - fprintf(fp, "\t\tfaceType CONVEX\n"); - fprintf(fp, "\t\tshapeType SOLID\n"); - fprintf(fp, "\t\tcreaseAngle 0.0\n"); - fprintf(fp, "\t}\n"); - fprintf(fp, "\tDEF STLModel Separator {\n"); - fprintf(fp, "\t\tDEF STLColor Material {\n"); - fprintf(fp, "\t\t\temissiveColor 0.700000 0.700000 0.000000\n"); - fprintf(fp, "\t\t}\n"); - fprintf(fp, "\t\tDEF STLVertices Coordinate3 {\n"); - fprintf(fp, "\t\t\tpoint [\n"); + int i = 0; + for (; i < (stl->stats.shared_vertices - 1); i++) + fprintf(fp, "\t\t\t\t%f %f %f,\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); + fprintf(fp, "\t\t\t\t%f %f %f]\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); + fprintf(fp, "\t\t}\n"); + fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n"); + fprintf(fp, "\t\t\tcoordIndex [\n"); - for(i = 0; i < (stl->stats.shared_vertices - 1); i++) { - fprintf(fp, "\t\t\t\t%f %f %f,\n", - stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); - } - fprintf(fp, "\t\t\t\t%f %f %f]\n", - stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); - fprintf(fp, "\t\t}\n"); - fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n"); - fprintf(fp, "\t\t\tcoordIndex [\n"); - - for(i = 0; i < (stl->stats.number_of_facets - 1); i++) { - fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", stl->v_indices[i].vertex[0], - stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); - } - fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", stl->v_indices[i].vertex[0], - stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); - fprintf(fp, "\t\t}\n"); - fprintf(fp, "\t}\n"); - fprintf(fp, "}\n"); - fclose(fp); + for (int i = 0; i + 1 < (int)stl->stats.number_of_facets; ++ i) + fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); + fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); + fprintf(fp, "\t\t}\n"); + fprintf(fp, "\t}\n"); + fprintf(fp, "}\n"); + fclose(fp); } -void stl_write_obj (stl_file *stl, const char *file) { - int i; - FILE* fp; +void stl_write_obj (stl_file *stl, const char *file) +{ + if (stl->error) + return; - if (stl->error) return; + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == nullptr) { + char* error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ + sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); + perror(error_msg); + free(error_msg); + stl->error = 1; + return; + } - /* Open the file */ - fp = boost::nowide::fopen(file, "w"); - if (fp == NULL) { - char* error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); - perror(error_msg); - free(error_msg); - stl->error = 1; - return; - } - - for (i = 0; i < stl->stats.shared_vertices; i++) { - fprintf(fp, "v %f %f %f\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); - } - for (i = 0; i < stl->stats.number_of_facets; i++) { - fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1); - } - - fclose(fp); + for (int i = 0; i < stl->stats.shared_vertices; ++ i) + fprintf(fp, "v %f %f %f\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1); + fclose(fp); } diff --git a/src/admesh/stl.h b/src/admesh/stl.h index f867e197bd..5ecd94bd11 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -277,5 +277,7 @@ extern void stl_add_facet(stl_file *stl, stl_facet *new_facet); extern void stl_clear_error(stl_file *stl); extern int stl_get_error(stl_file *stl); extern void stl_exit_on_error(stl_file *stl); +// Validate the mesh, assert on error. +extern bool stl_validate(stl_file *stl); #endif diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index 305a58e22b..c2d2c27269 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -457,3 +457,50 @@ All facets connected. No further nearby check necessary.\n"); stl_verify_neighbors(stl); } } + +// Check validity of the mesh, assert on error. +bool stl_validate(stl_file *stl) +{ + assert(! stl->error); + assert(stl->fp == nullptr); + assert(stl->facet_start != nullptr); + assert(stl->heads == nullptr); + assert(stl->tail == nullptr); + assert(stl->neighbors_start != nullptr); + assert((stl->v_indices == nullptr) == (stl->v_shared == nullptr)); + assert(stl->stats.number_of_facets > 0); + +#ifdef _DEBUG + // Verify validity of neighborship data. + for (int facet_idx = 0; facet_idx < (int)stl->stats.number_of_facets; ++ facet_idx) { + const stl_neighbors &nbr = stl->neighbors_start[facet_idx]; + const int *vertices = (stl->v_indices == nullptr) ? nullptr : stl->v_indices[facet_idx].vertex; + for (int nbr_idx = 0; nbr_idx < 3; ++ nbr_idx) { + int nbr_face = stl->neighbors_start[facet_idx].neighbor[nbr_idx]; + assert(nbr_face < (int)stl->stats.number_of_facets); + if (nbr_face != -1) { + int nbr_vnot = nbr.which_vertex_not[nbr_idx]; + assert(nbr_vnot >= 0 && nbr_vnot < 6); + // Neighbor of the neighbor is the original face. + assert(stl->neighbors_start[nbr_face].neighbor[(nbr_vnot + 1) % 3] == facet_idx); + int vnot_back = stl->neighbors_start[nbr_face].which_vertex_not[(nbr_vnot + 1) % 3]; + assert(vnot_back >= 0 && vnot_back < 6); + assert((nbr_vnot < 3) == (vnot_back < 3)); + assert(vnot_back % 3 == (nbr_idx + 2) % 3); + if (vertices != nullptr) { + // Has shared vertices. + if (nbr_vnot < 3) { + // Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are correctly oriented. + assert((stl->v_indices[nbr_face].vertex[(nbr_vnot + 1) % 3] == vertices[(nbr_idx + 1) % 3] && stl->v_indices[nbr_face].vertex[(nbr_vnot + 2) % 3] == vertices[nbr_idx])); + } else { + // Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are incorrectly oriented, one of them is flipped. + assert((stl->v_indices[nbr_face].vertex[(nbr_vnot + 2) % 3] == vertices[(nbr_idx + 1) % 3] && stl->v_indices[nbr_face].vertex[(nbr_vnot + 1) % 3] == vertices[nbr_idx])); + } + } + } + } + } +#endif /* _DEBUG */ + + return true; +} diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 4d35cabca2..b7c6c07a75 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -104,18 +104,21 @@ TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) void TriangleMesh::repair() { - if (this->repaired) return; + if (this->repaired) + return; // admesh fails when repairing empty meshes - if (this->stl.stats.number_of_facets == 0) return; + if (this->stl.stats.number_of_facets == 0) + return; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started"; - + // checking exact #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; #endif /* SLIC3R_TRACE_REPAIR */ stl_check_facets_exact(&stl); + assert(stl_validate(&this->stl)); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge); @@ -141,6 +144,7 @@ void TriangleMesh::repair() } } } + assert(stl_validate(&this->stl)); // remove_unconnected if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { @@ -148,6 +152,7 @@ void TriangleMesh::repair() BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets"; #endif /* SLIC3R_TRACE_REPAIR */ stl_remove_unconnected_facets(&stl); + assert(stl_validate(&this->stl)); } // fill_holes @@ -168,24 +173,28 @@ void TriangleMesh::repair() BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_directions"; #endif /* SLIC3R_TRACE_REPAIR */ stl_fix_normal_directions(&stl); + assert(stl_validate(&this->stl)); // normal_values #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_values"; #endif /* SLIC3R_TRACE_REPAIR */ stl_fix_normal_values(&stl); + assert(stl_validate(&this->stl)); // always calculate the volume and reverse all normals if volume is negative #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_calculate_volume"; #endif /* SLIC3R_TRACE_REPAIR */ stl_calculate_volume(&stl); + assert(stl_validate(&this->stl)); // neighbors #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_verify_neighbors"; #endif /* SLIC3R_TRACE_REPAIR */ stl_verify_neighbors(&stl); + assert(stl_validate(&this->stl)); this->repaired = true; @@ -594,27 +603,14 @@ TriangleMesh TriangleMesh::convex_hull_3d() const void TriangleMesh::require_shared_vertices() { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start"; - if (!this->repaired) + assert(stl_validate(&this->stl)); + if (! this->repaired) this->repair(); - if (this->stl.v_shared == NULL) { + if (this->stl.v_shared == nullptr) { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices"; stl_generate_shared_vertices(&(this->stl)); } -#ifdef _DEBUG - // Verify validity of neighborship data. - for (int facet_idx = 0; facet_idx < stl.stats.number_of_facets; ++facet_idx) { - const stl_neighbors &nbr = stl.neighbors_start[facet_idx]; - const int *vertices = stl.v_indices[facet_idx].vertex; - for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) { - int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx]; - if (nbr_face != -1) { - assert( - (stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3] && stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]) || - (stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[(nbr_idx + 1) % 3] && stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[nbr_idx])); - } - } - } -#endif /* _DEBUG */ + assert(stl_validate(&this->stl)); BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; } From 8da54139c463a09ec899a9df6bccae16802dccdf Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 4 Jun 2019 22:06:42 +0200 Subject: [PATCH 041/627] WIP: Admesh - replacement of C memory allocation with std vectors --- CMakeLists.txt | 2 +- src/admesh/connect.cpp | 401 ++++++++----------- src/admesh/normals.cpp | 26 +- src/admesh/shared.cpp | 37 +- src/admesh/stl.h | 187 +++++---- src/admesh/stl_io.cpp | 30 +- src/admesh/stlinit.cpp | 104 ++--- src/admesh/util.cpp | 104 +++-- src/libslic3r/Format/3mf.cpp | 2 +- src/libslic3r/Format/AMF.cpp | 2 +- src/libslic3r/Format/PRUS.cpp | 6 +- src/libslic3r/Model.cpp | 18 +- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 17 +- src/libslic3r/SlicingAdaptive.cpp | 4 +- src/libslic3r/TriangleMesh.cpp | 83 ++-- src/libslic3r/TriangleMesh.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 8 +- 17 files changed, 450 insertions(+), 585 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 719bdd04e7..3d7157d5ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,7 +60,7 @@ if (MSVC) # /bigobj (Increase Number of Sections in .Obj file) # error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm90' or greater # Generate symbols at every build target, even for the release. - add_compile_options(-bigobj -Zm316 /Zi) + add_compile_options(-bigobj -Zm520 /Zi) endif () # Display and check CMAKE_PREFIX_PATH diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index 3069251d3a..b99f93f3d8 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -97,18 +97,10 @@ void stl_check_facets_exact(stl_file *stl) stl->stats.freed = 0; stl->stats.collisions = 0; stl->M = (int)hash_size_from_nr_faces(stl->stats.number_of_facets); - for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { - // initialize neighbors list to -1 to mark unconnected edges - stl->neighbors_start[i].neighbor[0] = -1; - stl->neighbors_start[i].neighbor[1] = -1; - stl->neighbors_start[i].neighbor[2] = -1; - } - stl->heads = (stl_hash_edge**)calloc(stl->M, sizeof(*stl->heads)); - if (stl->heads == NULL) - perror("stl_initialize_facet_check_exact"); - stl->tail = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); - if (stl->tail == NULL) - perror("stl_initialize_facet_check_exact"); + for (auto &neighbor : stl->neighbors_start) + neighbor.reset(); + stl->heads.assign(stl->M, nullptr); + stl->tail = new stl_hash_edge; stl->tail->next = stl->tail; for (int i = 0; i < stl->M; ++ i) stl->heads[i] = stl->tail; @@ -180,7 +172,7 @@ static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, stl_hash_edge *temp; if(link == stl->tail) { /* This list doesn't have any edges currently in it. Add this one. */ - new_edge = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); + new_edge = new stl_hash_edge; if(new_edge == NULL) perror("insert_hash_edge"); stl->stats.malloced++; *new_edge = edge; @@ -192,7 +184,7 @@ static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, match_neighbors(stl, &edge, link); /* Delete the matched edge from the list. */ stl->heads[chain_number] = link->next; - free(link); + delete link; stl->stats.freed++; return; } else { @@ -200,7 +192,7 @@ static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, for(;;) { if(link->next == stl->tail) { /* This is the last item in the list. Insert a new edge. */ - new_edge = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); + new_edge = new stl_hash_edge; if(new_edge == NULL) perror("insert_hash_edge"); stl->stats.malloced++; *new_edge = edge; @@ -215,7 +207,7 @@ static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, /* Delete the matched edge from the list. */ temp = link->next; link->next = link->next->next; - free(temp); + delete temp; stl->stats.freed++; return; } else { @@ -307,48 +299,38 @@ static void stl_free_edges(stl_file *stl) for (int i = 0; i < stl->M; i++) { for (stl_hash_edge *temp = stl->heads[i]; stl->heads[i] != stl->tail; temp = stl->heads[i]) { stl->heads[i] = stl->heads[i]->next; - free(temp); + delete temp; ++ stl->stats.freed; } } } - free(stl->heads); - stl->heads = nullptr; - free(stl->tail); + stl->heads.clear(); + delete stl->tail; stl->tail = nullptr; } static void stl_initialize_facet_check_nearby(stl_file *stl) { - int i; + if (stl->error) + return; - if (stl->error) return; + stl->stats.malloced = 0; + stl->stats.freed = 0; + stl->stats.collisions = 0; - stl->stats.malloced = 0; - stl->stats.freed = 0; - stl->stats.collisions = 0; + /* tolerance = STL_MAX(stl->stats.shortest_edge, tolerance);*/ + /* tolerance = STL_MAX((stl->stats.bounding_diameter / 500000.0), tolerance);*/ + /* tolerance *= 0.5;*/ + stl->M = (int)hash_size_from_nr_faces(stl->stats.number_of_facets); - /* tolerance = STL_MAX(stl->stats.shortest_edge, tolerance);*/ - /* tolerance = STL_MAX((stl->stats.bounding_diameter / 500000.0), tolerance);*/ - /* tolerance *= 0.5;*/ + stl->heads.assign(stl->M, nullptr); + stl->tail = new stl_hash_edge; + stl->tail->next = stl->tail; - stl->M = (int)hash_size_from_nr_faces(stl->stats.number_of_facets); - - stl->heads = (stl_hash_edge**)calloc(stl->M, sizeof(*stl->heads)); - if(stl->heads == NULL) perror("stl_initialize_facet_check_nearby"); - - stl->tail = (stl_hash_edge*)malloc(sizeof(stl_hash_edge)); - if(stl->tail == NULL) perror("stl_initialize_facet_check_nearby"); - - stl->tail->next = stl->tail; - - for(i = 0; i < stl->M; i++) { - stl->heads[i] = stl->tail; - } + for (int i = 0; i < stl->M; ++ i) + stl->heads[i] = stl->tail; } - - static void stl_record_neighbors(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b) { @@ -358,29 +340,19 @@ stl_record_neighbors(stl_file *stl, if (stl->error) return; /* Facet a's neighbor is facet b */ - stl->neighbors_start[edge_a->facet_number].neighbor[edge_a->which_edge % 3] = - edge_b->facet_number; /* sets the .neighbor part */ - - stl->neighbors_start[edge_a->facet_number]. - which_vertex_not[edge_a->which_edge % 3] = - (edge_b->which_edge + 2) % 3; /* sets the .which_vertex_not part */ + stl->neighbors_start[edge_a->facet_number].neighbor[edge_a->which_edge % 3] = edge_b->facet_number; /* sets the .neighbor part */ + stl->neighbors_start[edge_a->facet_number].which_vertex_not[edge_a->which_edge % 3] = (edge_b->which_edge + 2) % 3; /* sets the .which_vertex_not part */ /* Facet b's neighbor is facet a */ - stl->neighbors_start[edge_b->facet_number].neighbor[edge_b->which_edge % 3] = - edge_a->facet_number; /* sets the .neighbor part */ - - stl->neighbors_start[edge_b->facet_number]. - which_vertex_not[edge_b->which_edge % 3] = - (edge_a->which_edge + 2) % 3; /* sets the .which_vertex_not part */ + stl->neighbors_start[edge_b->facet_number].neighbor[edge_b->which_edge % 3] = edge_a->facet_number; /* sets the .neighbor part */ + stl->neighbors_start[edge_b->facet_number].which_vertex_not[edge_b->which_edge % 3] = (edge_a->which_edge + 2) % 3; /* sets the .which_vertex_not part */ if( ((edge_a->which_edge < 3) && (edge_b->which_edge < 3)) || ((edge_a->which_edge > 2) && (edge_b->which_edge > 2))) { /* these facets are oriented in opposite directions. */ /* their normals are probably messed up. */ - stl->neighbors_start[edge_a->facet_number]. - which_vertex_not[edge_a->which_edge % 3] += 3; - stl->neighbors_start[edge_b->facet_number]. - which_vertex_not[edge_b->which_edge % 3] += 3; + stl->neighbors_start[edge_a->facet_number].which_vertex_not[edge_a->which_edge % 3] += 3; + stl->neighbors_start[edge_b->facet_number].which_vertex_not[edge_b->which_edge % 3] += 3; } @@ -561,8 +533,7 @@ stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, *facet1 = -1; } else { if( (stl->neighbors_start[edge_a->facet_number].neighbor[v1a] == -1) - && (stl->neighbors_start[edge_a->facet_number]. - neighbor[(v1a + 2) % 3] == -1)) { + && (stl->neighbors_start[edge_a->facet_number].neighbor[(v1a + 2) % 3] == -1)) { /* This vertex has no neighbors. This is a good one to change */ *facet1 = edge_a->facet_number; *vertex1 = v1a; @@ -581,8 +552,7 @@ stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, *facet2 = -1; } else { if( (stl->neighbors_start[edge_a->facet_number].neighbor[v2a] == -1) - && (stl->neighbors_start[edge_a->facet_number]. - neighbor[(v2a + 2) % 3] == -1)) { + && (stl->neighbors_start[edge_a->facet_number].neighbor[(v2a + 2) % 3] == -1)) { /* This vertex has no neighbors. This is a good one to change */ *facet2 = edge_a->facet_number; *vertex2 = v2a; @@ -595,140 +565,6 @@ stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, } } -static void remove_facet(stl_file *stl, int facet_number) -{ - assert(! stl->error); - ++ stl->stats.facets_removed; - /* Update list of connected edges */ - stl_neighbors &neighbors = stl->neighbors_start[facet_number]; - // Update statistics on unconnected triangle edges. - switch ((neighbors.neighbor[0] == -1) + (neighbors.neighbor[1] == -1) + (neighbors.neighbor[2] == -1)) { - case 0: // Facet has 3 neighbors - -- stl->stats.connected_facets_3_edge; - -- stl->stats.connected_facets_2_edge; - -- stl->stats.connected_facets_1_edge; - break; - case 1: // Facet has 2 neighbors - -- stl->stats.connected_facets_2_edge; - -- stl->stats.connected_facets_1_edge; - break; - case 2: // Facet has 1 neighbor - -- stl->stats.connected_facets_1_edge; - case 3: // Facet has 0 neighbors - break; - default: - assert(false); - } - - if (facet_number == -- stl->stats.number_of_facets) - // Removing the last face is easy, just forget the last face. - return; - - // Copy the face and neighborship from the last face to facet_number. - stl->facet_start[facet_number] = stl->facet_start[stl->stats.number_of_facets]; - neighbors = stl->neighbors_start[stl->stats.number_of_facets]; - // Update neighborship of faces, which used to point to the last face, now moved to facet_number. - for (int i = 0; i < 3; ++ i) - if (neighbors.neighbor[i] != -1) { - int &other_face_idx = stl->neighbors_start[neighbors.neighbor[i]].neighbor[(neighbors.which_vertex_not[i] + 1) % 3]; - if (other_face_idx != stl->stats.number_of_facets) { - printf("in remove_facet: neighbor = %d numfacets = %d this is wrong\n", other_face_idx, stl->stats.number_of_facets); - return; - } - other_face_idx = facet_number; - } -} - -static void remove_degenerate(stl_file *stl, int facet) -{ - assert(! stl->error); - - // Update statistics on face connectivity. - auto stl_update_connects_remove_1 = [stl](int facet_num) { - assert(! stl->error); - //FIXME when decreasing 3_edge, should I increase 2_edge etc? - switch ((stl->neighbors_start[facet_num].neighbor[0] == -1) + (stl->neighbors_start[facet_num].neighbor[1] == -1) + (stl->neighbors_start[facet_num].neighbor[2] == -1)) { - case 0: // Facet has 3 neighbors - -- stl->stats.connected_facets_3_edge; break; - case 1: // Facet has 2 neighbors - -- stl->stats.connected_facets_2_edge; break; - case 2: // Facet has 1 neighbor - -- stl->stats.connected_facets_1_edge; break; - case 3: // Facet has 0 neighbors - break; - default: - assert(false); - } - }; - - int edge_to_collapse = 0; - if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1]) { - if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { - // All 3 vertices are equal. Collapse the edge with no neighbor if it exists. - const int *nbr = stl->neighbors_start[facet].neighbor; - edge_to_collapse = (nbr[0] == -1) ? 0 : (nbr[1] == -1) ? 1 : 2; - } else { - edge_to_collapse = 0; - } - } else if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { - edge_to_collapse = 1; - } else if (stl->facet_start[facet].vertex[2] == stl->facet_start[facet].vertex[0]) { - edge_to_collapse = 2; - } else { - // No degenerate. Function shouldn't have been called. - return; - } - - int edge[3] = { (edge_to_collapse + 1) % 3, (edge_to_collapse + 2) % 3, edge_to_collapse }; - int neighbor[] = { - stl->neighbors_start[facet].neighbor[edge[0]], - stl->neighbors_start[facet].neighbor[edge[1]], - stl->neighbors_start[facet].neighbor[edge[2]] - }; - int vnot[] = { - stl->neighbors_start[facet].which_vertex_not[edge[0]], - stl->neighbors_start[facet].which_vertex_not[edge[1]], - stl->neighbors_start[facet].which_vertex_not[edge[2]] - }; - // Update statistics on edge connectivity. - if (neighbor[0] == -1) - stl_update_connects_remove_1(neighbor[1]); - if (neighbor[1] == -1) - stl_update_connects_remove_1(neighbor[0]); - - if (neighbor[0] >= 0) { - if (neighbor[1] >= 0) { - // Adjust the "flip" flag for the which_vertex_not values. - if (vnot[0] > 2) { - if (vnot[1] > 2) { - // The face to be removed has its normal flipped compared to the left & right neighbors, therefore after removing this face - // the two remaining neighbors will be oriented correctly. - vnot[0] -= 3; - vnot[1] -= 3; - } else - // One neighbor has its normal inverted compared to the face to be removed, the other is oriented equally. - // After removal, the two neighbors will have their normals flipped. - vnot[1] += 3; - } else if (vnot[1] > 2) - // One neighbor has its normal inverted compared to the face to be removed, the other is oriented equally. - // After removal, the two neighbors will have their normals flipped. - vnot[0] += 3; - } - stl->neighbors_start[neighbor[0]].neighbor[(vnot[0] + 1) % 3] = (neighbor[0] == neighbor[1]) ? -1 : neighbor[1]; - stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = vnot[1]; - } - if (neighbor[1] >= 0) { - stl->neighbors_start[neighbor[1]].neighbor[(vnot[1] + 1) % 3] = (neighbor[0] == neighbor[1]) ? -1 : neighbor[0]; - stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = vnot[0]; - } - if (neighbor[2] >= 0) { - stl_update_connects_remove_1(neighbor[2]); - stl->neighbors_start[neighbor[2]].neighbor[(vnot[2] + 1) % 3] = -1; - } - - remove_facet(stl, facet); -} - void stl_remove_unconnected_facets(stl_file *stl) { // A couple of things need to be done here. One is to remove any completely unconnected facets (0 edges connected) since these are @@ -737,12 +573,143 @@ void stl_remove_unconnected_facets(stl_file *stl) if (stl->error) return; + auto remove_facet = [stl](int facet_number) + { + ++ stl->stats.facets_removed; + /* Update list of connected edges */ + stl_neighbors &neighbors = stl->neighbors_start[facet_number]; + // Update statistics on unconnected triangle edges. + switch ((neighbors.neighbor[0] == -1) + (neighbors.neighbor[1] == -1) + (neighbors.neighbor[2] == -1)) { + case 0: // Facet has 3 neighbors + -- stl->stats.connected_facets_3_edge; + -- stl->stats.connected_facets_2_edge; + -- stl->stats.connected_facets_1_edge; + break; + case 1: // Facet has 2 neighbors + -- stl->stats.connected_facets_2_edge; + -- stl->stats.connected_facets_1_edge; + break; + case 2: // Facet has 1 neighbor + -- stl->stats.connected_facets_1_edge; + case 3: // Facet has 0 neighbors + break; + default: + assert(false); + } + + if (facet_number == -- stl->stats.number_of_facets) + // Removing the last face is easy, just forget the last face. + return; + + // Copy the face and neighborship from the last face to facet_number. + stl->facet_start[facet_number] = stl->facet_start[stl->stats.number_of_facets]; + neighbors = stl->neighbors_start[stl->stats.number_of_facets]; + // Update neighborship of faces, which used to point to the last face, now moved to facet_number. + for (int i = 0; i < 3; ++ i) + if (neighbors.neighbor[i] != -1) { + int &other_face_idx = stl->neighbors_start[neighbors.neighbor[i]].neighbor[(neighbors.which_vertex_not[i] + 1) % 3]; + if (other_face_idx != stl->stats.number_of_facets) { + printf("in remove_facet: neighbor = %d numfacets = %d this is wrong\n", other_face_idx, stl->stats.number_of_facets); + return; + } + other_face_idx = facet_number; + } + }; + + auto remove_degenerate = [stl, remove_facet](int facet) + { + // Update statistics on face connectivity. + auto stl_update_connects_remove_1 = [stl](int facet_num) { + assert(! stl->error); + //FIXME when decreasing 3_edge, should I increase 2_edge etc? + switch ((stl->neighbors_start[facet_num].neighbor[0] == -1) + (stl->neighbors_start[facet_num].neighbor[1] == -1) + (stl->neighbors_start[facet_num].neighbor[2] == -1)) { + case 0: // Facet has 3 neighbors + -- stl->stats.connected_facets_3_edge; break; + case 1: // Facet has 2 neighbors + -- stl->stats.connected_facets_2_edge; break; + case 2: // Facet has 1 neighbor + -- stl->stats.connected_facets_1_edge; break; + case 3: // Facet has 0 neighbors + break; + default: + assert(false); + } + }; + + int edge_to_collapse = 0; + if (stl->facet_start[facet].vertex[0] == stl->facet_start[facet].vertex[1]) { + if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { + // All 3 vertices are equal. Collapse the edge with no neighbor if it exists. + const int *nbr = stl->neighbors_start[facet].neighbor; + edge_to_collapse = (nbr[0] == -1) ? 0 : (nbr[1] == -1) ? 1 : 2; + } else { + edge_to_collapse = 0; + } + } else if (stl->facet_start[facet].vertex[1] == stl->facet_start[facet].vertex[2]) { + edge_to_collapse = 1; + } else if (stl->facet_start[facet].vertex[2] == stl->facet_start[facet].vertex[0]) { + edge_to_collapse = 2; + } else { + // No degenerate. Function shouldn't have been called. + return; + } + + int edge[3] = { (edge_to_collapse + 1) % 3, (edge_to_collapse + 2) % 3, edge_to_collapse }; + int neighbor[] = { + stl->neighbors_start[facet].neighbor[edge[0]], + stl->neighbors_start[facet].neighbor[edge[1]], + stl->neighbors_start[facet].neighbor[edge[2]] + }; + int vnot[] = { + stl->neighbors_start[facet].which_vertex_not[edge[0]], + stl->neighbors_start[facet].which_vertex_not[edge[1]], + stl->neighbors_start[facet].which_vertex_not[edge[2]] + }; + // Update statistics on edge connectivity. + if (neighbor[0] == -1) + stl_update_connects_remove_1(neighbor[1]); + if (neighbor[1] == -1) + stl_update_connects_remove_1(neighbor[0]); + + if (neighbor[0] >= 0) { + if (neighbor[1] >= 0) { + // Adjust the "flip" flag for the which_vertex_not values. + if (vnot[0] > 2) { + if (vnot[1] > 2) { + // The face to be removed has its normal flipped compared to the left & right neighbors, therefore after removing this face + // the two remaining neighbors will be oriented correctly. + vnot[0] -= 3; + vnot[1] -= 3; + } else + // One neighbor has its normal inverted compared to the face to be removed, the other is oriented equally. + // After removal, the two neighbors will have their normals flipped. + vnot[1] += 3; + } else if (vnot[1] > 2) + // One neighbor has its normal inverted compared to the face to be removed, the other is oriented equally. + // After removal, the two neighbors will have their normals flipped. + vnot[0] += 3; + } + stl->neighbors_start[neighbor[0]].neighbor[(vnot[0] + 1) % 3] = (neighbor[0] == neighbor[1]) ? -1 : neighbor[1]; + stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = vnot[1]; + } + if (neighbor[1] >= 0) { + stl->neighbors_start[neighbor[1]].neighbor[(vnot[1] + 1) % 3] = (neighbor[0] == neighbor[1]) ? -1 : neighbor[0]; + stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = vnot[0]; + } + if (neighbor[2] >= 0) { + stl_update_connects_remove_1(neighbor[2]); + stl->neighbors_start[neighbor[2]].neighbor[(vnot[2] + 1) % 3] = -1; + } + + remove_facet(facet); + }; + // remove degenerate facets for (uint32_t i = 0; i < stl->stats.number_of_facets;) if (stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[1] || stl->facet_start[i].vertex[0] == stl->facet_start[i].vertex[2] || stl->facet_start[i].vertex[1] == stl->facet_start[i].vertex[2]) { - remove_degenerate(stl, i); + remove_degenerate(i); // assert(stl_validate(stl)); } else ++ i; @@ -754,7 +721,7 @@ void stl_remove_unconnected_facets(stl_file *stl) stl->neighbors_start[i].neighbor[1] == -1 && stl->neighbors_start[i].neighbor[2] == -1) { // This facet is completely unconnected. Remove it. - remove_facet(stl, i); + remove_facet(i); assert(stl_validate(stl)); } else ++ i; @@ -850,8 +817,7 @@ stl_fill_holes(stl_file *stl) { } break; } else { - vnot = stl->neighbors_start[facet_num]. - which_vertex_not[next_edge]; + vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; facet_num = next_facet; } @@ -867,27 +833,14 @@ Try using a smaller tolerance or don't do a nearby check\n"); } } -void -stl_add_facet(stl_file *stl, stl_facet *new_facet) { - if (stl->error) return; - - stl->stats.facets_added += 1; - if(stl->stats.facets_malloced < (int)stl->stats.number_of_facets + 1) { - stl->facet_start = (stl_facet*)realloc(stl->facet_start, - (sizeof(stl_facet) * (stl->stats.facets_malloced + 256))); - if(stl->facet_start == NULL) perror("stl_add_facet"); - stl->neighbors_start = (stl_neighbors*)realloc(stl->neighbors_start, - (sizeof(stl_neighbors) * (stl->stats.facets_malloced + 256))); - if(stl->neighbors_start == NULL) perror("stl_add_facet"); - stl->stats.facets_malloced += 256; - } - stl->facet_start[stl->stats.number_of_facets] = *new_facet; - - /* note that the normal vector is not set here, just initialized to 0 */ - stl->facet_start[stl->stats.number_of_facets].normal = stl_normal::Zero(); - - stl->neighbors_start[stl->stats.number_of_facets].neighbor[0] = -1; - stl->neighbors_start[stl->stats.number_of_facets].neighbor[1] = -1; - stl->neighbors_start[stl->stats.number_of_facets].neighbor[2] = -1; - stl->stats.number_of_facets += 1; +void stl_add_facet(stl_file *stl, const stl_facet *new_facet) +{ + if (stl->error) + return; + ++ stl->stats.facets_added; + ++ stl->stats.number_of_facets; + stl->facet_start.emplace_back(*new_facet); + // note that the normal vector is not set here, just initialized to 0. + stl->facet_start[stl->stats.number_of_facets].normal = stl_normal::Zero(); + stl->neighbors_start.emplace_back(); } diff --git a/src/admesh/normals.cpp b/src/admesh/normals.cpp index ecf08b59c9..e11f1a3c16 100644 --- a/src/admesh/normals.cpp +++ b/src/admesh/normals.cpp @@ -84,7 +84,6 @@ stl_reverse_facet(stl_file *stl, int facet_num) { void stl_fix_normal_directions(stl_file *stl) { - char *norm_sw; /* int edge_num;*/ /* int vnot;*/ int checked = 0; @@ -101,7 +100,6 @@ stl_fix_normal_directions(stl_file *stl) { struct stl_normal *newn; struct stl_normal *temp; - int* reversed_ids; int reversed_count = 0; int id; int force_exit = 0; @@ -112,20 +110,15 @@ stl_fix_normal_directions(stl_file *stl) { if (stl->stats.number_of_facets == 0) return; /* Initialize linked list. */ - head = (struct stl_normal*)malloc(sizeof(struct stl_normal)); - if(head == NULL) perror("stl_fix_normal_directions"); - tail = (struct stl_normal*)malloc(sizeof(struct stl_normal)); - if(tail == NULL) perror("stl_fix_normal_directions"); + head = new stl_normal; + tail = new stl_normal; head->next = tail; tail->next = tail; /* Initialize list that keeps track of already fixed facets. */ - norm_sw = (char*)calloc(stl->stats.number_of_facets, sizeof(char)); - if(norm_sw == NULL) perror("stl_fix_normal_directions"); - + std::vector norm_sw(stl->stats.number_of_facets, 0); /* Initialize list that keeps track of reversed facets. */ - reversed_ids = (int*)calloc(stl->stats.number_of_facets, sizeof(int)); - if (reversed_ids == NULL) perror("stl_fix_normal_directions reversed_ids"); + std::vector reversed_ids(stl->stats.number_of_facets, 0); facet_num = 0; /* If normal vector is not within tolerance and backwards: @@ -166,8 +159,7 @@ stl_fix_normal_directions(stl_file *stl) { /* If we haven't fixed this facet yet, add it to the list: */ if(norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) { /* Add node to beginning of list. */ - newn = (struct stl_normal*)malloc(sizeof(struct stl_normal)); - if(newn == NULL) perror("stl_fix_normal_directions"); + newn = new stl_normal; newn->facet_num = stl->neighbors_start[facet_num].neighbor[j]; newn->next = head->next; head->next = newn; @@ -187,7 +179,7 @@ stl_fix_normal_directions(stl_file *stl) { } temp = head->next; /* Delete this facet from the list. */ head->next = head->next->next; - free(temp); + delete temp; } else { /* if we ran out of facets to fix: */ /* All of the facets in this part have been fixed. */ stl->stats.number_of_parts += 1; @@ -213,10 +205,8 @@ stl_fix_normal_directions(stl_file *stl) { } } } - free(head); - free(tail); - free(reversed_ids); - free(norm_sw); + delete head; + delete tail; } static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag) { diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index 2ad2709031..8162c6a8d4 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -31,17 +31,8 @@ void stl_invalidate_shared_vertices(stl_file *stl) { - if (stl->error) - return; - - if (stl->v_indices != nullptr) { - free(stl->v_indices); - stl->v_indices = nullptr; - } - if (stl->v_shared != nullptr) { - free(stl->v_shared); - stl->v_shared = nullptr; - } + stl->v_indices.clear(); + stl->v_shared.clear(); } void stl_generate_shared_vertices(stl_file *stl) @@ -53,23 +44,11 @@ void stl_generate_shared_vertices(stl_file *stl) stl_invalidate_shared_vertices(stl); // 3 indices to vertex per face - stl->v_indices = (v_indices_struct*)calloc(stl->stats.number_of_facets, sizeof(v_indices_struct)); - if (stl->v_indices == nullptr) - perror("stl_generate_shared_vertices"); + stl->v_indices.assign(stl->stats.number_of_facets, v_indices_struct()); // Shared vertices (3D coordinates) - stl->v_shared = (stl_vertex*)calloc((stl->stats.number_of_facets / 2), sizeof(stl_vertex)); - if (stl->v_shared == nullptr) - perror("stl_generate_shared_vertices"); - stl->stats.shared_malloced = stl->stats.number_of_facets / 2; + stl->v_shared.assign(stl->stats.number_of_facets / 2, stl_vertex()); stl->stats.shared_vertices = 0; - for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { - // vertex index -1 means no shared vertex was assigned yet. - stl->v_indices[i].vertex[0] = -1; - stl->v_indices[i].vertex[1] = -1; - stl->v_indices[i].vertex[2] = -1; - } - // A degenerate mesh may contain loops: Traversing a fan will end up in an endless loop // while never reaching the starting face. To avoid these endless loops, traversed faces at each fan traversal // are marked with a unique fan_traversal_stamp. @@ -82,13 +61,7 @@ void stl_generate_shared_vertices(stl_file *stl) // Shared vertex was already assigned. continue; // Create a new shared vertex. - if (stl->stats.shared_vertices == stl->stats.shared_malloced) { - stl->stats.shared_malloced += 1024; - stl->v_shared = (stl_vertex*)realloc(stl->v_shared, stl->stats.shared_malloced * sizeof(stl_vertex)); - if(stl->v_shared == nullptr) - perror("stl_generate_shared_vertices"); - } - stl->v_shared[stl->stats.shared_vertices] = stl->facet_start[facet_idx].vertex[j]; + stl->v_shared.emplace_back(stl->facet_start[facet_idx].vertex[j]); // Traverse the fan around the j-th vertex of the i-th face, assign the newly created shared vertex index to all the neighboring triangles in the triangle fan. int facet_in_fan_idx = facet_idx; bool edge_direction = false; diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 5ecd94bd11..3459511212 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -27,6 +27,7 @@ #include #include +#include #include // Size of the binary STL header, free form. @@ -44,18 +45,18 @@ static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect"); static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect"); struct stl_facet { - stl_normal normal; - stl_vertex vertex[3]; - char extra[2]; + stl_normal normal; + stl_vertex vertex[3]; + char extra[2]; - stl_facet rotated(const Eigen::Quaternion &rot) { - stl_facet out; - out.normal = rot * this->normal; - out.vertex[0] = rot * this->vertex[0]; - out.vertex[1] = rot * this->vertex[1]; - out.vertex[2] = rot * this->vertex[2]; - return out; - } + stl_facet rotated(const Eigen::Quaternion &rot) const { + stl_facet out; + out.normal = rot * this->normal; + out.vertex[0] = rot * this->vertex[0]; + out.vertex[1] = rot * this->vertex[1]; + out.vertex[2] = rot * this->vertex[2]; + return out; + } }; #define SIZEOF_STL_FACET 50 @@ -67,86 +68,100 @@ static_assert(sizeof(stl_facet) >= SIZEOF_STL_FACET, "size of stl_facet incorrec typedef enum {binary, ascii, inmemory} stl_type; -typedef struct { - stl_vertex p1; - stl_vertex p2; - int facet_number; -} stl_edge; +struct stl_edge { + stl_vertex p1; + stl_vertex p2; + int facet_number; +}; -typedef struct stl_hash_edge { - // Key of a hash edge: sorted vertices of the edge. - uint32_t key[6]; - // Compare two keys. - bool operator==(const stl_hash_edge &rhs) { return memcmp(key, rhs.key, sizeof(key)) == 0; } - bool operator!=(const stl_hash_edge &rhs) { return ! (*this == rhs); } - int hash(int M) const { return ((key[0] / 11 + key[1] / 7 + key[2] / 3) ^ (key[3] / 11 + key[4] / 7 + key[5] / 3)) % M; } - // Index of a facet owning this edge. - int facet_number; - // Index of this edge inside the facet with an index of facet_number. - // If this edge is stored backwards, which_edge is increased by 3. - int which_edge; - struct stl_hash_edge *next; -} stl_hash_edge; +struct stl_hash_edge { + // Key of a hash edge: sorted vertices of the edge. + uint32_t key[6]; + // Compare two keys. + bool operator==(const stl_hash_edge &rhs) { return memcmp(key, rhs.key, sizeof(key)) == 0; } + bool operator!=(const stl_hash_edge &rhs) { return ! (*this == rhs); } + int hash(int M) const { return ((key[0] / 11 + key[1] / 7 + key[2] / 3) ^ (key[3] / 11 + key[4] / 7 + key[5] / 3)) % M; } + // Index of a facet owning this edge. + int facet_number; + // Index of this edge inside the facet with an index of facet_number. + // If this edge is stored backwards, which_edge is increased by 3. + int which_edge; + struct stl_hash_edge *next; +}; -typedef struct { - // Index of a neighbor facet. - int neighbor[3]; - // Index of an opposite vertex at the neighbor face. - char which_vertex_not[3]; -} stl_neighbors; +struct stl_neighbors { + stl_neighbors() { reset(); } + void reset() { + neighbor[0] = -1; + neighbor[1] = -1; + neighbor[2] = -1; + which_vertex_not[0] = -1; + which_vertex_not[1] = -1; + which_vertex_not[2] = -1; + } -typedef struct { - int vertex[3]; -} v_indices_struct; + // Index of a neighbor facet. + int neighbor[3]; + // Index of an opposite vertex at the neighbor face. + char which_vertex_not[3]; +}; -typedef struct { - char header[81]; - stl_type type; - uint32_t number_of_facets; - stl_vertex max; - stl_vertex min; - stl_vertex size; - float bounding_diameter; - float shortest_edge; - float volume; - unsigned number_of_blocks; - int connected_edges; - int connected_facets_1_edge; - int connected_facets_2_edge; - int connected_facets_3_edge; - int facets_w_1_bad_edge; - int facets_w_2_bad_edge; - int facets_w_3_bad_edge; - int original_num_facets; - int edges_fixed; - int degenerate_facets; - int facets_removed; - int facets_added; - int facets_reversed; - int backwards_edges; - int normals_fixed; - int number_of_parts; - int malloced; - int freed; - int facets_malloced; - int collisions; - int shared_vertices; - int shared_malloced; -} stl_stats; +struct v_indices_struct { + // -1 means no vertex index has been assigned yet + v_indices_struct() { vertex[0] = -1; vertex[1] = -1; vertex[2] = -1; } + int vertex[3]; +}; -typedef struct { - FILE *fp; - stl_facet *facet_start; - stl_hash_edge **heads; - stl_hash_edge *tail; - int M; - stl_neighbors *neighbors_start; - v_indices_struct *v_indices; - stl_vertex *v_shared; - stl_stats stats; - char error; -} stl_file; +struct stl_stats { + char header[81]; + stl_type type; + uint32_t number_of_facets; + stl_vertex max; + stl_vertex min; + stl_vertex size; + float bounding_diameter; + float shortest_edge; + float volume; + unsigned number_of_blocks; + int connected_edges; + int connected_facets_1_edge; + int connected_facets_2_edge; + int connected_facets_3_edge; + int facets_w_1_bad_edge; + int facets_w_2_bad_edge; + int facets_w_3_bad_edge; + int original_num_facets; + int edges_fixed; + int degenerate_facets; + int facets_removed; + int facets_added; + int facets_reversed; + int backwards_edges; + int normals_fixed; + int number_of_parts; + int shared_vertices; + // hash table statistics + int malloced; + int freed; + int collisions; +}; + +struct stl_file { + FILE *fp; + std::vector facet_start; + std::vector neighbors_start; + // Hash table on edges + std::vector heads; + stl_hash_edge* tail; + int M; + // Indexed face set + std::vector v_indices; + std::vector v_shared; + // Statistics + stl_stats stats; + char error; +}; extern void stl_open(stl_file *stl, const char *file); extern void stl_close(stl_file *stl); @@ -272,7 +287,7 @@ extern void stl_allocate(stl_file *stl); extern void stl_read(stl_file *stl, int first_facet, bool first); extern void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first); extern void stl_reallocate(stl_file *stl); -extern void stl_add_facet(stl_file *stl, stl_facet *new_facet); +extern void stl_add_facet(stl_file *stl, const stl_facet *new_facet); extern void stl_clear_error(stl_file *stl); extern int stl_get_error(stl_file *stl); diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp index 85f66785b3..81060c0a3c 100644 --- a/src/admesh/stl_io.cpp +++ b/src/admesh/stl_io.cpp @@ -109,7 +109,6 @@ Normals fixed : %5d\n", stl->stats.normals_fixed); void stl_write_ascii(stl_file *stl, const char *file, const char *label) { - int i; char *error_msg; if (stl->error) return; @@ -129,7 +128,7 @@ stl_write_ascii(stl_file *stl, const char *file, const char *label) { fprintf(fp, "solid %s\n", label); - for(i = 0; i < stl->stats.number_of_facets; i++) { + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { fprintf(fp, " facet normal % .8E % .8E % .8E\n", stl->facet_start[i].normal(0), stl->facet_start[i].normal(1), stl->facet_start[i].normal(2)); @@ -154,7 +153,6 @@ stl_write_ascii(stl_file *stl, const char *file, const char *label) { void stl_print_neighbors(stl_file *stl, char *file) { - int i; FILE *fp; char *error_msg; @@ -173,7 +171,7 @@ stl_print_neighbors(stl_file *stl, char *file) { return; } - for(i = 0; i < stl->stats.number_of_facets; i++) { + for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n", i, stl->neighbors_start[i].neighbor[0], @@ -200,7 +198,6 @@ void stl_internal_reverse_quads(char *buf, size_t cnt) void stl_write_binary(stl_file *stl, const char *file, const char *label) { FILE *fp; - int i; char *error_msg; if (stl->error) return; @@ -219,13 +216,13 @@ stl_write_binary(stl_file *stl, const char *file, const char *label) { } fprintf(fp, "%s", label); - for(i = strlen(label); i < LABEL_SIZE; i++) putc(0, fp); + for(size_t i = strlen(label); i < LABEL_SIZE; i++) putc(0, fp); fseek(fp, LABEL_SIZE, SEEK_SET); #ifdef BOOST_LITTLE_ENDIAN fwrite(&stl->stats.number_of_facets, 4, 1, fp); - for (i = 0; i < stl->stats.number_of_facets; ++ i) - fwrite(stl->facet_start + i, SIZEOF_STL_FACET, 1, fp); + for (const stl_facet &facet : stl->facet_start) + fwrite(&facet, SIZEOF_STL_FACET, 1, fp); #else /* BOOST_LITTLE_ENDIAN */ char buffer[50]; // Convert the number of facets to little endian. @@ -288,8 +285,6 @@ stl_write_neighbor(stl_file *stl, int facet) { void stl_write_quad_object(stl_file *stl, char *file) { FILE *fp; - int i; - int j; char *error_msg; stl_vertex connect_color = stl_vertex::Zero(); stl_vertex uncon_1_color = stl_vertex::Zero(); @@ -313,10 +308,10 @@ stl_write_quad_object(stl_file *stl, char *file) { } fprintf(fp, "CQUAD\n"); - for(i = 0; i < stl->stats.number_of_facets; i++) { - j = ((stl->neighbors_start[i].neighbor[0] == -1) + - (stl->neighbors_start[i].neighbor[1] == -1) + - (stl->neighbors_start[i].neighbor[2] == -1)); + for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { + int j = ((stl->neighbors_start[i].neighbor[0] == -1) + + (stl->neighbors_start[i].neighbor[1] == -1) + + (stl->neighbors_start[i].neighbor[2] == -1)); if(j == 0) { color = connect_color; } else if(j == 1) { @@ -346,9 +341,8 @@ stl_write_quad_object(stl_file *stl, char *file) { fclose(fp); } -void -stl_write_dxf(stl_file *stl, const char *file, char *label) { - int i; +void stl_write_dxf(stl_file *stl, const char *file, char *label) +{ FILE *fp; char *error_msg; @@ -375,7 +369,7 @@ stl_write_dxf(stl_file *stl, const char *file, char *label) { fprintf(fp, "0\nSECTION\n2\nENTITIES\n"); - for(i = 0; i < stl->stats.number_of_facets; i++) { + for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { fprintf(fp, "0\n3DFACE\n8\n0\n"); fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index 911f4f5e82..81b7914c3a 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -35,22 +35,38 @@ #error "SEEK_SET not defined" #endif -void -stl_open(stl_file *stl, const char *file) { - stl_initialize(stl); - stl_count_facets(stl, file); - stl_allocate(stl); - stl_read(stl, 0, true); - if (stl->fp != nullptr) { - fclose(stl->fp); - stl->fp = nullptr; - } +void stl_open(stl_file *stl, const char *file) +{ + stl_initialize(stl); + stl_count_facets(stl, file); + stl_allocate(stl); + stl_read(stl, 0, true); + if (stl->fp != nullptr) { + fclose(stl->fp); + stl->fp = nullptr; + } } -void -stl_initialize(stl_file *stl) { - memset(stl, 0, sizeof(stl_file)); - stl->stats.volume = -1.0; +void stl_initialize(stl_file *stl) +{ + stl->fp = nullptr; + stl->tail = nullptr; + stl->M = 0; + stl->error = 0; + stl->facet_start.clear(); + stl->neighbors_start.clear(); + stl->v_indices.clear(); + stl->v_shared.clear(); + memset(&stl->stats, 0, sizeof(stl_stats)); + stl->stats.volume = -1.0; +} + +void stl_close(stl_file *stl) +{ + assert(stl->fp == nullptr); + assert(stl->heads.empty()); + assert(stl->tail == nullptr); + stl_initialize(stl); } #ifndef BOOST_LITTLE_ENDIAN @@ -175,20 +191,14 @@ stl_count_facets(stl_file *stl, const char *file) { stl->stats.original_num_facets = stl->stats.number_of_facets; } -void -stl_allocate(stl_file *stl) { - if (stl->error) return; - - /* Allocate memory for the entire .STL file */ - stl->facet_start = (stl_facet*)calloc(stl->stats.number_of_facets, - sizeof(stl_facet)); - if(stl->facet_start == NULL) perror("stl_initialize"); - stl->stats.facets_malloced = stl->stats.number_of_facets; - - /* Allocate memory for the neighbors list */ - stl->neighbors_start = (stl_neighbors*) - calloc(stl->stats.number_of_facets, sizeof(stl_neighbors)); - if(stl->facet_start == NULL) perror("stl_initialize"); +void stl_allocate(stl_file *stl) +{ + if (stl->error) + return; + // Allocate memory for the entire .STL file. + stl->facet_start.assign(stl->stats.number_of_facets, stl_facet()); + // Allocate memory for the neighbors list. + stl->neighbors_start.assign(stl->stats.number_of_facets, stl_neighbors()); } void @@ -237,23 +247,14 @@ stl_open_merge(stl_file *stl, char *file_to_merge) { stl->fp=origFp; } -extern void -stl_reallocate(stl_file *stl) { - if (stl->error) return; - /* Reallocate more memory for the .STL file(s) */ - stl->facet_start = (stl_facet*)realloc(stl->facet_start, stl->stats.number_of_facets * - sizeof(stl_facet)); - if(stl->facet_start == NULL) perror("stl_initialize"); - stl->stats.facets_malloced = stl->stats.number_of_facets; - - /* Reallocate more memory for the neighbors list */ - stl->neighbors_start = (stl_neighbors*) - realloc(stl->neighbors_start, stl->stats.number_of_facets * - sizeof(stl_neighbors)); - if(stl->facet_start == NULL) perror("stl_initialize"); +void stl_reallocate(stl_file *stl) +{ + if (stl->error) + return; + stl->facet_start.resize(stl->stats.number_of_facets); + stl->neighbors_start.resize(stl->stats.number_of_facets); } - /* Reads the contents of the file pointed to by stl->fp into the stl structure, starting at facet first_facet. The second argument says if it's our first time running this for the stl and therefore we should reset our max and min stats. */ @@ -366,20 +367,3 @@ void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first) stl->stats.max = stl->stats.max.cwiseMax(facet.vertex[i]); } } - -void stl_close(stl_file *stl) -{ - assert(stl->fp == nullptr); - assert(stl->heads == nullptr); - assert(stl->tail == nullptr); - - if (stl->facet_start != NULL) - free(stl->facet_start); - if (stl->neighbors_start != NULL) - free(stl->neighbors_start); - if (stl->v_indices != NULL) - free(stl->v_indices); - if (stl->v_shared != NULL) - free(stl->v_shared); - memset(stl, 0, sizeof(stl_file)); -} diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index c2d2c27269..61e0d11e7c 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -32,45 +32,39 @@ static float get_area(stl_facet *facet); static float get_volume(stl_file *stl); -void -stl_verify_neighbors(stl_file *stl) { - int i; - int j; - stl_edge edge_a; - stl_edge edge_b; - int neighbor; - int vnot; +void stl_verify_neighbors(stl_file *stl) +{ + if (stl->error) + return; - if (stl->error) return; + stl->stats.backwards_edges = 0; - stl->stats.backwards_edges = 0; - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - edge_a.p1 = stl->facet_start[i].vertex[j]; - edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3]; - neighbor = stl->neighbors_start[i].neighbor[j]; - vnot = stl->neighbors_start[i].which_vertex_not[j]; - - if(neighbor == -1) - continue; /* this edge has no neighbor... Continue. */ - if(vnot < 3) { - edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; - edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; - } else { - stl->stats.backwards_edges += 1; - edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; - edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; - } - if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) { - /* These edges should match but they don't. Print results. */ - printf("edge %d of facet %d doesn't match edge %d of facet %d\n", - j, i, vnot + 1, neighbor); - stl_write_facet(stl, (char*)"first facet", i); - stl_write_facet(stl, (char*)"second facet", neighbor); - } - } - } + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + for (int j = 0; j < 3; ++ j) { + stl_edge edge_a; + edge_a.p1 = stl->facet_start[i].vertex[j]; + edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3]; + int neighbor = stl->neighbors_start[i].neighbor[j]; + if (neighbor == -1) + continue; // this edge has no neighbor... Continue. + int vnot = stl->neighbors_start[i].which_vertex_not[j]; + stl_edge edge_b; + if (vnot < 3) { + edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; + edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; + } else { + stl->stats.backwards_edges += 1; + edge_b.p1 = stl->facet_start[neighbor].vertex[(vnot + 1) % 3]; + edge_b.p2 = stl->facet_start[neighbor].vertex[(vnot + 2) % 3]; + } + if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) { + // These edges should match but they don't. Print results. + printf("edge %d of facet %d doesn't match edge %d of facet %d\n", j, i, vnot + 1, neighbor); + stl_write_facet(stl, (char*)"first facet", i); + stl_write_facet(stl, (char*)"second facet", neighbor); + } + } + } } void stl_translate(stl_file *stl, float x, float y, float z) @@ -263,21 +257,19 @@ void stl_mirror_yz(stl_file *stl) void stl_mirror_xz(stl_file *stl) { - if (stl->error) - return; + if (stl->error) + return; - for (int i = 0; i < stl->stats.number_of_facets; i++) { - for (int j = 0; j < 3; j++) { - stl->facet_start[i].vertex[j](1) *= -1.0; - } - } - float temp_size = stl->stats.min(1); - stl->stats.min(1) = stl->stats.max(1); - stl->stats.max(1) = temp_size; - stl->stats.min(1) *= -1.0; - stl->stats.max(1) *= -1.0; - stl_reverse_all_facets(stl); - stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j](1) *= -1.0; + float temp_size = stl->stats.min(1); + stl->stats.min(1) = stl->stats.max(1); + stl->stats.max(1) = temp_size; + stl->stats.min(1) *= -1.0; + stl->stats.max(1) *= -1.0; + stl_reverse_all_facets(stl); + stl->stats.facets_reversed -= stl->stats.number_of_facets; // for not altering stats } static float get_volume(stl_file *stl) @@ -463,18 +455,18 @@ bool stl_validate(stl_file *stl) { assert(! stl->error); assert(stl->fp == nullptr); - assert(stl->facet_start != nullptr); - assert(stl->heads == nullptr); + assert(! stl->facet_start.empty()); + assert(stl->heads.empty()); assert(stl->tail == nullptr); - assert(stl->neighbors_start != nullptr); - assert((stl->v_indices == nullptr) == (stl->v_shared == nullptr)); + assert(! stl->neighbors_start.empty()); + assert((stl->v_indices.empty()) == (stl->v_shared.empty())); assert(stl->stats.number_of_facets > 0); #ifdef _DEBUG // Verify validity of neighborship data. for (int facet_idx = 0; facet_idx < (int)stl->stats.number_of_facets; ++ facet_idx) { const stl_neighbors &nbr = stl->neighbors_start[facet_idx]; - const int *vertices = (stl->v_indices == nullptr) ? nullptr : stl->v_indices[facet_idx].vertex; + const int *vertices = (stl->v_indices.empty()) ? nullptr : stl->v_indices[facet_idx].vertex; for (int nbr_idx = 0; nbr_idx < 3; ++ nbr_idx) { int nbr_face = stl->neighbors_start[facet_idx].neighbor[nbr_idx]; assert(nbr_face < (int)stl->stats.number_of_facets); diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 38b34c4620..c3916a14e7 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1885,7 +1885,7 @@ namespace Slic3r { volume->mesh.repair(); stl_file& stl = volume->mesh.stl; - if (stl.v_shared == nullptr) + if (stl.v_shared.empty()) stl_generate_shared_vertices(&stl); if (stl.stats.shared_vertices == 0) diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index d26b5f3ed9..f48b5b58cf 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -926,7 +926,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) if (! volume->mesh.repaired) throw std::runtime_error("store_amf() requires repair()"); auto &stl = volume->mesh.stl; - if (stl.v_shared == nullptr) + if (stl.v_shared.empty()) stl_generate_shared_vertices(&stl); const Transform3d& matrix = volume->get_matrix(); for (size_t i = 0; i < stl.stats.shared_vertices; ++i) { diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index 502cac6e9e..d983b1098b 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -167,10 +167,10 @@ static void extract_model_from_archive( stl.stats.original_num_facets = header.nTriangles; stl_allocate(&stl); if (header.nTriangles > 0 && data.size() == 50 * header.nTriangles + sizeof(StlHeader)) { - memcpy((char*)stl.facet_start, data.data() + sizeof(StlHeader), 50 * header.nTriangles); + memcpy((char*)stl.facet_start.data(), data.data() + sizeof(StlHeader), 50 * header.nTriangles); if (sizeof(stl_facet) > SIZEOF_STL_FACET) { // The stl.facet_start is not packed tightly. Unpack the array of stl_facets. - unsigned char *data = (unsigned char*)stl.facet_start; + unsigned char *data = (unsigned char*)stl.facet_start.data(); for (size_t i = header.nTriangles - 1; i > 0; -- i) memmove(data + i * sizeof(stl_facet), data + i * SIZEOF_STL_FACET, SIZEOF_STL_FACET); } @@ -257,7 +257,7 @@ static void extract_model_from_archive( stl.stats.number_of_facets = (uint32_t)facets.size(); stl.stats.original_num_facets = (int)facets.size(); stl_allocate(&stl); - memcpy((void*)stl.facet_start, facets.data(), facets.size() * 50); + memcpy((void*)stl.facet_start.data(), facets.data(), facets.size() * 50); stl_get_size(&stl); mesh.repair(); // Add a mesh to a model. diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3b1bd5df22..64fbb9a2a8 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -910,18 +910,16 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const if (v->is_model_part()) { const stl_file &stl = v->mesh.stl; Transform3d trafo = trafo_instance * v->get_matrix(); - if (stl.v_shared == nullptr) { + if (stl.v_shared.empty()) { // Using the STL faces. - for (unsigned int i = 0; i < stl.stats.number_of_facets; ++ i) { - const stl_facet &facet = stl.facet_start[i]; + for (const stl_facet &facet : stl.facet_start) for (size_t j = 0; j < 3; ++ j) { Vec3d p = trafo * facet.vertex[j].cast(); pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); } - } } else { // Using the shared vertices should be a bit quicker than using the STL faces. - for (int i = 0; i < stl.stats.shared_vertices; ++ i) { + for (int i = 0; i < stl.stats.shared_vertices; ++ i) { Vec3d p = trafo * stl.v_shared[i].cast(); pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); } @@ -1347,13 +1345,9 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const Transform3d mv = mi * v->get_matrix(); const TriangleMesh& hull = v->get_convex_hull(); - for (uint32_t f = 0; f < hull.stl.stats.number_of_facets; ++f) - { - const stl_facet* facet = hull.stl.facet_start + f; - min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[0].cast())); - min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[1].cast())); - min_z = std::min(min_z, Vec3d::UnitZ().dot(mv * facet->vertex[2].cast())); - } + for (const stl_facet &facet : hull.stl.facet_start) + for (int i = 0; i < 3; ++ i) + min_z = std::min(min_z, (mv * facet.vertex[i].cast()).z()); } return min_z + inst->get_offset(Z); diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index c368b8604d..1609b9ac41 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -121,19 +121,10 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { V.resize(3*stl.stats.number_of_facets, 3); F.resize(stl.stats.number_of_facets, 3); for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) { - const stl_facet* facet = stl.facet_start+i; - V(3*i+0, 0) = double(facet->vertex[0](0)); - V(3*i+0, 1) = double(facet->vertex[0](1)); - V(3*i+0, 2) = double(facet->vertex[0](2)); - - V(3*i+1, 0) = double(facet->vertex[1](0)); - V(3*i+1, 1) = double(facet->vertex[1](1)); - V(3*i+1, 2) = double(facet->vertex[1](2)); - - V(3*i+2, 0) = double(facet->vertex[2](0)); - V(3*i+2, 1) = double(facet->vertex[2](1)); - V(3*i+2, 2) = double(facet->vertex[2](2)); - + const stl_facet &facet = stl.facet_start[i]; + V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0].cast(); + V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1].cast(); + V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2].cast(); F(i, 0) = int(3*i+0); F(i, 1) = int(3*i+1); F(i, 2) = int(3*i+2); diff --git a/src/libslic3r/SlicingAdaptive.cpp b/src/libslic3r/SlicingAdaptive.cpp index 2ef4aec8c6..ad03b550b9 100644 --- a/src/libslic3r/SlicingAdaptive.cpp +++ b/src/libslic3r/SlicingAdaptive.cpp @@ -27,8 +27,8 @@ void SlicingAdaptive::prepare() nfaces_total += (*it_mesh)->stl.stats.number_of_facets; m_faces.reserve(nfaces_total); for (std::vector::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh) - for (int i = 0; i < (*it_mesh)->stl.stats.number_of_facets; ++ i) - m_faces.push_back((*it_mesh)->stl.facet_start + i); + for (const stl_facet &face : (*it_mesh)->stl.facet_start) + m_faces.emplace_back(&face); // 2) Sort faces lexicographically by their Z span. std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) { diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index b7c6c07a75..ac23abd26a 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -51,7 +51,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& f stl.stats.type = inmemory; // count facets and allocate memory - stl.stats.number_of_facets = facets.size(); + stl.stats.number_of_facets = (uint32_t)facets.size(); stl.stats.original_num_facets = stl.stats.number_of_facets; stl_allocate(&stl); @@ -78,25 +78,14 @@ TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) stl_close(&this->stl); this->stl = other.stl; this->repaired = other.repaired; - this->stl.heads = nullptr; + this->stl.heads.clear(); this->stl.tail = nullptr; this->stl.error = other.stl.error; - if (other.stl.facet_start != nullptr) { - this->stl.facet_start = (stl_facet*)calloc(other.stl.stats.number_of_facets, sizeof(stl_facet)); - std::copy(other.stl.facet_start, other.stl.facet_start + other.stl.stats.number_of_facets, this->stl.facet_start); - } - if (other.stl.neighbors_start != nullptr) { - this->stl.neighbors_start = (stl_neighbors*)calloc(other.stl.stats.number_of_facets, sizeof(stl_neighbors)); - std::copy(other.stl.neighbors_start, other.stl.neighbors_start + other.stl.stats.number_of_facets, this->stl.neighbors_start); - } - if (other.stl.v_indices != nullptr) { - this->stl.v_indices = (v_indices_struct*)calloc(other.stl.stats.number_of_facets, sizeof(v_indices_struct)); - std::copy(other.stl.v_indices, other.stl.v_indices + other.stl.stats.number_of_facets, this->stl.v_indices); - } - if (other.stl.v_shared != nullptr) { - this->stl.v_shared = (stl_vertex*)calloc(other.stl.stats.shared_vertices, sizeof(stl_vertex)); - std::copy(other.stl.v_shared, other.stl.v_shared + other.stl.stats.shared_vertices, this->stl.v_shared); - } + this->stl.facet_start = other.stl.facet_start; + this->stl.neighbors_start = other.stl.neighbors_start; + this->stl.v_indices = other.stl.v_indices; + this->stl.v_shared = other.stl.v_shared; + this->stl.stats = other.stl.stats; return *this; } @@ -125,8 +114,8 @@ void TriangleMesh::repair() // checking nearby //int last_edges_fixed = 0; - float tolerance = stl.stats.shortest_edge; - float increment = stl.stats.bounding_diameter / 10000.0; + float tolerance = (float)stl.stats.shortest_edge; + float increment = (float)stl.stats.bounding_diameter / 10000.0f; int iterations = 2; if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { for (int i = 0; i < iterations; i++) { @@ -444,7 +433,7 @@ TriangleMeshPtrs TriangleMesh::split() const TriangleMesh* mesh = new TriangleMesh; meshes.emplace_back(mesh); mesh->stl.stats.type = inmemory; - mesh->stl.stats.number_of_facets = facets.size(); + mesh->stl.stats.number_of_facets = (uint32_t)facets.size(); mesh->stl.stats.original_num_facets = mesh->stl.stats.number_of_facets; stl_clear_error(&mesh->stl); stl_allocate(&mesh->stl); @@ -486,13 +475,12 @@ ExPolygons TriangleMesh::horizontal_projection() const { Polygons pp; pp.reserve(this->stl.stats.number_of_facets); - for (uint32_t i = 0; i < this->stl.stats.number_of_facets; ++ i) { - stl_facet* facet = &this->stl.facet_start[i]; + for (const stl_facet &facet : this->stl.facet_start) { Polygon p; p.points.resize(3); - p.points[0] = Point::new_scale(facet->vertex[0](0), facet->vertex[0](1)); - p.points[1] = Point::new_scale(facet->vertex[1](0), facet->vertex[1](1)); - p.points[2] = Point::new_scale(facet->vertex[2](0), facet->vertex[2](1)); + p.points[0] = Point::new_scale(facet.vertex[0](0), facet.vertex[0](1)); + p.points[1] = Point::new_scale(facet.vertex[1](0), facet.vertex[1](1)); + p.points[2] = Point::new_scale(facet.vertex[2](0), facet.vertex[2](1)); p.make_counter_clockwise(); // do this after scaling, as winding order might change while doing that pp.emplace_back(p); } @@ -526,17 +514,15 @@ BoundingBoxf3 TriangleMesh::bounding_box() const BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) const { BoundingBoxf3 bbox; - if (stl.v_shared == nullptr) { + if (stl.v_shared.empty()) { // Using the STL faces. - for (size_t i = 0; i < this->facets_count(); ++ i) { - const stl_facet &facet = this->stl.facet_start[i]; + for (const stl_facet &facet : this->stl.facet_start) for (size_t j = 0; j < 3; ++ j) bbox.merge(trafo * facet.vertex[j].cast()); - } } else { // Using the shared vertices should be a bit quicker than using the STL faces. - for (int i = 0; i < stl.stats.shared_vertices; ++ i) - bbox.merge(trafo * this->stl.v_shared[i].cast()); + for (const stl_vertex &v : this->stl.v_shared) + bbox.merge(trafo * v.cast()); } return bbox; } @@ -551,18 +537,12 @@ TriangleMesh TriangleMesh::convex_hull_3d() const std::vector src_vertices; // We will now fill the vector with input points for computation: - stl_facet* facet_ptr = stl.facet_start; - while (facet_ptr < stl.facet_start + stl.stats.number_of_facets) - { - for (int i = 0; i < 3; ++i) - { - const stl_vertex& v = facet_ptr->vertex[i]; + for (const stl_facet &facet : stl.facet_start) + for (int i = 0; i < 3; ++ i) { + const stl_vertex& v = facet.vertex[i]; src_vertices.emplace_back(v(0), v(1), v(2)); } - facet_ptr += 1; - } - // The qhull call: orgQhull::Qhull qhull; qhull.disableOutputStream(); // we want qhull to be quiet @@ -606,7 +586,7 @@ void TriangleMesh::require_shared_vertices() assert(stl_validate(&this->stl)); if (! this->repaired) this->repair(); - if (this->stl.v_shared == nullptr) { + if (this->stl.v_shared.empty()) { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices"; stl_generate_shared_vertices(&(this->stl)); } @@ -622,10 +602,9 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac throw_on_cancel(); facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1); - v_scaled_shared.assign(_mesh->stl.v_shared, _mesh->stl.v_shared + _mesh->stl.stats.shared_vertices); - // Scale the copied vertices. - for (int i = 0; i < this->mesh->stl.stats.shared_vertices; ++ i) - this->v_scaled_shared[i] *= float(1. / SCALING_FACTOR); + v_scaled_shared.assign(_mesh->stl.v_shared.size(), stl_vertex()); + for (size_t i = 0; i < v_scaled_shared.size(); ++ i) + this->v_scaled_shared[i] = _mesh->stl.v_shared[i] / float(SCALING_FACTOR); // Create a mapping from triangle edge into face. struct EdgeToFace { @@ -814,7 +793,7 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vector* lines, boost::mutex* lines_mutex, const std::vector &z) const { - const stl_facet &facet = m_use_quaternion ? this->mesh->stl.facet_start[facet_idx].rotated(m_quaternion) : this->mesh->stl.facet_start[facet_idx]; + const stl_facet &facet = m_use_quaternion ? (this->mesh->stl.facet_start.data() + facet_idx)->rotated(m_quaternion) : *(this->mesh->stl.facet_start.data() + facet_idx); // find facet extents const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2))); @@ -1710,7 +1689,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - slicing object"; float scaled_z = scale_(z); for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) { - stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; + const stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; // find facet extents float min_z = std::min(facet->vertex[0](2), std::min(facet->vertex[1](2), facet->vertex[2](2))); @@ -1901,10 +1880,10 @@ TriangleMesh make_cylinder(double r, double h, double fa) //FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html TriangleMesh make_sphere(double radius, double fa) { - int sectorCount = ceil(2. * M_PI / fa); - int stackCount = ceil(M_PI / fa); - float sectorStep = 2. * M_PI / sectorCount; - float stackStep = M_PI / stackCount; + int sectorCount = int(ceil(2. * M_PI / fa)); + int stackCount = int(ceil(M_PI / fa)); + float sectorStep = float(2. * M_PI / sectorCount); + float stackStep = float(M_PI / stackCount); Pointf3s vertices; vertices.reserve((stackCount - 1) * sectorCount + 2); diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index c284f6482b..4de1f5989e 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -58,7 +58,7 @@ public: TriangleMeshPtrs split() const; void merge(const TriangleMesh &mesh); ExPolygons horizontal_projection() const; - const float* first_vertex() const { return this->stl.facet_start ? &this->stl.facet_start->vertex[0](0) : nullptr; } + const float* first_vertex() const { return this->stl.facet_start.empty() ? nullptr : &this->stl.facet_start.front().vertex[0](0); } // 2D convex hull of a 3D mesh projected into the Z=0 plane. Polygon convex_hull(); BoundingBoxf3 bounding_box() const; @@ -69,7 +69,7 @@ public: void reset_repair_stats(); bool needed_repair() const; void require_shared_vertices(); - bool has_shared_vertices() const { return stl.v_shared != NULL; } + bool has_shared_vertices() const { return ! stl.v_shared.empty(); } size_t facets_count() const { return this->stl.stats.number_of_facets; } bool empty() const { return this->facets_count() == 0; } bool is_splittable() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index d118a6877f..ae017f7d12 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -396,10 +396,10 @@ void GLGizmoSlaSupports::update_mesh() V.resize(3 * stl.stats.number_of_facets, 3); F.resize(stl.stats.number_of_facets, 3); for (unsigned int i=0; ivertex[0](0); V(3*i+0, 1) = facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2); - V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) = facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2); - V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) = facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2); + const stl_facet &facet = stl.facet_start[i]; + V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0]; + V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1]; + V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2]; F(i, 0) = 3*i+0; F(i, 1) = 3*i+1; F(i, 2) = 3*i+2; From c7ba48a4735cb2f6e98b56999a1b5d7476bde0fb Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 5 Jun 2019 09:54:52 +0200 Subject: [PATCH 042/627] Fix of perl bindings --- xs/xsp/TriangleMesh.xsp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index e519f9210c..a188e0b8dc 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -100,7 +100,7 @@ TriangleMesh::vertices() CODE: if (!THIS->repaired) CONFESS("vertices() requires repair()"); - if (THIS->stl.v_shared == NULL) + if (THIS->stl.v_shared.empty()) stl_generate_shared_vertices(&(THIS->stl)); // vertices @@ -124,7 +124,7 @@ TriangleMesh::facets() CODE: if (!THIS->repaired) CONFESS("facets() requires repair()"); - if (THIS->stl.v_shared == NULL) + if (THIS->stl.v_shared.empty()) stl_generate_shared_vertices(&(THIS->stl)); // facets From 836f2d777fabb94fdd0585d403bd0bedc603c2ce Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 5 Jun 2019 10:07:59 +0200 Subject: [PATCH 043/627] Update 3D scene when all compressed texture data are sent to GPU --- src/slic3r/GUI/3DBed.cpp | 66 +++++++++++++++++++++++++++++++++-- src/slic3r/GUI/3DBed.hpp | 14 ++++++++ src/slic3r/GUI/GLCanvas3D.cpp | 16 +++++++++ src/slic3r/GUI/GLCanvas3D.hpp | 8 +++++ src/slic3r/GUI/GLTexture.cpp | 13 +++++++ src/slic3r/GUI/GLTexture.hpp | 2 ++ 6 files changed, 117 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 98f2d7dc32..04ab4e2da2 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -9,6 +9,9 @@ #include "GUI_App.hpp" #include "PresetBundle.hpp" #include "Gizmos/GLGizmoBase.hpp" +#if ENABLE_COMPRESSED_TEXTURES +#include "GLCanvas3D.hpp" +#endif // ENABLE_COMPRESSED_TEXTURES #include @@ -273,6 +276,9 @@ void Bed3D::Axes::render_axis(double length) const Bed3D::Bed3D() : m_type(Custom) +#if ENABLE_COMPRESSED_TEXTURES + , m_requires_canvas_update(false) +#endif // ENABLE_COMPRESSED_TEXTURES #if ENABLE_TEXTURES_FROM_SVG , m_vbo_id(0) #endif // ENABLE_TEXTURES_FROM_SVG @@ -328,14 +334,34 @@ Point Bed3D::point_projection(const Point& point) const } #if ENABLE_TEXTURES_FROM_SVG +#if ENABLE_COMPRESSED_TEXTURES +void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const +#else void Bed3D::render(float theta, bool useVBOs, float scale_factor) const +#endif // ENABLE_COMPRESSED_TEXTURES { m_scale_factor = scale_factor; EType type = useVBOs ? m_type : Custom; switch (type) - { +#if ENABLE_COMPRESSED_TEXTURES + case MK2: + { + render_prusa(canvas, "mk2", theta > 90.0f); + break; + } + case MK3: + { + render_prusa(canvas, "mk3", theta > 90.0f); + break; + } + case SL1: + { + render_prusa(canvas, "sl1", theta > 90.0f); + break; + } +#else case MK2: { render_prusa("mk2", theta > 90.0f); @@ -351,6 +377,7 @@ void Bed3D::render(float theta, bool useVBOs, float scale_factor) const render_prusa("sl1", theta > 90.0f); break; } +#endif // ENABLE_COMPRESSED_TEXTURES default: case Custom: { @@ -360,7 +387,11 @@ void Bed3D::render(float theta, bool useVBOs, float scale_factor) const } } #else +#if ENABLE_COMPRESSED_TEXTURES +void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const +#else void Bed3D::render(float theta, bool useVBOs, float scale_factor) const +#endif // ENABLE_COMPRESSED_TEXTURES { m_scale_factor = scale_factor; @@ -369,6 +400,23 @@ void Bed3D::render(float theta, bool useVBOs, float scale_factor) const switch (m_type) { +#if ENABLE_COMPRESSED_TEXTURES + case MK2: + { + render_prusa(canvas, "mk2", theta, useVBOs); + break; + } + case MK3: + { + render_prusa(canvas, "mk3", theta, useVBOs); + break; + } + case SL1: + { + render_prusa(canvas, "sl1", theta, useVBOs); + break; + } +#else case MK2: { render_prusa("mk2", theta, useVBOs); @@ -383,7 +431,8 @@ void Bed3D::render(float theta, bool useVBOs, float scale_factor) const { render_prusa("sl1", theta, useVBOs); break; - } + } +#endif // ENABLE_COMPRESSED_TEXTURES default: case Custom: { @@ -487,7 +536,11 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const } #if ENABLE_TEXTURES_FROM_SVG +#if ENABLE_COMPRESSED_TEXTURES +void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom) const +#else void Bed3D::render_prusa(const std::string &key, bool bottom) const +#endif // ENABLE_COMPRESSED_TEXTURES { std::string tex_path = resources_dir() + "/icons/bed/" + key; @@ -557,6 +610,15 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const // the temporary texture is not needed anymore, reset it if (m_temp_texture.get_id() != 0) m_temp_texture.reset(); + + m_requires_canvas_update = true; + } + else if (m_requires_canvas_update && m_texture.all_compressed_data_sent_to_gpu()) + { + if (canvas != nullptr) + canvas->stop_keeping_dirty(); + + m_requires_canvas_update = false; } #endif // ENABLE_COMPRESSED_TEXTURES diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 054b746e5e..401f1232f3 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -13,6 +13,10 @@ typedef class GLUquadric GLUquadricObj; namespace Slic3r { namespace GUI { +#if ENABLE_COMPRESSED_TEXTURES +class GLCanvas3D; +#endif // ENABLE_COMPRESSED_TEXTURES + class GeometryBuffer { #if ENABLE_TEXTURES_FROM_SVG @@ -94,6 +98,8 @@ private: #if ENABLE_COMPRESSED_TEXTURES // temporary texture shown until the main texture has still no levels compressed mutable GLTexture m_temp_texture; + // used to trigger 3D scene update once all compressed textures have been sent to GPU + mutable bool m_requires_canvas_update; #endif // ENABLE_COMPRESSED_TEXTURES mutable Shader m_shader; mutable unsigned int m_vbo_id; @@ -125,7 +131,11 @@ public: bool contains(const Point& point) const; Point point_projection(const Point& point) const; +#if ENABLE_COMPRESSED_TEXTURES + void render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const; +#else void render(float theta, bool useVBOs, float scale_factor) const; +#endif // ENABLE_COMPRESSED_TEXTURES void render_axes() const; private: @@ -134,7 +144,11 @@ private: void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); EType detect_type(const Pointfs& shape) const; #if ENABLE_TEXTURES_FROM_SVG +#if ENABLE_COMPRESSED_TEXTURES + void render_prusa(GLCanvas3D* canvas, const std::string& key, bool bottom) const; +#else void render_prusa(const std::string& key, bool bottom) const; +#endif // ENABLE_COMPRESSED_TEXTURES void render_prusa_shader(bool transparent) const; #else void render_prusa(const std::string &key, float theta, bool useVBOs) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f7d685502a..90c21608e4 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1266,6 +1266,9 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar #endif // ENABLE_SVG_ICONS , m_use_clipping_planes(false) , m_sidebar_field("") +#if ENABLE_COMPRESSED_TEXTURES + , m_keep_dirty(false) +#endif // ENABLE_COMPRESSED_TEXTURES , m_config(nullptr) , m_process(nullptr) , m_model(nullptr) @@ -1483,6 +1486,10 @@ void GLCanvas3D::bed_shape_changed() m_camera.set_scene_box(scene_bounding_box()); m_camera.requires_zoom_to_bed = true; m_dirty = true; +#if ENABLE_COMPRESSED_TEXTURES + if (m_bed.is_prusa()) + start_keeping_dirty(); +#endif // ENABLE_COMPRESSED_TEXTURES } void GLCanvas3D::set_color_by(const std::string& value) @@ -2353,6 +2360,11 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) return; _refresh_if_shown_on_screen(); + +#if ENABLE_COMPRESSED_TEXTURES + if (m_keep_dirty) + m_dirty = true; +#endif // ENABLE_COMPRESSED_TEXTURES } void GLCanvas3D::on_char(wxKeyEvent& evt) @@ -3966,7 +3978,11 @@ void GLCanvas3D::_render_bed(float theta) const #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); #endif // ENABLE_RETINA_GL +#if ENABLE_COMPRESSED_TEXTURES + m_bed.render(const_cast(this), theta, m_use_VBOs, scale_factor); +#else m_bed.render(theta, m_use_VBOs, scale_factor); +#endif // ENABLE_COMPRESSED_TEXTURES } void GLCanvas3D::_render_axes() const diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 6d77a507f1..d7a4c6d22d 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -449,6 +449,9 @@ private: bool m_use_clipping_planes; mutable SlaCap m_sla_caps[2]; std::string m_sidebar_field; +#if ENABLE_COMPRESSED_TEXTURES + bool m_keep_dirty; +#endif // ENABLE_COMPRESSED_TEXTURES mutable GLVolumeCollection m_volumes; Selection m_selection; @@ -635,6 +638,11 @@ public: void set_cursor(ECursorType type); void msw_rescale(); +#if ENABLE_COMPRESSED_TEXTURES + void start_keeping_dirty() { m_keep_dirty = true; } + void stop_keeping_dirty() { m_keep_dirty = false; } +#endif // ENABLE_COMPRESSED_TEXTURES + private: bool _is_shown_on_screen() const; diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 78de38d549..9b373440a1 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -85,6 +85,17 @@ void GLTexture::Compressor::send_compressed_data_to_gpu() glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); } +bool GLTexture::Compressor::all_compressed_data_sent_to_gpu() const +{ + for (const Level& level : m_levels) + { + if (!level.sent_to_gpu) + return false; + } + + return true; +} + void GLTexture::Compressor::compress() { // reference: https://github.com/Cyan4973/RygsDXTc @@ -512,7 +523,9 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px) #endif // ENABLE_COMPRESSED_TEXTURES { +#if ENABLE_COMPRESSED_TEXTURES bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc; +#endif // ENABLE_COMPRESSED_TEXTURES NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f); if (image == nullptr) diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index d1ff366c34..5df6189b6f 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -42,6 +42,7 @@ namespace GUI { bool unsent_compressed_data_available() const; void send_compressed_data_to_gpu(); + bool all_compressed_data_sent_to_gpu() const; private: void compress(); @@ -109,6 +110,7 @@ namespace GUI { #if ENABLE_COMPRESSED_TEXTURES bool unsent_compressed_data_available() const { return m_compressor.unsent_compressed_data_available(); } void send_compressed_data_to_gpu() { m_compressor.send_compressed_data_to_gpu(); } + bool all_compressed_data_sent_to_gpu() const { return m_compressor.all_compressed_data_sent_to_gpu(); } #endif // ENABLE_COMPRESSED_TEXTURES static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); From 567f382938b7939c4266b2ffcc3e14d91f9a80a1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 5 Jun 2019 11:03:46 +0200 Subject: [PATCH 044/627] Implemented focusing of the last edited range --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 73 +++++++++++++++++++++++------ src/slic3r/GUI/GUI_ObjectLayers.hpp | 19 ++++++-- src/slic3r/GUI/GUI_ObjectList.cpp | 8 ++-- 3 files changed, 76 insertions(+), 24 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index de1c95fc90..1f0511acb7 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -43,28 +43,67 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : wxSizer* ObjectLayers::create_layer_without_buttons(const t_layer_config_ranges::value_type& layer) { + const bool is_last_edited_range = layer.first == m_last_edited_range; + // Add control for the "Min Z" + auto temp = new LayerRangeEditor(m_parent, double_to_string(layer.first.first), - [layer](coordf_t min_z) { - wxGetApp().obj_list()->edit_layer_range(layer.first, { min_z, layer.first.second }); - }); + [layer, this](coordf_t min_z) + { + if (fabs(min_z - layer.first.first) < EPSILON) { + m_selection_type = sitUndef; + return false; // LayersList would not be updated/recreated + } + + // data for next focusing + m_last_edited_range = { min_z, layer.first.second }; + m_selection_type = sitMinZ; + + wxGetApp().obj_list()->edit_layer_range(layer.first, m_last_edited_range); + return true; // LayersList will be updated/recreated + } ); + + if (is_last_edited_range && m_selection_type == sitMinZ) { + temp->SetFocus(); + temp->SetInsertionPointEnd(); + } m_grid_sizer->Add(temp); // Add control for the "Max Z" + temp = new LayerRangeEditor(m_parent, double_to_string(layer.first.second), - [layer](coordf_t max_z) { - wxGetApp().obj_list()->edit_layer_range(layer.first, { layer.first.first, max_z }); + [layer, this](coordf_t max_z) + { + if (fabs(max_z - layer.first.second) < EPSILON) { + m_selection_type = sitUndef; + return false; // LayersList would not be updated/recreated + } + + // data for next focusing + m_last_edited_range = { layer.first.first, max_z }; + m_selection_type = sitMaxZ; + + wxGetApp().obj_list()->edit_layer_range(layer.first, m_last_edited_range); + return true; // LayersList will not be updated/recreated }); + + if (is_last_edited_range && m_selection_type == sitMaxZ) { + temp->SetFocus(); + temp->SetInsertionPointEnd(); + } m_grid_sizer->Add(temp); // Add control for the "Layer height" + temp = new LayerRangeEditor(m_parent, double_to_string(layer.second.option("layer_height")->getFloat()), - [layer](coordf_t layer_height) { + [layer, this](coordf_t layer_height) + { wxGetApp().obj_list()->edit_layer_range(layer.first, layer_height); - }, false ); + return false; // LayersList would not be updated/recreated + }); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(temp); @@ -169,8 +208,7 @@ void ObjectLayers::msw_rescale() LayerRangeEditor::LayerRangeEditor( wxWindow* parent, const wxString& value, - std::function edit_fn, - const bool deletable_after_change + std::function edit_fn ) : wxTextCtrl(parent, wxID_ANY, value, wxDefaultPosition, wxSize(field_width * em_unit(parent), wxDefaultCoord), wxTE_PROCESS_ENTER) @@ -179,18 +217,23 @@ LayerRangeEditor::LayerRangeEditor( wxWindow* parent, this->Bind(wxEVT_TEXT_ENTER, ([this, edit_fn](wxEvent& e) { - m_enter_pressed = true; - edit_fn(get_value()); + m_enter_pressed = true; + // If LayersList wasn't updated/recreated, we can call wxEVT_KILL_FOCUS.Skip() + if ( !edit_fn(get_value()) ) + m_call_kill_focus = true; }), this->GetId()); - this->Bind(wxEVT_KILL_FOCUS, ([this, edit_fn, deletable_after_change](wxEvent& e) + this->Bind(wxEVT_KILL_FOCUS, ([this, edit_fn](wxEvent& e) { - if (!deletable_after_change) - e.Skip(); if (!m_enter_pressed) { m_enter_pressed = false; - edit_fn(get_value()); + + // If LayersList wasn't updated/recreated, we should call e.Skip() + if ( !edit_fn(get_value()) ) + e.Skip(); } + else if (m_call_kill_focus) + e.Skip(); }), this->GetId()); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index f34dd13141..e69a6173af 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -14,16 +14,16 @@ class ConfigOptionsGroup; class LayerRangeEditor : public wxTextCtrl { - bool m_enter_pressed { false }; + bool m_enter_pressed { false }; + bool m_call_kill_focus { false }; + public: LayerRangeEditor( wxWindow* parent, const wxString& value = wxEmptyString, - std::function edit_fn = [](coordf_t) {}, - const bool deletable_after_change = true + std::function edit_fn = [](coordf_t) {return false; } ); ~LayerRangeEditor() {} - private: coordf_t get_value(); }; @@ -34,7 +34,16 @@ class ObjectLayers : public OG_Settings ScalableBitmap m_bmp_add; ModelObject* m_object {nullptr}; - wxFlexGridSizer* m_grid_sizer; + wxFlexGridSizer* m_grid_sizer; + std::pair m_last_edited_range; + + enum SelectedItemType + { + sitUndef, + sitMinZ, + sitMaxZ, + sitLayerHeight, + } m_selection_type {sitUndef}; public: ObjectLayers(wxWindow* parent); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 96d071d54b..04423e359e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2212,7 +2212,7 @@ void ObjectList::del_layer_range(const std::pair& range) const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return; - t_layer_height_ranges& ranges = object(obj_idx)->layer_height_ranges; + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; wxDataViewItem selectable_item = GetSelection(); int layer_idx = 0; @@ -2221,10 +2221,10 @@ void ObjectList::del_layer_range(const std::pair& range) selectable_item = m_objects_model->GetParent(selectable_item); else { // May be not a best solution #ys_FIXME - t_layer_height_ranges::iterator layer_selected = ranges.find(range); - t_layer_height_ranges::iterator it = ranges.begin(); + t_layer_config_ranges::iterator layer_selected = ranges.find(range); + t_layer_config_ranges::iterator it = ranges.begin(); while (it != layer_selected) { - it++; + ++it; layer_idx++; } } From 1090105b6879b0f0212abd4259bd54e86471e60f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 5 Jun 2019 11:50:59 +0200 Subject: [PATCH 045/627] Experiments with updating layer_config_ranges from UI + Fixed OSX build --- src/libslic3r/Model.cpp | 2 ++ src/libslic3r/Print.cpp | 6 ++++-- src/libslic3r/PrintObject.cpp | 5 +++-- src/libslic3r/Slicing.cpp | 13 +++++++++---- src/libslic3r/Slicing.hpp | 3 ++- src/slic3r/GUI/GUI_ObjectLayers.hpp | 10 +++++++--- src/slic3r/GUI/GUI_ObjectList.hpp | 15 +++++++++------ src/slic3r/GUI/Selection.cpp | 3 ++- 8 files changed, 38 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3b1bd5df22..f31ae27dbc 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -600,6 +600,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->sla_support_points = rhs.sla_support_points; this->sla_points_status = rhs.sla_points_status; this->layer_height_ranges = rhs.layer_height_ranges; + this->layer_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment this->layer_height_profile = rhs.layer_height_profile; this->origin_translation = rhs.origin_translation; m_bounding_box = rhs.m_bounding_box; @@ -636,6 +637,7 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs) this->sla_support_points = std::move(rhs.sla_support_points); this->sla_points_status = std::move(rhs.sla_points_status); this->layer_height_ranges = std::move(rhs.layer_height_ranges); + this->layer_config_ranges = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment this->layer_height_profile = std::move(rhs.layer_height_profile); this->origin_translation = std::move(rhs.origin_translation); m_bounding_box = std::move(rhs.m_bounding_box); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index f9129f15a1..ed4af39957 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -874,7 +874,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); if (model_parts_differ || modifiers_differ || model_object.origin_translation != model_object_new.origin_translation || - model_object.layer_height_ranges != model_object_new.layer_height_ranges || +// model_object.layer_height_ranges != model_object_new.layer_height_ranges || + model_object.layer_config_ranges != model_object_new.layer_config_ranges || // #ys_FIXME_experiment model_object.layer_height_profile != model_object_new.layer_height_profile) { // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); @@ -1227,7 +1228,8 @@ std::string Print::validate() const bool has_custom_layering = false; std::vector> layer_height_profiles; for (const PrintObject *object : m_objects) { - has_custom_layering = ! object->model_object()->layer_height_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); +// has_custom_layering = ! object->model_object()->layer_height_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); + has_custom_layering = ! object->model_object()->layer_config_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); // #ys_FIXME_experiment if (has_custom_layering) { layer_height_profiles.assign(m_objects.size(), std::vector()); break; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 660a2d9398..c12861d219 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1434,8 +1434,9 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c // if (this->layer_height_profile.empty()) layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_height_ranges, model_object.volumes); else - layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_height_ranges); - updated = true; +// layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_height_ranges); + layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges); // #ys_FIXME_experiment + updated = true; } return updated; } diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 3a05e9d8af..b4c78ad09e 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -157,20 +157,25 @@ SlicingParameters SlicingParameters::create_from_config( // in the height profile and the printed object may be lifted by the raft thickness at the time of the G-code generation. std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges) +// const t_layer_height_ranges &layer_height_ranges) + const t_layer_config_ranges &layer_config_ranges) // #ys_FIXME_experiment { // 1) If there are any height ranges, trim one by the other to make them non-overlapping. Insert the 1st layer if fixed. std::vector> ranges_non_overlapping; - ranges_non_overlapping.reserve(layer_height_ranges.size() * 4); +// ranges_non_overlapping.reserve(layer_height_ranges.size() * 4); + ranges_non_overlapping.reserve(layer_config_ranges.size() * 4); // #ys_FIXME_experiment if (slicing_params.first_object_layer_height_fixed()) ranges_non_overlapping.push_back(std::pair( t_layer_height_range(0., slicing_params.first_object_layer_height), slicing_params.first_object_layer_height)); // The height ranges are sorted lexicographically by low / high layer boundaries. - for (t_layer_height_ranges::const_iterator it_range = layer_height_ranges.begin(); it_range != layer_height_ranges.end(); ++ it_range) { +// for (t_layer_height_ranges::const_iterator it_range = layer_height_ranges.begin(); it_range != layer_height_ranges.end(); ++ it_range) { + for (t_layer_config_ranges::const_iterator it_range = layer_config_ranges.begin(); + it_range != layer_config_ranges.end(); ++ it_range) { // #ys_FIXME_experiment coordf_t lo = it_range->first.first; coordf_t hi = std::min(it_range->first.second, slicing_params.object_print_z_height()); - coordf_t height = it_range->second; +// coordf_t height = it_range->second; + coordf_t height = it_range->second.option("layer_height")->getFloat(); // #ys_FIXME_experiment if (! ranges_non_overlapping.empty()) // Trim current low with the last high. lo = std::max(lo, ranges_non_overlapping.back().first.second); diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index c3278dc0d8..ea5413e9c6 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -135,7 +135,8 @@ typedef std::map t_layer_config_ranges extern std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges); +// const t_layer_height_ranges &layer_height_ranges); + const t_layer_config_ranges &layer_config_ranges); extern std::vector layer_height_profile_adaptive( const SlicingParameters &slicing_params, diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index e69a6173af..aa09e64a6d 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -12,6 +12,10 @@ class ModelObject; namespace GUI { class ConfigOptionsGroup; +typedef double coordf_t; +typedef std::pair t_layer_height_range; +typedef std::map t_layer_config_ranges; + class LayerRangeEditor : public wxTextCtrl { bool m_enter_pressed { false }; @@ -34,8 +38,8 @@ class ObjectLayers : public OG_Settings ScalableBitmap m_bmp_add; ModelObject* m_object {nullptr}; - wxFlexGridSizer* m_grid_sizer; - std::pair m_last_edited_range; + wxFlexGridSizer* m_grid_sizer; + t_layer_height_range m_last_edited_range; enum SelectedItemType { @@ -49,7 +53,7 @@ public: ObjectLayers(wxWindow* parent); ~ObjectLayers() {} - wxSizer* create_layer_without_buttons(const std::map, DynamicPrintConfig>::value_type& layer); + wxSizer* create_layer_without_buttons(const t_layer_config_ranges::value_type& layer); void create_layer(int id); void create_layers_list(); void update_layers_list(); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index c970a23a00..24c9f65b29 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -33,6 +33,9 @@ typedef std::map< std::string, std::vector< std::pair typedef std::vector ModelVolumePtrs; +typedef double coordf_t; +typedef std::pair t_layer_height_range; + namespace GUI { wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); @@ -271,14 +274,14 @@ public: // Remove objects/sub-object from the list void remove(); - void del_layer_range(const std::pair& range); - void add_layer_range(const std::pair& range); - void add_layer_item (const std::pair& range, + void del_layer_range(const t_layer_height_range& range); + void add_layer_range(const t_layer_height_range& range); + void add_layer_item (const t_layer_height_range& range, const wxDataViewItem layers_item, const int layer_idx = -1); - void edit_layer_range(const std::pair& range, coordf_t layer_height); - void edit_layer_range(const std::pair& range, - const std::pair& new_range); + void edit_layer_range(const t_layer_height_range& range, coordf_t layer_height); + void edit_layer_range(const t_layer_height_range& range, + const t_layer_height_range& new_range); void init_objects(); bool multiple_selection() const ; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index ff994c32d5..0f364f8a12 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1126,7 +1126,8 @@ void Selection::copy_to_clipboard() dst_object->config = src_object->config; dst_object->sla_support_points = src_object->sla_support_points; dst_object->sla_points_status = src_object->sla_points_status; - dst_object->layer_height_ranges = src_object->layer_height_ranges; +// dst_object->layer_height_ranges = src_object->layer_height_ranges; + dst_object->layer_config_ranges = src_object->layer_config_ranges; // #ys_FIXME_experiment dst_object->layer_height_profile = src_object->layer_height_profile; dst_object->origin_translation = src_object->origin_translation; From 401999b68b3e8e625356a9fc017427aa0903432f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 5 Jun 2019 12:32:59 +0200 Subject: [PATCH 046/627] Next try to fix OSX build --- src/slic3r/GUI/GUI_ObjectLayers.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index aa09e64a6d..0b404be4f6 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -9,13 +9,13 @@ class wxBoxSizer; namespace Slic3r { class ModelObject; -namespace GUI { -class ConfigOptionsGroup; - typedef double coordf_t; typedef std::pair t_layer_height_range; typedef std::map t_layer_config_ranges; +namespace GUI { +class ConfigOptionsGroup; + class LayerRangeEditor : public wxTextCtrl { bool m_enter_pressed { false }; From 67ed89c2405a242370066993c16e26fa26fdb6c4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 5 Jun 2019 13:52:53 +0200 Subject: [PATCH 047/627] Fixed OSX build and a bug when for part is able to add "layer_height" option --- src/slic3r/GUI/GUI_ObjectLayers.hpp | 10 +++++++--- src/slic3r/GUI/GUI_ObjectList.cpp | 12 +++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 0b404be4f6..bec2a1f491 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -4,18 +4,22 @@ #include "GUI_ObjectSettings.hpp" #include "wxExtensions.hpp" +#ifdef __WXOSX__ +#include "..\libslic3r\PrintConfig.hpp" +#endif + class wxBoxSizer; namespace Slic3r { class ModelObject; +namespace GUI { +class ConfigOptionsGroup; + typedef double coordf_t; typedef std::pair t_layer_height_range; typedef std::map t_layer_config_ranges; -namespace GUI { -class ConfigOptionsGroup; - class LayerRangeEditor : public wxTextCtrl { bool m_enter_pressed { false }; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 04423e359e..b7a5820622 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1035,7 +1035,17 @@ void ObjectList::get_settings_choice(const wxString& category_name) void ObjectList::get_freq_settings_choice(const wxString& bundle_name) { - const std::vector& options = get_options_for_bundle(bundle_name); + std::vector options = get_options_for_bundle(bundle_name); + + /* Because of we couldn't edited layer_height for ItVolume and itLayer from settings list, + * correct options according to the selected item type : + * remove "layer_height" option + */ + if (m_objects_model->GetItemType(GetSelection()) & (itVolume | itLayer) && bundle_name == _("Layers and Perimeters")) { + const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); + if (layer_height_it != options.end()) + options.erase(layer_height_it); + } assert(m_config); auto opt_keys = m_config->keys(); From 12797f2aa8a3d663b2c07a98d2194603140daade Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 5 Jun 2019 15:50:27 +0200 Subject: [PATCH 048/627] Re-adding missing file --- src/libslic3r/SLA/bicubic.h | 186 ++++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 src/libslic3r/SLA/bicubic.h diff --git a/src/libslic3r/SLA/bicubic.h b/src/libslic3r/SLA/bicubic.h new file mode 100644 index 0000000000..870d00dbd6 --- /dev/null +++ b/src/libslic3r/SLA/bicubic.h @@ -0,0 +1,186 @@ +#ifndef BICUBIC_HPP +#define BICUBIC_HPP + +#include +#include +#include + +#include + +namespace Slic3r { + +namespace BicubicInternal { + // Linear kernel, to be able to test cubic methods with hat kernels. + template + struct LinearKernel + { + typedef T FloatType; + + static T a00() { return T(0.); } + static T a01() { return T(0.); } + static T a02() { return T(0.); } + static T a03() { return T(0.); } + static T a10() { return T(1.); } + static T a11() { return T(-1.); } + static T a12() { return T(0.); } + static T a13() { return T(0.); } + static T a20() { return T(0.); } + static T a21() { return T(1.); } + static T a22() { return T(0.); } + static T a23() { return T(0.); } + static T a30() { return T(0.); } + static T a31() { return T(0.); } + static T a32() { return T(0.); } + static T a33() { return T(0.); } + }; + + // Interpolation kernel aka Catmul-Rom aka Keyes kernel. + template + struct CubicCatmulRomKernel + { + typedef T FloatType; + + static T a00() { return 0; } + static T a01() { return (T)-0.5; } + static T a02() { return (T) 1.; } + static T a03() { return (T)-0.5; } + static T a10() { return (T) 1.; } + static T a11() { return 0; } + static T a12() { return (T)-5./2.; } + static T a13() { return (T) 3./2.; } + static T a20() { return 0; } + static T a21() { return (T) 0.5; } + static T a22() { return (T) 2.; } + static T a23() { return (T)-3./2.; } + static T a30() { return 0; } + static T a31() { return 0; } + static T a32() { return (T)-0.5; } + static T a33() { return (T) 0.5; } + }; + + // B-spline kernel + template + struct CubicBSplineKernel + { + typedef T FloatType; + + static T a00() { return (T) 1./6.; } + static T a01() { return (T) -3./6.; } + static T a02() { return (T) 3./6.; } + static T a03() { return (T) -1./6.; } + static T a10() { return (T) 4./6.; } + static T a11() { return 0; } + static T a12() { return (T) -6./6.; } + static T a13() { return (T) 3./6.; } + static T a20() { return (T) 1./6.; } + static T a21() { return (T) 3./6.; } + static T a22() { return (T) 3./6.; } + static T a23() { return (T)- 3./6.; } + static T a30() { return 0; } + static T a31() { return 0; } + static T a32() { return 0; } + static T a33() { return (T) 1./6.; } + }; + + template + inline T clamp(T a, T lower, T upper) + { + return (a < lower) ? lower : + (a > upper) ? upper : a; + } +} + +template +struct CubicKernel +{ + typedef typename KERNEL KernelInternal; + typedef typename KERNEL::FloatType FloatType; + + static FloatType kernel(FloatType x) + { + x = fabs(x); + if (x >= (FloatType)2.) + return 0.0f; + if (x <= (FloatType)1.) { + FloatType x2 = x * x; + FloatType x3 = x2 * x; + return KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3; + } + assert(x > (FloatType)1. && x < (FloatType)2.); + x -= (FloatType)1.; + FloatType x2 = x * x; + FloatType x3 = x2 * x; + return KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3; + } + + static FloatType interpolate(FloatType f0, FloatType f1, FloatType f2, FloatType f3, FloatType x) + { + const FloatType x2 = x*x; + const FloatType x3 = x*x*x; + return f0*(KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3) + + f1*(KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3) + + f2*(KERNEL::a20() + KERNEL::a21() * x + KERNEL::a22() * x2 + KERNEL::a23() * x3) + + f3*(KERNEL::a30() + KERNEL::a31() * x + KERNEL::a32() * x2 + KERNEL::a33() * x3); + } +}; + +// Linear splines +typedef CubicKernel> LinearKernelf; +typedef CubicKernel> LinearKerneld; +// Catmul-Rom splines +typedef CubicKernel> CubicCatmulRomKernelf; +typedef CubicKernel> CubicCatmulRomKerneld; +typedef CubicKernel> CubicInterpolationKernelf; +typedef CubicKernel> CubicInterpolationKerneld; +// Cubic B-splines +typedef CubicKernel> CubicBSplineKernelf; +typedef CubicKernel> CubicBSplineKerneld; + +template +static float cubic_interpolate(const Eigen::ArrayBase &F, const typename KERNEL::FloatType pt, const typename KERNEL::FloatType dx) +{ + typedef typename KERNEL::FloatType T; + const int w = int(F.size()); + const int ix = (int)floor(pt); + const T s = pt - (T)ix; + + if (ix > 1 && ix + 2 < w) { + // Inside the fully interpolated region. + return KERNEL::interpolate(F[ix - 1], F[ix], F[ix + 1], F[ix + 2], s); + } + // Transition region. Extend with a constant function. + auto f = [&F, w](x) { return F[BicubicInternal::clamp(x, 0, w - 1)]; } + return KERNEL::interpolate(f(ix - 1), f(ix), f(ix + 1), f(ix + 2), s); +} + +template +static float bicubic_interpolate(const Eigen::MatrixBase &F, const Eigen::Matrix &pt, const typename KERNEL::FloatType dx) +{ + typedef typename KERNEL::FloatType T; + const int w = F.cols(); + const int h = F.rows(); + const int ix = (int)floor(pt[0]); + const int iy = (int)floor(pt[1]); + const T s = pt[0] - (T)ix; + const T t = pt[1] - (T)iy; + + if (ix > 1 && ix + 2 < w && iy > 1 && iy + 2 < h) { + // Inside the fully interpolated region. + return KERNEL::interpolate( + KERNEL::interpolate(F(ix-1,iy-1),F(ix ,iy-1),F(ix+1,iy-1),F(ix+2,iy-1),s), + KERNEL::interpolate(F(ix-1,iy ),F(ix ,iy ),F(ix+1,iy ),F(ix+2,iy ),s), + KERNEL::interpolate(F(ix-1,iy+1),F(ix ,iy+1),F(ix+1,iy+1),F(ix+2,iy+1),s), + KERNEL::interpolate(F(ix-1,iy+2),F(ix ,iy+2),F(ix+1,iy+2),F(ix+2,iy+2),s),t); + } + // Transition region. Extend with a constant function. + auto f = [&f, w, h](int x, int y) { return F(BicubicInternal::clamp(x,0,w-1),BicubicInternal::clamp(y,0,h-1)); } + return KERNEL::interpolate( + KERNEL::interpolate(f(ix-1,iy-1),f(ix ,iy-1),f(ix+1,iy-1),f(ix+2,iy-1),s), + KERNEL::interpolate(f(ix-1,iy ),f(ix ,iy ),f(ix+1,iy ),f(ix+2,iy ),s), + KERNEL::interpolate(f(ix-1,iy+1),f(ix ,iy+1),f(ix+1,iy+1),f(ix+2,iy+1),s), + KERNEL::interpolate(f(ix-1,iy+2),f(ix ,iy+2),f(ix+1,iy+2),f(ix+2,iy+2),s),t); +} + +} // namespace Slic3r + +#endif /* BICUBIC_HPP */ From 44c05fa2099b3c0a7daa96181d07a8d61e8f6397 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 5 Jun 2019 16:47:09 +0200 Subject: [PATCH 049/627] Implemented additional settings for layers range (except of "extruder") --- src/slic3r/GUI/GUI_ObjectLayers.hpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 80 +++++++++++++++++++++------ src/slic3r/GUI/GUI_ObjectList.hpp | 2 + src/slic3r/GUI/GUI_ObjectSettings.cpp | 11 ++-- 4 files changed, 72 insertions(+), 23 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index bec2a1f491..55002ff354 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -5,7 +5,7 @@ #include "wxExtensions.hpp" #ifdef __WXOSX__ -#include "..\libslic3r\PrintConfig.hpp" +#include "../libslic3r/PrintConfig.hpp" #endif class wxBoxSizer; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b7a5820622..f68130ddd1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -360,6 +360,21 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons (*m_objects)[obj_idx]->config; } +const t_layer_height_range& ObjectList::get_layer_range_from_item(const wxDataViewItem layer_item, const int obj_idx) const +{ + ModelObject* object = (*m_objects)[obj_idx]; + t_layer_config_ranges::iterator layer_range = object->layer_config_ranges.begin(); + int id = m_objects_model->GetLayerIdByItem(layer_item); + + // May be not a best solution #ys_FIXME + while (id > 0 && layer_range != object->layer_config_ranges.end()) { + ++layer_range; + id--; + } + + return layer_range->first; +} + wxDataViewColumn* ObjectList::create_objects_list_extruder_column(int extruders_count) { wxArrayString choices; @@ -1037,11 +1052,11 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) { std::vector options = get_options_for_bundle(bundle_name); - /* Because of we couldn't edited layer_height for ItVolume and itLayer from settings list, + /* Because of we couldn't edited layer_height for ItVolume from settings list, * correct options according to the selected item type : * remove "layer_height" option */ - if (m_objects_model->GetItemType(GetSelection()) & (itVolume | itLayer) && bundle_name == _("Layers and Perimeters")) { + if ((m_objects_model->GetItemType(GetSelection()) & itVolume) && bundle_name == _("Layers and Perimeters")) { const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); if (layer_height_it != options.end()) options.erase(layer_height_it); @@ -1833,7 +1848,7 @@ void ObjectList::layers_editing() t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; if (ranges.empty()) - ranges[{ 0.0f, 0.2f }] = *DynamicPrintConfig::new_from_defaults_keys({"layer_height"});// some default value + ranges[{ 0.0f, 0.6f }] = get_default_layer_config(obj_idx); // and create Layer item(s) according to the layer_config_ranges for (const auto range : ranges) @@ -1845,6 +1860,18 @@ void ObjectList::layers_editing() Expand(layers_item); } +DynamicPrintConfig ObjectList::get_default_layer_config(const int obj_idx) +{ + DynamicPrintConfig config; + coordf_t layer_height = object(obj_idx)->config.has("layer_height") ? + object(obj_idx)->config.opt_float("layer_height") : + wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_float("layer_height"); + config.set_key_value("layer_height",new ConfigOptionFloat(layer_height)); + config.set_key_value("extruder", new ConfigOptionInt(0)); + + return config; +} + bool ObjectList::get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume) { auto obj_idx = get_selected_obj_idx(); @@ -1937,22 +1964,28 @@ void ObjectList::part_selection_changed() update_and_show_manipulations = true; } else { - auto parent = m_objects_model->GetParent(item); - // Take ID of the parent object to "inform" perl-side which object have to be selected on the scene - obj_idx = m_objects_model->GetIdByItem(parent); + obj_idx = m_objects_model->GetObjectIdByItem(item); + const ItemType type = m_objects_model->GetItemType(item); if (type & itSettings) { - if (m_objects_model->GetParent(parent) == wxDataViewItem(0)) { + const auto parent = m_objects_model->GetParent(item); + const ItemType parent_type = m_objects_model->GetItemType(parent); + + if (parent_type & itObject) { og_name = _(L("Object Settings to modify")); m_config = &(*m_objects)[obj_idx]->config; } - else { + else if (parent_type & itVolume) { og_name = _(L("Part Settings to modify")); - auto main_parent = m_objects_model->GetParent(parent); - obj_idx = m_objects_model->GetIdByItem(main_parent); - const auto volume_id = m_objects_model->GetVolumeIdByItem(parent); + volume_id = m_objects_model->GetVolumeIdByItem(parent); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; } + else if (parent_type & itLayer) { + og_name = _(L("Layer range Settings to modify")); + + const t_layer_height_range& layer_height_range = get_layer_range_from_item(parent, obj_idx); + m_config = &(*m_objects)[obj_idx]->layer_config_ranges[layer_height_range]; + } update_and_show_settings = true; } else if (type & itVolume) { @@ -1966,12 +1999,14 @@ void ObjectList::part_selection_changed() update_and_show_manipulations = true; // fill m_config by object's values - const int obj_idx_ = m_objects_model->GetObjectIdByItem(item); - m_config = &(*m_objects)[obj_idx_]->config; + m_config = &(*m_objects)[obj_idx]->config; } else if (type & (itLayerRoot|itLayer)) { og_name = type & itLayerRoot ? _(L("Layers Editing")) : _(L("Layer Editing")); update_and_show_layers = true; + + const t_layer_height_range& layer_height_range = get_layer_range_from_item(item, obj_idx); + m_config = &(*m_objects)[obj_idx]->layer_config_ranges[layer_height_range]; } } } @@ -2259,8 +2294,8 @@ void ObjectList::add_layer_range(const t_layer_height_range& range) if (selected_range->first == last_range->first) { - const t_layer_height_range new_range = { last_range->first.second, last_range->first.second + 0.2f }; - ranges[new_range] = last_range->second; + const t_layer_height_range new_range = { last_range->first.second, last_range->first.second + 0.5f }; + ranges[new_range] = get_default_layer_config(obj_idx); add_layer_item(new_range, layers_item); } else @@ -2300,13 +2335,13 @@ void ObjectList::add_layer_range(const t_layer_height_range& range) add_layer_item(new_range, layers_item, layer_idx); new_range = { selected_range->first.second, midl_layer }; - ranges[new_range] = selected_range->second; + ranges[new_range] = get_default_layer_config(obj_idx); add_layer_item(new_range, layers_item, layer_idx); } else { const t_layer_height_range new_range = { selected_range->first.second, next_range->first.first }; - ranges[new_range] = selected_range->second; + ranges[new_range] = get_default_layer_config(obj_idx); add_layer_item(new_range, layers_item, layer_idx); } } @@ -2322,7 +2357,16 @@ void ObjectList::add_layer_item(const t_layer_height_range& range, const int layer_idx /* = -1*/) { const std::string label = (boost::format(" %.2f-%.2f ") % range.first % range.second).str(); - m_objects_model->AddLayersChild(layers_item, label, layer_idx); + const wxDataViewItem layer_item = m_objects_model->AddLayersChild(layers_item, label, layer_idx); + + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + +// auto opt_keys = object(obj_idx)->layer_config_ranges[range].keys(); + const DynamicPrintConfig& config = object(obj_idx)->layer_config_ranges[range]; +// if (!opt_keys.empty() && !(opt_keys.size() == 2 && opt_keys[0] == "layer_height" && opt_keys[1] == "extruder")) + if (config.keys().size() > 2) + select_item(m_objects_model->AddSettingsChild(layer_item)); } void ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 24c9f65b29..ad7587a011 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -235,6 +235,7 @@ public: bool del_subobject_from_object(const int obj_idx, const int idx, const int type); void split(); void layers_editing(); + DynamicPrintConfig get_default_layer_config(const int obj_idx); bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); bool is_splittable(); bool selected_instances_of_same_object(); @@ -244,6 +245,7 @@ public: wxBoxSizer* get_sizer() {return m_sizer;} int get_selected_obj_idx() const; DynamicPrintConfig& get_item_config(const wxDataViewItem& item) const; + const t_layer_height_range& get_layer_range_from_item(const wxDataViewItem layer_item, const int obj_idx) const; void changed_object(const int obj_idx = -1) const; void part_selection_changed(); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index a4aa7dec2e..ab2614895c 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -72,6 +72,8 @@ void ObjectSettings::update_settings_list() auto config = wxGetApp().obj_list()->config(); const auto item = objects_ctrl->GetSelection(); + const bool is_layers_range_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itLayer; + if (item && !objects_ctrl->multiple_selection() && config && objects_model->IsSettingsItem(item)) { @@ -119,7 +121,8 @@ void ObjectSettings::update_settings_list() } for (auto& cat : cat_options) { - if (cat.second.size() == 1 && cat.second[0] == "extruder") + if (cat.second.size() == 1 && + (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height")) continue; auto optgroup = std::make_shared(m_og->ctrl_parent(), _(cat.first), config, false, extra_column); @@ -129,14 +132,14 @@ void ObjectSettings::update_settings_list() optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) { wxGetApp().obj_list()->changed_object(); }; - const bool is_extriders_cat = cat.first == "Extruders"; + const bool is_extruders_cat = cat.first == "Extruders"; for (auto& opt : cat.second) { - if (opt == "extruder") + if (opt == "extruder" || is_layers_range_settings && opt == "layer_height") continue; Option option = optgroup->get_option(opt); option.opt.width = 12; - if (is_extriders_cat) + if (is_extruders_cat) option.opt.max = wxGetApp().extruders_cnt(); optgroup->append_single_option_line(option); } From 6136fe7d92e386f1a6a6ab227f49c7d6a68abbb3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 5 Jun 2019 19:19:49 +0200 Subject: [PATCH 050/627] Future-proof qhull dependency handling --- deps/CMakeLists.txt | 2 + deps/deps-unix-common.cmake | 13 ++++++ deps/deps-windows.cmake | 27 +++++++++++ deps/qhull-mods.patch | 85 ++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 3 -- src/libslic3r/TriangleMesh.cpp | 2 +- src/qhull/CMakeLists.txt | 23 ++++++++- 7 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 deps/qhull-mods.patch diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 5720901578..d07c8d1e85 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -84,6 +84,7 @@ if (MSVC) dep_wxwidgets dep_gtest dep_nlopt + dep_qhull dep_zlib # on Windows we still need zlib ) @@ -97,6 +98,7 @@ else() dep_wxwidgets dep_gtest dep_nlopt + dep_qhull ) endif() diff --git a/deps/deps-unix-common.cmake b/deps/deps-unix-common.cmake index 3cf843f73d..d6a92efcb3 100644 --- a/deps/deps-unix-common.cmake +++ b/deps/deps-unix-common.cmake @@ -32,3 +32,16 @@ ExternalProject_Add(dep_nlopt -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local ${DEP_CMAKE_OPTS} ) + +find_package(Git REQUIRED) + +ExternalProject_Add(dep_qhull + EXCLUDE_FROM_ALL 1 + URL "https://github.com/qhull/qhull/archive/v7.2.1.tar.gz" + URL_HASH SHA256=6fc251e0b75467e00943bfb7191e986fce0e1f8f6f0251f9c6ce5a843821ea78 + CMAKE_ARGS + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + ${DEP_CMAKE_OPTS} + PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/qhull-mods.patch +) diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index b3b31e5f37..df47a0f444 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -199,6 +199,33 @@ if (${DEP_DEBUG}) ) endif () +find_package(Git REQUIRED) + +ExternalProject_Add(dep_qhull + EXCLUDE_FROM_ALL 1 + URL "https://github.com/qhull/qhull/archive/v7.2.1.tar.gz" + URL_HASH SHA256=6fc251e0b75467e00943bfb7191e986fce0e1f8f6f0251f9c6ce5a843821ea78 + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_DEBUG_POSTFIX=d + PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/qhull-mods.patch + BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) + +if (${DEP_DEBUG}) + ExternalProject_Get_Property(dep_qhull BINARY_DIR) + ExternalProject_Add_Step(dep_qhull build_debug + DEPENDEES build + DEPENDERS install + COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj + WORKING_DIRECTORY "${BINARY_DIR}" + ) +endif () + if (${DEPS_BITS} EQUAL 32) set(DEP_WXWIDGETS_TARGET "") diff --git a/deps/qhull-mods.patch b/deps/qhull-mods.patch new file mode 100644 index 0000000000..b1ea7159bf --- /dev/null +++ b/deps/qhull-mods.patch @@ -0,0 +1,85 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 59dff41..20c2ec5 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -61,7 +61,7 @@ + # $DateTime: 2016/01/18 19:29:17 $$Author: bbarber $ + + project(qhull) +-cmake_minimum_required(VERSION 2.6) ++cmake_minimum_required(VERSION 3.0) + + # Define qhull_VERSION in CMakeLists.txt, Makefile, qhull-exports.def, qhull_p-exports.def, qhull_r-exports.def, qhull-warn.pri + set(qhull_VERSION2 "2015.2 2016/01/18") # not used, See global.c, global_r.c, rbox.c, rbox_r.c +@@ -610,10 +610,48 @@ add_test(NAME user_eg3 + # Define install + # --------------------------------------- + +-install(TARGETS ${qhull_TARGETS_INSTALL} ++install(TARGETS ${qhull_TARGETS_INSTALL} EXPORT QhullTargets + RUNTIME DESTINATION ${BIN_INSTALL_DIR} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} +- ARCHIVE DESTINATION ${LIB_INSTALL_DIR}) ++ ARCHIVE DESTINATION ${LIB_INSTALL_DIR} ++ INCLUDES DESTINATION include) ++ ++include(CMakePackageConfigHelpers) ++ ++write_basic_package_version_file( ++ "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfigVersion.cmake" ++ VERSION ${qhull_VERSION} ++ COMPATIBILITY AnyNewerVersion ++) ++ ++export(EXPORT QhullTargets ++ FILE "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullTargets.cmake" ++ NAMESPACE Qhull:: ++) ++ ++configure_file(Config.cmake.in ++ "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfig.cmake" ++ @ONLY ++) ++ ++set(ConfigPackageLocation lib/cmake/Qhull) ++install(EXPORT QhullTargets ++ FILE ++ QhullTargets.cmake ++ NAMESPACE ++ Qhull:: ++ DESTINATION ++ ${ConfigPackageLocation} ++) ++install( ++ FILES ++ "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfig.cmake" ++ "${CMAKE_CURRENT_BINARY_DIR}/Qhull/QhullConfigVersion.cmake" ++ DESTINATION ++ ${ConfigPackageLocation} ++ COMPONENT ++ Devel ++) + + install(FILES ${libqhull_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/libqhull) + install(FILES ${libqhull_DOC} DESTINATION ${INCLUDE_INSTALL_DIR}/libqhull) +diff --git a/Config.cmake.in b/Config.cmake.in +new file mode 100644 +index 0000000..bc92bfe +--- /dev/null ++++ b/Config.cmake.in +@@ -0,0 +1,2 @@ ++include("${CMAKE_CURRENT_LIST_DIR}/QhullTargets.cmake") ++ +diff --git a/src/libqhull_r/user_r.h b/src/libqhull_r/user_r.h +index fc105b9..7cca65a 100644 +--- a/src/libqhull_r/user_r.h ++++ b/src/libqhull_r/user_r.h +@@ -139,7 +139,7 @@ Code flags -- + REALfloat = 1 all numbers are 'float' type + = 0 all numbers are 'double' type + */ +-#define REALfloat 0 ++#define REALfloat 1 + + #if (REALfloat == 1) + #define realT float diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4f6959623b..2f2483eca8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,9 +17,6 @@ add_subdirectory(semver) set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d") add_subdirectory(libnest2d) -include_directories(${LIBDIR}/qhull/src) -#message(STATUS ${LIBDIR}/qhull/src) - add_subdirectory(libslic3r) if (SLIC3R_GUI) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 4d35cabca2..6581a83201 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -578,7 +578,7 @@ TriangleMesh TriangleMesh::convex_hull_3d() const { // iterate through facet's vertices orgQhull::QhullPoint p = vertices[i].point(); - const float* coords = p.coordinates(); + const auto* coords = p.coordinates(); dst_vertices.emplace_back(coords[0], coords[1], coords[2]); } unsigned int size = (unsigned int)dst_vertices.size(); diff --git a/src/qhull/CMakeLists.txt b/src/qhull/CMakeLists.txt index a3e7da1264..262214a5c4 100644 --- a/src/qhull/CMakeLists.txt +++ b/src/qhull/CMakeLists.txt @@ -8,6 +8,22 @@ # Created by modification of the original qhull CMakeLists. # Lukas Matena (25.7.2018), lukasmatena@seznam.cz +# see bug report: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=925540 + +# find_package(Qhull 7.2 QUIET) + +add_library(qhull INTERFACE) + +if(Qhull_FOUND) + +message(STATUS "Using qhull from system.") +if(SLICER_STATIC) + target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r) +else() + target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhull_r) +endif() + +else(Qhull_FOUND) project(qhull) cmake_minimum_required(VERSION 2.6) @@ -112,7 +128,7 @@ set(libqhull_SOURCES ################################################## # combined library (reentrant qhull and qhullcpp) for Slic3r: -set(qhull_STATIC qhull) +set(qhull_STATIC qhullstatic) add_library(${qhull_STATIC} STATIC ${libqhull_SOURCES}) set_target_properties(${qhull_STATIC} PROPERTIES VERSION ${qhull_VERSION}) @@ -124,3 +140,8 @@ endif(UNIX) # LIBDIR is defined in the main xs CMake file: target_include_directories(${qhull_STATIC} PRIVATE ${LIBDIR}/qhull/src) + +target_link_libraries(qhull INTERFACE ${qhull_STATIC}) +target_include_directories(qhull INTERFACE ${LIBDIR}/qhull/src) + +endif() From c9a847a6a609d405d6798e702eaaeabb62f1e539 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 6 Jun 2019 11:40:35 +0200 Subject: [PATCH 051/627] Support for Visual Studio 2019 Community --- Build.PL | 2 ++ CMakeLists.txt | 4 +++- deps/deps-windows.cmake | 27 ++++++++++++++++++++++----- xs/src/xsinit.h | 3 +++ 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Build.PL b/Build.PL index ef17ec046b..1c3b0e3a7f 100644 --- a/Build.PL +++ b/Build.PL @@ -16,6 +16,8 @@ my %prereqs = qw( ExtUtils::MakeMaker 6.80 ExtUtils::ParseXS 3.22 ExtUtils::XSpp 0 + ExtUtils::XSpp::Cmd 0 + ExtUtils::CppGuess 0 ExtUtils::Typemaps 0 ExtUtils::Typemaps::Basic 0 File::Basename 0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 719bdd04e7..de435813d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -241,7 +241,9 @@ if(NOT WIN32) endif() find_package(Boost ${MINIMUM_BOOST_VERSION} REQUIRED COMPONENTS system filesystem thread log locale regex) if(Boost_FOUND) -# include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) +# message("Boost include dir: ${Boost_INCLUDE_DIRS}") +# message("Boost library dirs: ${Boost_LIBRARY_DIRS}") +# message("Boost libraries: ${Boost_LIBRARIES}") if (APPLE) # BOOST_ASIO_DISABLE_KQUEUE : prevents a Boost ASIO bug on OS X: https://svn.boost.org/trac/boost/ticket/5339 add_definitions(-DBOOST_ASIO_DISABLE_KQUEUE) diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index b3b31e5f37..f20ffcc774 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -1,21 +1,34 @@ - +# https://cmake.org/cmake/help/latest/variable/MSVC_VERSION.html if (MSVC_VERSION EQUAL 1800) +# 1800 = VS 12.0 (v120 toolset) set(DEP_VS_VER "12") set(DEP_BOOST_TOOLSET "msvc-12.0") elseif (MSVC_VERSION EQUAL 1900) +# 1900 = VS 14.0 (v140 toolset) set(DEP_VS_VER "14") set(DEP_BOOST_TOOLSET "msvc-14.0") -elseif (MSVC_VERSION GREATER 1900) +elseif (MSVC_VERSION LESS 1920) +# 1910-1919 = VS 15.0 (v141 toolset) set(DEP_VS_VER "15") set(DEP_BOOST_TOOLSET "msvc-14.1") +elseif (MSVC_VERSION LESS 1930) +# 1920-1929 = VS 16.0 (v142 toolset) + set(DEP_VS_VER "16") + set(DEP_BOOST_TOOLSET "msvc-14.2") else () message(FATAL_ERROR "Unsupported MSVC version") endif () if (${DEPS_BITS} EQUAL 32) set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER}") + set(DEP_PLATFORM "Win32") else () - set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER} Win64") + if (DEP_VS_VER LESS 16) + set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER} Win64") + else () + set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER}") + endif () + set(DEP_PLATFORM "x64") endif () @@ -28,8 +41,8 @@ endif () ExternalProject_Add(dep_boost EXCLUDE_FROM_ALL 1 - URL "https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.gz" - URL_HASH SHA256=bd0df411efd9a585e5a2212275f8762079fed8842264954675a4fddc46cfcf60 + URL "https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz" + URL_HASH SHA256=882b48708d211a5f48e60b0124cf5863c1534cd544ecd0664bb534a4b5d506e9 BUILD_IN_SOURCE 1 CONFIGURE_COMMAND bootstrap.bat BUILD_COMMAND b2.exe @@ -57,6 +70,7 @@ ExternalProject_Add(dep_tbb URL "https://github.com/wjakob/tbb/archive/a0dc9bf76d0120f917b641ed095360448cabc85b.tar.gz" URL_HASH SHA256=0545cb6033bd1873fcae3ea304def720a380a88292726943ae3b9b207f322efe CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" CMAKE_ARGS -DCMAKE_DEBUG_POSTFIX=_debug -DTBB_BUILD_SHARED=OFF @@ -81,6 +95,7 @@ ExternalProject_Add(dep_gtest URL "https://github.com/google/googletest/archive/release-1.8.1.tar.gz" URL_HASH SHA256=9bf1fe5182a604b4135edc1a425ae356c9ad15e9b23f9f12a02e80184c3a249c CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" CMAKE_ARGS -DBUILD_GMOCK=OFF -Dgtest_force_shared_crt=ON @@ -105,6 +120,7 @@ ExternalProject_Add(dep_nlopt URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz" URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DNLOPT_PYTHON=OFF @@ -133,6 +149,7 @@ ExternalProject_Add(dep_zlib URL "https://zlib.net/zlib-1.2.11.tar.xz" URL_HASH SHA256=4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066 CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" CMAKE_ARGS -DSKIP_INSTALL_FILES=ON # Prevent installation of man pages et al. "-DINSTALL_BIN_DIR=${CMAKE_CURRENT_BINARY_DIR}\\fallout" # I found no better way of preventing zlib from creating & installing DLLs :-/ diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index 506ef0a0be..f14e1262dc 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -69,7 +69,10 @@ extern "C" { #undef fwrite #undef fclose #undef sleep + #undef snprintf + #undef strerror #undef test + #undef times #undef accept #undef wait From c55b4ff586c3d761d0be6faab8aff734d135cd9d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 6 Jun 2019 12:21:38 +0200 Subject: [PATCH 052/627] Small addition to qhull dep handling. --- src/qhull/CMakeLists.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/qhull/CMakeLists.txt b/src/qhull/CMakeLists.txt index 262214a5c4..734bb82950 100644 --- a/src/qhull/CMakeLists.txt +++ b/src/qhull/CMakeLists.txt @@ -10,7 +10,7 @@ # see bug report: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=925540 -# find_package(Qhull 7.2 QUIET) +find_package(Qhull 7.2 QUIET) add_library(qhull INTERFACE) @@ -139,9 +139,7 @@ endif(UNIX) ################################################## # LIBDIR is defined in the main xs CMake file: -target_include_directories(${qhull_STATIC} PRIVATE ${LIBDIR}/qhull/src) - +target_include_directories(${qhull_STATIC} BEFORE PUBLIC ${LIBDIR}/qhull/src) target_link_libraries(qhull INTERFACE ${qhull_STATIC}) -target_include_directories(qhull INTERFACE ${LIBDIR}/qhull/src) endif() From d4fe7b5a962b0aff416e8b8929535034fe9ba69b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 6 Jun 2019 14:27:07 +0200 Subject: [PATCH 053/627] Adding rotating calipers algorithm for minimum are bounding box rotation. Cleanup, fix build on windows and add test for rotcalipers. Try to fix compilation on windows With updates from libnest2d Another build fix. Clean up and add comments. adding rotcalipers test and some cleanup Trying to fix on OSX Fix rotcalipers array indexing Get rid of boost convex hull. Adding helper function 'remove_collinear_points' Importing new libnest2d upgrades. Disable using __int128 in NFP on OSX --- src/libnest2d/CMakeLists.txt | 16 +- src/libnest2d/include/libnest2d.h | 125 ++--- .../libnest2d/backends/clipper/CMakeLists.txt | 13 +- .../backends/clipper/clipper_polygon.hpp | 8 +- .../libnest2d/backends/clipper/geometries.hpp | 117 +--- src/libnest2d/include/libnest2d/common.hpp | 28 + .../include/libnest2d/geometry_traits.hpp | 408 ++++++++++---- .../include/libnest2d/geometry_traits_nfp.hpp | 527 +++--------------- src/libnest2d/include/libnest2d/libnest2d.hpp | 15 +- src/libnest2d/include/libnest2d/optimizer.hpp | 4 +- .../libnest2d/optimizers/nlopt/CMakeLists.txt | 6 +- .../optimizers/optimlib/CMakeLists.txt | 5 - .../libnest2d/placers/bottomleftplacer.hpp | 8 +- .../include/libnest2d/placers/nfpplacer.hpp | 13 +- .../include/libnest2d/utils/boost_alg.hpp | 37 +- .../include/libnest2d/utils/rotcalipers.hpp | 268 +++++++++ src/libnest2d/src/libnest2d.cpp | 23 + src/libnest2d/tests/CMakeLists.txt | 9 +- src/libnest2d/tests/test.cpp | 205 ++++++- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Int128.hpp | 6 + src/libslic3r/MinAreaBoundingBox.cpp | 142 +++++ src/libslic3r/MinAreaBoundingBox.hpp | 59 ++ src/libslic3r/ModelArrange.cpp | 31 +- src/slic3r/GUI/Plater.cpp | 53 +- 25 files changed, 1272 insertions(+), 856 deletions(-) delete mode 100644 src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt create mode 100644 src/libnest2d/include/libnest2d/utils/rotcalipers.hpp create mode 100644 src/libnest2d/src/libnest2d.cpp create mode 100644 src/libslic3r/MinAreaBoundingBox.cpp create mode 100644 src/libslic3r/MinAreaBoundingBox.hpp diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index 2508c984a7..d50c78b171 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -48,6 +48,9 @@ set(LIBNEST2D_SRCFILES ${SRC_DIR}/libnest2d/optimizer.hpp ${SRC_DIR}/libnest2d/utils/metaloop.hpp ${SRC_DIR}/libnest2d/utils/rotfinder.hpp + ${SRC_DIR}/libnest2d/utils/rotcalipers.hpp + ${SRC_DIR}/libnest2d/utils/bigint.hpp + ${SRC_DIR}/libnest2d/utils/rational.hpp ${SRC_DIR}/libnest2d/placers/placer_boilerplate.hpp ${SRC_DIR}/libnest2d/placers/bottomleftplacer.hpp ${SRC_DIR}/libnest2d/placers/nfpplacer.hpp @@ -70,12 +73,10 @@ if(TBB_FOUND) # The Intel TBB library will use the std::exception_ptr feature of C++11. target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0) - target_link_libraries(libnest2d INTERFACE tbb) - # The following breaks compilation on Visual Studio in Debug mode. - #find_package(Threads REQUIRED) - #target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS} - # Threads::Threads - # ) + find_package(Threads REQUIRED) + target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS} + Threads::Threads + ) else() find_package(OpenMP QUIET) @@ -92,10 +93,11 @@ endif() add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES}) target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_GEOMETRIES}Backend) + add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER}) target_link_libraries(libnest2d INTERFACE ${LIBNEST2D_OPTIMIZER}Optimizer) -#target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) +# target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) target_include_directories(libnest2d INTERFACE ${SRC_DIR}) if(NOT LIBNEST2D_HEADER_ONLY) diff --git a/src/libnest2d/include/libnest2d.h b/src/libnest2d/include/libnest2d.h index 4ad7524219..a6eb36a4b0 100644 --- a/src/libnest2d/include/libnest2d.h +++ b/src/libnest2d/include/libnest2d.h @@ -47,6 +47,17 @@ using NfpPlacer = _NfpPlacer; // This supports only box shaped bins using BottomLeftPlacer = placers::_BottomLeftPlacer; +#ifdef LIBNEST2D_STATIC + +extern template class Nester; +extern template class Nester; +extern template PackGroup Nester::execute( + std::vector::iterator, std::vector::iterator); +extern template PackGroup Nester::execute( + std::vector::iterator, std::vector::iterator); + +#endif + template::iterator> @@ -60,19 +71,6 @@ PackGroup nest(Iterator from, Iterator to, return nester.execute(from, to); } -template> -PackGroup nest(Container&& cont, - const typename Placer::BinType& bin, - Coord dist = 0, - const typename Placer::Config& pconf = {}, - const typename Selector::Config& sconf = {}) -{ - return nest(cont.begin(), cont.end(), - bin, dist, pconf, sconf); -} - template::iterator> @@ -90,6 +88,42 @@ PackGroup nest(Iterator from, Iterator to, return nester.execute(from, to); } +#ifdef LIBNEST2D_STATIC + +extern template class Nester; +extern template class Nester; + +extern template PackGroup nest(std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + Coord dist = 0, + const NfpPlacer::Config& pconf, + const FirstFitSelection::Config& sconf); + +extern template PackGroup nest(std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + ProgressFunction prg, + StopCondition scond, + Coord dist = 0, + const NfpPlacer::Config& pconf, + const FirstFitSelection::Config& sconf); + +#endif + +template> +PackGroup nest(Container&& cont, + const typename Placer::BinType& bin, + Coord dist = 0, + const typename Placer::Config& pconf = {}, + const typename Selector::Config& sconf = {}) +{ + return nest(cont.begin(), cont.end(), + bin, dist, pconf, sconf); +} + template> @@ -105,71 +139,6 @@ PackGroup nest(Container&& cont, bin, prg, scond, dist, pconf, sconf); } -#ifdef LIBNEST2D_STATIC -extern template -PackGroup nest&>( - std::vector& cont, - const Box& bin, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -extern template -PackGroup nest&>( - std::vector& cont, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -extern template -PackGroup nest>( - std::vector&& cont, - const Box& bin, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -extern template -PackGroup nest>( - std::vector&& cont, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -extern template -PackGroup nest::iterator>( - std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -extern template -PackGroup nest::iterator>( - std::vector::iterator from, - std::vector::iterator to, - const Box& bin, - ProgressFunction prg, - StopCondition scond, - Coord dist, - const NfpPlacer::Config& pcfg, - const FirstFitSelection::Config& scfg -); - -#endif - } #endif // LIBNEST2D_H diff --git a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt index e20cbc70da..e207ecde4f 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt +++ b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt @@ -33,19 +33,18 @@ if(NOT TARGET clipper) # If there is a clipper target in the parent project we a # ${clipper_library_BINARY_DIR} # ) - add_library(ClipperBackend STATIC + add_library(clipperBackend STATIC ${clipper_library_SOURCE_DIR}/clipper.cpp ${clipper_library_SOURCE_DIR}/clipper.hpp) - target_include_directories(ClipperBackend INTERFACE - ${clipper_library_SOURCE_DIR}) + target_include_directories(clipperBackend INTERFACE ${clipper_library_SOURCE_DIR}) else() message(FATAL_ERROR "Can't find clipper library and no SVN client found to download. You can download the clipper sources and define a clipper target in your project, that will work for libnest2d.") endif() else() - add_library(ClipperBackend INTERFACE) - target_link_libraries(ClipperBackend INTERFACE Clipper::Clipper) + add_library(clipperBackend INTERFACE) + target_link_libraries(clipperBackend INTERFACE Clipper::Clipper) endif() else() # set(CLIPPER_INCLUDE_DIRS "" PARENT_SCOPE) @@ -69,6 +68,6 @@ target_include_directories(clipperBackend SYSTEM INTERFACE ${Boost_INCLUDE_DIRS} target_compile_definitions(clipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER) -# And finally plug the ClipperBackend into libnest2d -#target_link_libraries(libnest2d INTERFACE ClipperBackend) +# And finally plug the clipperBackend into libnest2d +# target_link_libraries(libnest2d INTERFACE clipperBackend) diff --git a/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp index e9fbfbd18a..6511fbb72a 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp @@ -12,13 +12,13 @@ struct Polygon { inline Polygon() = default; inline explicit Polygon(const Path& cont): Contour(cont) {} - inline explicit Polygon(const Paths& holes): - Holes(holes) {} +// inline explicit Polygon(const Paths& holes): +// Holes(holes) {} inline Polygon(const Path& cont, const Paths& holes): Contour(cont), Holes(holes) {} inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {} - inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {} +// inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {} inline Polygon(Path&& cont, Paths&& holes): Contour(std::move(cont)), Holes(std::move(holes)) {} }; @@ -42,7 +42,7 @@ inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) { return p; } -inline IntPoint operator -(IntPoint& p ) { +inline IntPoint operator -(const IntPoint& p ) { IntPoint ret = p; ret.X = -ret.X; ret.Y = -ret.Y; diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index 232668f610..e9fad405b6 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -20,43 +20,23 @@ using PathImpl = ClipperLib::Path; using HoleStore = ClipperLib::Paths; using PolygonImpl = ClipperLib::Polygon; -// Type of coordinate units used by Clipper -template<> struct CoordType { - using Type = ClipperLib::cInt; -}; - -// Type of point used by Clipper -template<> struct PointType { - using Type = PointImpl; -}; - -template<> struct PointType { - using Type = PointImpl; -}; - -template<> struct PointType { - using Type = PointImpl; -}; - -template<> struct CountourType { - using Type = PathImpl; -}; - template<> struct ShapeTag { using Type = PolygonTag; }; -template<> struct ShapeTag { using Type = PathTag; }; -template<> struct ShapeTag { using Type = PointTag; }; +template<> struct ShapeTag { using Type = PathTag; }; +template<> struct ShapeTag { using Type = PointTag; }; -template<> struct ShapeTag> { - using Type = MultiPolygonTag; -}; +// Type of coordinate units used by Clipper. Enough to specialize for point, +// the rest of the types will work (Path, Polygon) +template<> struct CoordType { using Type = ClipperLib::cInt; }; -template<> struct PointType> { - using Type = PointImpl; -}; +// Enough to specialize for path, it will work for multishape and Polygon +template<> struct PointType { using Type = PointImpl; }; -template<> struct HolesContainer { - using Type = ClipperLib::Paths; -}; +// This is crucial. CountourType refers to itself by default, so we don't have +// to secialize for clipper Path. ContourType::Type is PathImpl. +template<> struct ContourType { using Type = PathImpl; }; + +// The holes are contained in Clipper::Paths +template<> struct HolesContainer { using Type = ClipperLib::Paths; }; namespace pointlike { @@ -86,39 +66,11 @@ template<> inline TCoord& y(PointImpl& p) } +// Using the libnest2d default area implementation #define DISABLE_BOOST_AREA -namespace _smartarea { - -template -inline double area(const PolygonImpl& /*sh*/) { - return std::nan(""); -} - -template<> -inline double area(const PolygonImpl& sh) { - return std::accumulate(sh.Holes.begin(), sh.Holes.end(), - ClipperLib::Area(sh.Contour), - [](double a, const ClipperLib::Path& pt){ - return a + ClipperLib::Area(pt); - }); -} - -template<> -inline double area(const PolygonImpl& sh) { - return -area(sh); -} - -} - namespace shapelike { -// Tell libnest2d how to make string out of a ClipperPolygon object -template<> inline double area(const PolygonImpl& sh, const PolygonTag&) -{ - return _smartarea::area::Value>(sh); -} - template<> inline void offset(PolygonImpl& sh, TCoord distance) { #define DISABLE_BOOST_OFFSET @@ -200,43 +152,16 @@ inline PolygonImpl create(const PathImpl& path, const HoleStore& holes) { PolygonImpl p; p.Contour = path; - - // Expecting that the coordinate system Y axis is positive in upwards - // direction - if(ClipperLib::Orientation(p.Contour)) { - // Not clockwise then reverse the b*tch - ClipperLib::ReversePath(p.Contour); - } - p.Holes = holes; - for(auto& h : p.Holes) { - if(!ClipperLib::Orientation(h)) { - ClipperLib::ReversePath(h); - } - } - + return p; } template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) { PolygonImpl p; p.Contour.swap(path); - - // Expecting that the coordinate system Y axis is positive in upwards - // direction - if(ClipperLib::Orientation(p.Contour)) { - // Not clockwise then reverse the b*tch - ClipperLib::ReversePath(p.Contour); - } - p.Holes.swap(holes); - - for(auto& h : p.Holes) { - if(!ClipperLib::Orientation(h)) { - ClipperLib::ReversePath(h); - } - } - + return p; } @@ -314,13 +239,13 @@ inline void rotate(PolygonImpl& sh, const Radians& rads) } // namespace shapelike #define DISABLE_BOOST_NFP_MERGE -inline std::vector clipper_execute( +inline TMultiShape clipper_execute( ClipperLib::Clipper& clipper, ClipperLib::ClipType clipType, ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd, ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd) { - shapelike::Shapes retv; + TMultiShape retv; ClipperLib::PolyTree result; clipper.Execute(clipType, result, subjFillType, clipFillType); @@ -370,8 +295,8 @@ inline std::vector clipper_execute( namespace nfp { -template<> inline std::vector -merge(const std::vector& shapes) +template<> inline TMultiShape +merge(const TMultiShape& shapes) { ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution); @@ -394,6 +319,8 @@ merge(const std::vector& shapes) } +#define DISABLE_BOOST_CONVEX_HULL + //#define DISABLE_BOOST_SERIALIZE //#define DISABLE_BOOST_UNSERIALIZE diff --git a/src/libnest2d/include/libnest2d/common.hpp b/src/libnest2d/include/libnest2d/common.hpp index 6867f76f3e..66e095ae27 100644 --- a/src/libnest2d/include/libnest2d/common.hpp +++ b/src/libnest2d/include/libnest2d/common.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L #define BP2D_NOEXCEPT @@ -197,6 +198,33 @@ public: } }; +struct ScalarTag {}; +struct BigIntTag {}; +struct RationalTag {}; + +template struct _NumTag { + using Type = + enable_if_t::value, ScalarTag>; +}; + +template using NumTag = typename _NumTag>::Type; + +/// A local version for abs that is garanteed to work with libnest2d types +template inline T abs(const T& v, ScalarTag) +{ + return std::abs(v); +} + +template inline T abs(const T& v) { return abs(v, NumTag()); } + +template inline T2 cast(const T1& v, ScalarTag, ScalarTag) +{ + return static_cast(v); +} + +template inline T2 cast(const T1& v) { + return cast(v, NumTag(), NumTag()); +} } #endif // LIBNEST2D_CONFIG_HPP diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 917f5280d1..6c55d0e3f2 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -7,45 +7,125 @@ #include #include #include -#include #include #include +#include -#include "common.hpp" +#include namespace libnest2d { +// Meta tags for different geometry concepts. +struct PointTag {}; +struct PolygonTag {}; +struct PathTag {}; +struct MultiPolygonTag {}; +struct BoxTag {}; +struct CircleTag {}; + +/// Meta-function to derive the tag of a shape type. +template struct ShapeTag { using Type = typename Shape::Tag; }; + +/// Tag will be used instead of `typename ShapeTag::Type` +template using Tag = typename ShapeTag>::Type; + +/// Meta function to derive the contour type for a polygon which could be itself +template struct ContourType { using Type = RawShape; }; + +/// TContour instead of `typename ContourType::type` +template +using TContour = typename ContourType>::Type; + +/// Getting the type of point structure used by a shape. +template struct PointType { + using Type = typename PointType>::Type; +}; + +/// TPoint as shorthand for `typename PointType::Type`. +template +using TPoint = typename PointType>::Type; + /// Getting the coordinate data type for a geometry class. -template struct CoordType { using Type = long; }; +template struct CoordType { + using Type = typename CoordType>::Type; +}; /// TCoord as shorthand for typename `CoordType::Type`. template using TCoord = typename CoordType>::Type; -/// Getting the type of point structure used by a shape. -template struct PointType { using Type = typename Sh::PointType; }; +/// Getting the computation type for a certain geometry type. +/// It is the coordinate type by default but it is advised that a type with +/// larger precision and (or) range is specified. +template::value> struct ComputeType {}; -/// TPoint as shorthand for `typename PointType::Type`. -template -using TPoint = typename PointType>::Type; +/// A compute type is introduced to hold the results of computations on +/// coordinates and points. It should be larger in range than the coordinate +/// type or the range of coordinates should be limited to not loose precision. +template struct ComputeType { + using Type = typename ComputeType>::Type; +}; +/// libnest2d will choose a default compute type for various coordinate types +/// if the backend has not specified anything. +template struct DoublePrecision { using Type = T; }; +template<> struct DoublePrecision { using Type = int16_t; }; +template<> struct DoublePrecision { using Type = int32_t; }; +template<> struct DoublePrecision { using Type = int64_t; }; +template<> struct DoublePrecision { using Type = double; }; +template<> struct DoublePrecision { using Type = long double; }; +template struct ComputeType { + using Type = typename DoublePrecision::Type; +}; -template struct CountourType { using Type = RawShape; }; - -template -using TContour = typename CountourType>::Type; - +/// TCompute shorthand for `typename ComputeType::Type` +template using TCompute = typename ComputeType>::Type; +/// A meta function to derive a container type for holes in a polygon template struct HolesContainer { using Type = std::vector>; }; +/// Shorthand for `typename HolesContainer::Type` template using THolesContainer = typename HolesContainer>::Type; +/* + * TContour, TPoint, TCoord and TCompute should be usable for any type for which + * it makes sense. For example, the point type could be derived from the contour, + * the polygon and (or) the multishape as well. The coordinate type also and + * including the point type. TCoord, TCoord, TCoord are + * all valid types and derives the coordinate type of template argument Polygon, + * Path and Point. This is also true for TCompute, but it can also take the + * coordinate type as argument. + */ -template -struct LastPointIsFirst { static const bool Value = true; }; +/* + * A Multi shape concept is also introduced. A multi shape is something that + * can contain the result of an operation where the input is one polygon and + * the result could be many polygons or path -> paths. The MultiShape should be + * a container type. If the backend does not specialize the MultiShape template, + * a default multi shape container will be used. + */ + +/// The default multi shape container. +template struct DefaultMultiShape: public std::vector { + using Tag = MultiPolygonTag; + template DefaultMultiShape(Args&&...args): + std::vector(std::forward(args)...) {} +}; + +/// The MultiShape Type trait which gets the container type for a geometry type. +template struct MultiShape { using Type = DefaultMultiShape; }; + +/// use TMultiShape instead of `typename MultiShape::Type` +template +using TMultiShape = typename MultiShape>::Type; + +// A specialization of ContourType to work with the default multishape type +template struct ContourType> { + using Type = typename ContourType::Type; +}; enum class Orientation { CLOCKWISE, @@ -59,6 +139,11 @@ struct OrientationType { static const Orientation Value = Orientation::CLOCKWISE; }; +template inline /*constexpr*/ bool is_clockwise() { + return OrientationType>::Value == Orientation::CLOCKWISE; +} + + /** * \brief A point pair base class for other point pairs (segment, box, ...). * \tparam RawPoint The actual point type to use. @@ -69,21 +154,6 @@ struct PointPair { RawPoint p2; }; -struct PointTag {}; -struct PolygonTag {}; -struct PathTag {}; -struct MultiPolygonTag {}; -struct BoxTag {}; -struct CircleTag {}; - -/// Meta-functions to derive the tags -template struct ShapeTag { using Type = typename Shape::Tag; }; -template using Tag = typename ShapeTag>::Type; - -template struct MultiShape { using Type = std::vector; }; -template -using TMultiShape =typename MultiShape>::Type; - /** * \brief An abstraction of a box; */ @@ -114,11 +184,16 @@ public: inline RawPoint center() const BP2D_NOEXCEPT; - inline double area() const BP2D_NOEXCEPT { - return double(width()*height()); + template> + inline Unit area() const BP2D_NOEXCEPT { + return Unit(width())*height(); } }; +template struct PointType<_Box> { + using Type = typename _Box::PointType; +}; + template class _Circle { RawPoint center_; @@ -129,7 +204,6 @@ public: using PointType = RawPoint; _Circle() = default; - _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; } @@ -137,12 +211,16 @@ public: inline double radius() const BP2D_NOEXCEPT { return radius_; } inline void radius(double r) { radius_ = r; } - + inline double area() const BP2D_NOEXCEPT { - return 2.0*Pi*radius_*radius_; + return Pi_2 * radius_ * radius_; } }; +template struct PointType<_Circle> { + using Type = typename _Circle::PointType; +}; + /** * \brief An abstraction of a directed line segment with two points. */ @@ -185,7 +263,12 @@ public: inline Radians angleToXaxis() const; /// The length of the segment in the measure of the coordinate system. - inline double length(); + template> inline Unit sqlength() const; + +}; + +template struct PointType<_Segment> { + using Type = typename _Circle::PointType; }; // This struct serves almost as a namespace. The only difference is that is can @@ -216,33 +299,56 @@ inline TCoord& y(RawPoint& p) return p.y(); } -template -inline double distance(const RawPoint& /*p1*/, const RawPoint& /*p2*/) +template> +inline Unit squaredDistance(const RawPoint& p1, const RawPoint& p2) { - static_assert(always_false::value, - "PointLike::distance(point, point) unimplemented!"); - return 0; + auto x1 = Unit(x(p1)), y1 = Unit(y(p1)), x2 = Unit(x(p2)), y2 = Unit(y(p2)); + Unit a = (x2 - x1), b = (y2 - y1); + return a * a + b * b; } template -inline double distance(const RawPoint& /*p1*/, - const _Segment& /*s*/) +inline double distance(const RawPoint& p1, const RawPoint& p2) { - static_assert(always_false::value, - "PointLike::distance(point, segment) unimplemented!"); - return 0; + return std::sqrt(squaredDistance(p1, p2)); } -template -inline std::pair, bool> horizontalDistance( +// create perpendicular vector +template inline Pt perp(const Pt& p) +{ + return Pt(y(p), -x(p)); +} + +template> +inline Unit dotperp(const Pt& a, const Pt& b) +{ + return Unit(x(a)) * Unit(y(b)) - Unit(y(a)) * Unit(x(b)); +} + +// dot product +template> +inline Unit dot(const Pt& a, const Pt& b) +{ + return Unit(x(a)) * x(b) + Unit(y(a)) * y(b); +} + +// squared vector magnitude +template> +inline Unit magnsq(const Pt& p) +{ + return Unit(x(p)) * x(p) + Unit(y(p)) * y(p); +} + +template> +inline std::pair horizontalDistance( const RawPoint& p, const _Segment& s) { - using Unit = TCoord; - auto x = pointlike::x(p), y = pointlike::y(p); - auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); - auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); + namespace pl = pointlike; + auto x = Unit(pl::x(p)), y = Unit(pl::y(p)); + auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first())); + auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second())); - TCoord ret; + Unit ret; if( (y < y1 && y < y2) || (y > y1 && y > y2) ) return {0, false}; @@ -250,8 +356,7 @@ inline std::pair, bool> horizontalDistance( ret = std::min( x-x1, x -x2); else if( (y == y1 && y == y2) && (x < x1 && x < x2)) ret = -std::min(x1 - x, x2 - x); - else if(std::abs(y - y1) <= std::numeric_limits::epsilon() && - std::abs(y - y2) <= std::numeric_limits::epsilon()) + else if(y == y1 && y == y2) ret = 0; else ret = x - x1 + (x1 - x2)*(y1 - y)/(y1 - y2); @@ -259,16 +364,16 @@ inline std::pair, bool> horizontalDistance( return {ret, true}; } -template -inline std::pair, bool> verticalDistance( +template> +inline std::pair verticalDistance( const RawPoint& p, const _Segment& s) { - using Unit = TCoord; - auto x = pointlike::x(p), y = pointlike::y(p); - auto x1 = pointlike::x(s.first()), y1 = pointlike::y(s.first()); - auto x2 = pointlike::x(s.second()), y2 = pointlike::y(s.second()); + namespace pl = pointlike; + auto x = Unit(pl::x(p)), y = Unit(pl::y(p)); + auto x1 = Unit(pl::x(s.first())), y1 = Unit(pl::y(s.first())); + auto x2 = Unit(pl::x(s.second())), y2 = Unit(pl::y(s.second())); - TCoord ret; + Unit ret; if( (x < x1 && x < x2) || (x > x1 && x > x2) ) return {0, false}; @@ -276,8 +381,7 @@ inline std::pair, bool> verticalDistance( ret = std::min( y-y1, y -y2); else if( (x == x1 && x == x2) && (y < y1 && y < y2)) ret = -std::min(y1 - y, y2 - y); - else if(std::abs(x - x1) <= std::numeric_limits::epsilon() && - std::abs(x - x2) <= std::numeric_limits::epsilon()) + else if(x == x1 && x == x2) ret = 0; else ret = y - y1 + (y1 - y2)*(x1 - x)/(x1 - x2); @@ -333,9 +437,10 @@ inline Radians _Segment::angleToXaxis() const } template -inline double _Segment::length() +template +inline Unit _Segment::sqlength() const { - return pointlike::distance(first(), second()); + return pointlike::squaredDistance(first(), second()); } template @@ -346,8 +451,8 @@ inline RawPoint _Box::center() const BP2D_NOEXCEPT { using Coord = TCoord; RawPoint ret = { // No rounding here, we dont know if these are int coords - static_cast( (getX(minc) + getX(maxc))/2.0 ), - static_cast( (getY(minc) + getY(maxc))/2.0 ) + Coord( (getX(minc) + getX(maxc)) / Coord(2) ), + Coord( (getY(minc) + getY(maxc)) / Coord(2) ) }; return ret; @@ -362,9 +467,6 @@ enum class Formats { // used in friend declarations and can be aliased at class scope. namespace shapelike { -template -using Shapes = TMultiShape; - template inline RawShape create(const TContour& contour, const THolesContainer& holes) @@ -449,7 +551,7 @@ inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&) template inline void addVertex(RawShape& sh, const PathTag&, Args...args) { - return sh.emplace_back(std::forward(args)...); + sh.emplace_back(std::forward(args)...); } template @@ -504,13 +606,8 @@ inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/) "shapelike::unserialize() unimplemented!"); } -template -inline double area(const RawShape& /*sh*/, const PolygonTag&) -{ - static_assert(always_false::value, - "shapelike::area() unimplemented!"); - return 0; -} +template +inline Unit area(const Cntr& poly, const PathTag& ); template inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) @@ -556,14 +653,14 @@ inline bool touches( const TPoint& /*point*/, template inline _Box> boundingBox(const RawShape& /*sh*/, - const PolygonTag&) + const PathTag&) { static_assert(always_false::value, "shapelike::boundingBox(shape) unimplemented!"); } template -inline _Box> +inline _Box> boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) { static_assert(always_false::value, @@ -571,21 +668,10 @@ boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) } template -inline RawShape convexHull(const RawShape& /*sh*/, const PolygonTag&) -{ - static_assert(always_false::value, - "shapelike::convexHull(shape) unimplemented!"); - return RawShape(); -} +inline RawShape convexHull(const RawShape& sh, const PathTag&); -template -inline typename RawShapes::value_type -convexHull(const RawShapes& /*sh*/, const MultiPolygonTag&) -{ - static_assert(always_false::value, - "shapelike::convexHull(shapes) unimplemented!"); - return typename RawShapes::value_type(); -} +template +inline S convexHull(const RawShapes& sh, const MultiPolygonTag&); template inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) @@ -745,13 +831,19 @@ inline void reserve(T& sh, size_t vertex_capacity) { template inline void addVertex(RawShape& sh, const PolygonTag&, Args...args) { - return addVertex(contour(sh), PathTag(), std::forward(args)...); + addVertex(contour(sh), PathTag(), std::forward(args)...); } template // Tag dispatcher inline void addVertex(RawShape& sh, Args...args) { - return addVertex(sh, Tag(), std::forward(args)...); + addVertex(sh, Tag(), std::forward(args)...); +} + +template +inline _Box> boundingBox(const RawShape& poly, const PolygonTag&) +{ + return boundingBox(contour(poly), PathTag()); } template @@ -786,7 +878,7 @@ inline _Box> boundingBox(const S& sh) template inline double area(const Box& box, const BoxTag& ) { - return box.area(); + return box.template area(); } template @@ -795,6 +887,35 @@ inline double area(const Circle& circ, const CircleTag& ) return circ.area(); } +template +inline Unit area(const Cntr& poly, const PathTag& ) +{ + namespace sl = shapelike; + if (sl::cend(poly) - sl::cbegin(poly) < 3) return 0.0; + + Unit a = 0; + for (auto i = sl::cbegin(poly), j = std::prev(sl::cend(poly)); + i < sl::cend(poly); ++i) + { + auto xj = Unit(getX(*j)), yj = Unit(getY(*j)); + auto xi = Unit(getX(*i)), yi = Unit(getY(*i)); + a += (xj + xi) * (yj - yi); + j = i; + } + a /= 2; + return is_clockwise() ? a : -a; +} + +template inline double area(const S& poly, const PolygonTag& ) +{ + auto hls = holes(poly); + return std::accumulate(hls.begin(), hls.end(), + area(contour(poly), PathTag()), + [](double a, const TContour &h){ + return a + area(h, PathTag()); + }); +} + template // Dispatching function inline double area(const RawShape& sh) { @@ -811,6 +932,12 @@ inline double area(const RawShapes& shapes, const MultiPolygonTag&) }); } +template +inline RawShape convexHull(const RawShape& sh, const PolygonTag&) +{ + return create(convexHull(contour(sh), PathTag())); +} + template inline auto convexHull(const RawShape& sh) -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce @@ -818,11 +945,91 @@ inline auto convexHull(const RawShape& sh) return convexHull(sh, Tag()); } +template +inline RawShape convexHull(const RawShape& sh, const PathTag&) +{ + using Unit = TCompute; + using Point = TPoint; + namespace sl = shapelike; + + size_t edges = sl::cend(sh) - sl::cbegin(sh); + if(edges <= 3) return {}; + + bool closed = false; + std::vector U, L; + U.reserve(1 + edges / 2); L.reserve(1 + edges / 2); + + std::vector pts; pts.reserve(edges); + std::copy(sl::cbegin(sh), sl::cend(sh), std::back_inserter(pts)); + + auto fpt = pts.front(), lpt = pts.back(); + if(getX(fpt) == getX(lpt) && getY(fpt) == getY(lpt)) { + closed = true; pts.pop_back(); + } + + std::sort(pts.begin(), pts.end(), + [](const Point& v1, const Point& v2) + { + Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2); + return x1 == x2 ? y1 < y2 : x1 < x2; + }); + + auto dir = [](const Point& p, const Point& q, const Point& r) { + return (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) - + (Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p)); + }; + + auto ik = pts.begin(); + + while(ik != pts.end()) { + + while(U.size() > 1 && dir(U[U.size() - 2], U.back(), *ik) <= 0) + U.pop_back(); + while(L.size() > 1 && dir(L[L.size() - 2], L.back(), *ik) >= 0) + L.pop_back(); + + U.emplace_back(*ik); + L.emplace_back(*ik); + + ++ik; + } + + RawShape ret; reserve(ret, U.size() + L.size()); + if(is_clockwise()) { + for(auto it = U.begin(); it != std::prev(U.end()); ++it) + addVertex(ret, *it); + for(auto it = L.rbegin(); it != std::prev(L.rend()); ++it) + addVertex(ret, *it); + if(closed) addVertex(ret, *std::prev(L.rend())); + } else { + for(auto it = L.begin(); it != std::prev(L.end()); ++it) + addVertex(ret, *it); + for(auto it = U.rbegin(); it != std::prev(U.rend()); ++it) + addVertex(ret, *it); + if(closed) addVertex(ret, *std::prev(U.rend())); + } + + return ret; +} + +template +inline S convexHull(const RawShapes& sh, const MultiPolygonTag&) +{ + namespace sl = shapelike; + S cntr; + for(auto& poly : sh) + for(auto it = sl::cbegin(poly); it != sl::cend(poly); ++it) + addVertex(cntr, *it); + + return convexHull(cntr, Tag()); +} + template inline bool isInside(const TP& point, const TC& circ, const PointTag&, const CircleTag&) { - return pointlike::distance(point, circ.center()) < circ.radius(); + auto r = circ.radius(); + return pointlike::squaredDistance(point, circ.center()) < r * r; } template @@ -972,6 +1179,9 @@ template inline bool isConvex(const RawShape& sh) // dispatch using Segment = _Segment; \ using Polygons = TMultiShape +namespace sl = shapelike; +namespace pl = pointlike; + } #endif // GEOMETRY_TRAITS_HPP diff --git a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp index cb0580ef4f..4a2c69bca2 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp @@ -1,26 +1,22 @@ #ifndef GEOMETRIES_NOFITPOLYGON_HPP #define GEOMETRIES_NOFITPOLYGON_HPP -#include "geometry_traits.hpp" #include #include #include #include +#include + namespace libnest2d { namespace __nfp { // Do not specialize this... -template +template> inline bool _vsort(const TPoint& v1, const TPoint& v2) { - using Coord = TCoord>; - Coord &&x1 = getX(v1), &&x2 = getX(v2), &&y1 = getY(v1), &&y2 = getY(v2); - auto diff = y1 - y2; - if(std::abs(diff) <= std::numeric_limits::epsilon()) - return x1 < x2; - - return diff < 0; + Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2); + return y1 == y2 ? x1 < x2 : y1 < y2; } template> @@ -202,7 +198,7 @@ inline TPoint referenceVertex(const RawShape& sh) * convex as well in this case. * */ -template +template inline NfpResult nfpConvexOnly(const RawShape& sh, const RawShape& other) { @@ -238,12 +234,62 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, ++first; ++next; } } - - // Sort the edges by angle to X axis. - std::sort(edgelist.begin(), edgelist.end(), - [](const Edge& e1, const Edge& e2) + + std::sort(edgelist.begin(), edgelist.end(), + [](const Edge& e1, const Edge& e2) { - return e1.angleToXaxis() > e2.angleToXaxis(); + Vertex ax(1, 0); // Unit vector for the X axis + + // get cectors from the edges + Vertex p1 = e1.second() - e1.first(); + Vertex p2 = e2.second() - e2.first(); + + // Quadrant mapping array. The quadrant of a vector can be determined + // from the dot product of the vector and its perpendicular pair + // with the unit vector X axis. The products will carry the values + // lcos = dot(p, ax) = l * cos(phi) and + // lsin = -dotperp(p, ax) = l * sin(phi) where + // l is the length of vector p. From the signs of these values we can + // construct an index which has the sign of lcos as MSB and the + // sign of lsin as LSB. This index can be used to retrieve the actual + // quadrant where vector p resides using the following map: + // (+ is 0, - is 1) + // cos | sin | decimal | quadrant + // + | + | 0 | 0 + // + | - | 1 | 3 + // - | + | 2 | 1 + // - | - | 3 | 2 + std::array quadrants {0, 3, 1, 2 }; + + std::array q {0, 0}; // Quadrant indices for p1 and p2 + + using TDots = std::array, 2>; + TDots lcos { pl::dot(p1, ax), pl::dot(p2, ax) }; + TDots lsin { -pl::dotperp(p1, ax), -pl::dotperp(p2, ax) }; + + // Construct the quadrant indices for p1 and p2 + for(size_t i = 0; i < 2; ++i) + if(lcos[i] == 0) q[i] = lsin[i] > 0 ? 1 : 3; + else if(lsin[i] == 0) q[i] = lcos[i] > 0 ? 0 : 2; + else q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)]; + + if(q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant + auto lsq1 = pl::magnsq(p1); // squared magnitudes, avoid sqrt + auto lsq2 = pl::magnsq(p2); // squared magnitudes, avoid sqrt + + // We will actually compare l^2 * cos^2(phi) which saturates the + // cos function. But with the quadrant info we can get the sign back + int sign = q[0] == 1 || q[0] == 2 ? -1 : 1; + + // If Ratio is an actual rational type, there is no precision loss + auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0]; + auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1]; + + return q[0] < 2 ? pcos1 < pcos2 : pcos1 > pcos2; + } + + // If in different quadrants, compare the quadrant indices only. + return q[0] > q[1]; }); __nfp::buildPolygon(edgelist, rsh, top_nfp); @@ -253,456 +299,9 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, template NfpResult nfpSimpleSimple(const RawShape& cstationary, - const RawShape& cother) + const RawShape& cother) { - - // Algorithms are from the original algorithm proposed in paper: - // https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf - - // ///////////////////////////////////////////////////////////////////////// - // Algorithm 1: Obtaining the minkowski sum - // ///////////////////////////////////////////////////////////////////////// - - // I guess this is not a full minkowski sum of the two input polygons by - // definition. This yields a subset that is compatible with the next 2 - // algorithms. - - using Result = NfpResult; - using Vertex = TPoint; - using Coord = TCoord; - using Edge = _Segment; - namespace sl = shapelike; - using std::signbit; - using std::sort; - using std::vector; - using std::ref; - using std::reference_wrapper; - - // TODO The original algorithms expects the stationary polygon in - // counter clockwise and the orbiter in clockwise order. - // So for preventing any further complication, I will make the input - // the way it should be, than make my way around the orientations. - - // Reverse the stationary contour to counter clockwise - auto stcont = sl::contour(cstationary); - { - std::reverse(sl::begin(stcont), sl::end(stcont)); - stcont.pop_back(); - auto it = std::min_element(sl::begin(stcont), sl::end(stcont), - [](const Vertex& v1, const Vertex& v2) { - return getY(v1) < getY(v2); - }); - std::rotate(sl::begin(stcont), it, sl::end(stcont)); - sl::addVertex(stcont, sl::front(stcont)); - } - RawShape stationary; - sl::contour(stationary) = stcont; - - // Reverse the orbiter contour to counter clockwise - auto orbcont = sl::contour(cother); - { - std::reverse(orbcont.begin(), orbcont.end()); - - // Step 1: Make the orbiter reverse oriented - - orbcont.pop_back(); - auto it = std::min_element(orbcont.begin(), orbcont.end(), - [](const Vertex& v1, const Vertex& v2) { - return getY(v1) < getY(v2); - }); - - std::rotate(orbcont.begin(), it, orbcont.end()); - orbcont.emplace_back(orbcont.front()); - - for(auto &v : orbcont) v = -v; - - } - - // Copy the orbiter (contour only), we will have to work on it - RawShape orbiter; - sl::contour(orbiter) = orbcont; - - // An edge with additional data for marking it - struct MarkedEdge { - Edge e; Radians turn_angle = 0; bool is_turning_point = false; - MarkedEdge() = default; - MarkedEdge(const Edge& ed, Radians ta, bool tp): - e(ed), turn_angle(ta), is_turning_point(tp) {} - - // debug - std::string label; - }; - - // Container for marked edges - using EdgeList = vector; - - EdgeList A, B; - - // This is how an edge list is created from the polygons - auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) { - auto& poly = sl::contour(ppoly); - - L.reserve(sl::contourVertexCount(poly)); - - if(dir > 0) { - auto it = poly.begin(); - auto nextit = std::next(it); - - double turn_angle = 0; - bool is_turn_point = false; - - while(nextit != poly.end()) { - L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); - it++; nextit++; - } - } else { - auto it = sl::rbegin(poly); - auto nextit = std::next(it); - - double turn_angle = 0; - bool is_turn_point = false; - - while(nextit != sl::rend(poly)) { - L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); - it++; nextit++; - } - } - - auto getTurnAngle = [](const Edge& e1, const Edge& e2) { - auto phi = e1.angleToXaxis(); - auto phi_prev = e2.angleToXaxis(); - auto turn_angle = phi-phi_prev; - if(turn_angle > Pi) turn_angle -= TwoPi; - if(turn_angle < -Pi) turn_angle += TwoPi; - return turn_angle; - }; - - auto eit = L.begin(); - auto enext = std::next(eit); - - eit->turn_angle = getTurnAngle(L.front().e, L.back().e); - - while(enext != L.end()) { - enext->turn_angle = getTurnAngle( enext->e, eit->e); - eit->is_turning_point = - signbit(enext->turn_angle) != signbit(eit->turn_angle); - ++eit; ++enext; - } - - L.back().is_turning_point = signbit(L.back().turn_angle) != - signbit(L.front().turn_angle); - - }; - - // Step 2: Fill the edgelists - fillEdgeList(A, stationary, 1); - fillEdgeList(B, orbiter, 1); - - int i = 1; - for(MarkedEdge& me : A) { - std::cout << "a" << i << ":\n\t" - << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" - << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" - << "Turning point: " << (me.is_turning_point ? "yes" : "no") - << std::endl; - - me.label = "a"; me.label += std::to_string(i); - i++; - } - - i = 1; - for(MarkedEdge& me : B) { - std::cout << "b" << i << ":\n\t" - << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" - << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" - << "Turning point: " << (me.is_turning_point ? "yes" : "no") - << std::endl; - me.label = "b"; me.label += std::to_string(i); - i++; - } - - // A reference to a marked edge that also knows its container - struct MarkedEdgeRef { - reference_wrapper eref; - reference_wrapper> container; - Coord dir = 1; // Direction modifier - - inline Radians angleX() const { return eref.get().e.angleToXaxis(); } - inline const Edge& edge() const { return eref.get().e; } - inline Edge& edge() { return eref.get().e; } - inline bool isTurningPoint() const { - return eref.get().is_turning_point; - } - inline bool isFrom(const vector& cont ) { - return &(container.get()) == &cont; - } - inline bool eq(const MarkedEdgeRef& mr) { - return &(eref.get()) == &(mr.eref.get()); - } - - MarkedEdgeRef(reference_wrapper er, - reference_wrapper> ec): - eref(er), container(ec), dir(1) {} - - MarkedEdgeRef(reference_wrapper er, - reference_wrapper> ec, - Coord d): - eref(er), container(ec), dir(d) {} - }; - - using EdgeRefList = vector; - - // Comparing two marked edges - auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) { - return e1.angleX() < e2.angleX(); - }; - - EdgeRefList Aref, Bref; // We create containers for the references - Aref.reserve(A.size()); Bref.reserve(B.size()); - - // Fill reference container for the stationary polygon - std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) { - Aref.emplace_back( ref(me), ref(Aref) ); - }); - - // Fill reference container for the orbiting polygon - std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) { - Bref.emplace_back( ref(me), ref(Bref) ); - }); - - auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure - (const EdgeRefList& Q, const EdgeRefList& R, bool positive) - { - - // Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)" - // Sort the containers of edge references and merge them. - // Q could be sorted only once and be reused here but we would still - // need to merge it with sorted(R). - - EdgeRefList merged; - EdgeRefList S, seq; - merged.reserve(Q.size() + R.size()); - - merged.insert(merged.end(), R.begin(), R.end()); - std::stable_sort(merged.begin(), merged.end(), sortfn); - merged.insert(merged.end(), Q.begin(), Q.end()); - std::stable_sort(merged.begin(), merged.end(), sortfn); - - // Step 2 "set i = 1, k = 1, direction = 1, s1 = q1" - // we don't use i, instead, q is an iterator into Q. k would be an index - // into the merged sequence but we use "it" as an iterator for that - - // here we obtain references for the containers for later comparisons - const auto& Rcont = R.begin()->container.get(); - const auto& Qcont = Q.begin()->container.get(); - - // Set the initial direction - Coord dir = 1; - - // roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q; - if(positive) { - auto q = Q.begin(); - S.emplace_back(*q); - - // Roughly step 3 - - std::cout << "merged size: " << merged.size() << std::endl; - auto mit = merged.begin(); - for(bool finish = false; !finish && q != Q.end();) { - ++q; // "Set i = i + 1" - - while(!finish && mit != merged.end()) { - if(mit->isFrom(Rcont)) { - auto s = *mit; - s.dir = dir; - S.emplace_back(s); - } - - if(mit->eq(*q)) { - S.emplace_back(*q); - if(mit->isTurningPoint()) dir = -dir; - if(q == Q.begin()) finish = true; - break; - } - - mit += dir; - // __nfp::advance(mit, merged, dir > 0); - } - } - } else { - auto q = Q.rbegin(); - S.emplace_back(*q); - - // Roughly step 3 - - std::cout << "merged size: " << merged.size() << std::endl; - auto mit = merged.begin(); - for(bool finish = false; !finish && q != Q.rend();) { - ++q; // "Set i = i + 1" - - while(!finish && mit != merged.end()) { - if(mit->isFrom(Rcont)) { - auto s = *mit; - s.dir = dir; - S.emplace_back(s); - } - - if(mit->eq(*q)) { - S.emplace_back(*q); - S.back().dir = -1; - if(mit->isTurningPoint()) dir = -dir; - if(q == Q.rbegin()) finish = true; - break; - } - - mit += dir; - // __nfp::advance(mit, merged, dir > 0); - } - } - } - - - // Step 4: - - // "Let starting edge r1 be in position si in sequence" - // whaaat? I guess this means the following: - auto it = S.begin(); - while(!it->eq(*R.begin())) ++it; - - // "Set j = 1, next = 2, direction = 1, seq1 = si" - // we don't use j, seq is expanded dynamically. - dir = 1; - auto next = std::next(R.begin()); seq.emplace_back(*it); - - // Step 5: - // "If all si edges have been allocated to seqj" should mean that - // we loop until seq has equal size with S - auto send = it; //it == S.begin() ? it : std::prev(it); - while(it != S.end()) { - ++it; if(it == S.end()) it = S.begin(); - if(it == send) break; - - if(it->isFrom(Qcont)) { - seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si" - - // "If si is a turning point in Q, - // direction = - direction, next = next + direction" - if(it->isTurningPoint()) { - dir = -dir; - next += dir; -// __nfp::advance(next, R, dir > 0); - } - } - - if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext" - // "j = j + 1, seqj = si, next = next + direction" - seq.emplace_back(*it); - next += dir; -// __nfp::advance(next, R, dir > 0); - } - } - - return seq; - }; - - std::vector seqlist; - seqlist.reserve(Bref.size()); - - EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram - - // make the slope diagram of B - std::sort(Bslope.begin(), Bslope.end(), sortfn); - - auto slopeit = Bslope.begin(); // search for the first turning point - while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++; - - if(slopeit == Bslope.end()) { - // no turning point means convex polygon. - seqlist.emplace_back(mink(Aref, Bref, true)); - } else { - int dir = 1; - - auto firstturn = Bref.begin(); - while(!firstturn->eq(*slopeit)) ++firstturn; - - assert(firstturn != Bref.end()); - - EdgeRefList bgroup; bgroup.reserve(Bref.size()); - bgroup.emplace_back(*slopeit); - - auto b_it = std::next(firstturn); - while(b_it != firstturn) { - if(b_it == Bref.end()) b_it = Bref.begin(); - - while(!slopeit->eq(*b_it)) { - __nfp::advance(slopeit, Bslope, dir > 0); - } - - if(!slopeit->isTurningPoint()) { - bgroup.emplace_back(*slopeit); - } else { - if(!bgroup.empty()) { - if(dir > 0) bgroup.emplace_back(*slopeit); - for(auto& me : bgroup) { - std::cout << me.eref.get().label << ", "; - } - std::cout << std::endl; - seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false)); - bgroup.clear(); - if(dir < 0) bgroup.emplace_back(*slopeit); - } else { - bgroup.emplace_back(*slopeit); - } - - dir *= -1; - } - ++b_it; - } - } - -// while(it != Bref.end()) // This is step 3 and step 4 in one loop -// if(it->isTurningPoint()) { -// R = {R.last, it++}; -// auto seq = mink(Q, R, orientation); - -// // TODO step 6 (should be 5 shouldn't it?): linking edges from A -// // I don't get this step - -// seqlist.insert(seqlist.end(), seq.begin(), seq.end()); -// orientation = !orientation; -// } else ++it; - -// if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true); - - // ///////////////////////////////////////////////////////////////////////// - // Algorithm 2: breaking Minkowski sums into track line trips - // ///////////////////////////////////////////////////////////////////////// - - - // ///////////////////////////////////////////////////////////////////////// - // Algorithm 3: finding the boundary of the NFP from track line trips - // ///////////////////////////////////////////////////////////////////////// - - - for(auto& seq : seqlist) { - std::cout << "seqlist size: " << seq.size() << std::endl; - for(auto& s : seq) { - std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", "; - } - std::cout << std::endl; - } - - auto& seq = seqlist.front(); - RawShape rsh; - Vertex top_nfp; - std::vector edgelist; edgelist.reserve(seq.size()); - for(auto& s : seq) { - edgelist.emplace_back(s.eref.get().e); - } - - __nfp::buildPolygon(edgelist, rsh, top_nfp); - - return Result(rsh, top_nfp); + return {}; } // Specializable NFP implementation class. Specialize it if you have a faster diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index c7b252e5d5..5d74aa3d9d 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -8,13 +8,10 @@ #include #include -#include "geometry_traits.hpp" +#include namespace libnest2d { -namespace sl = shapelike; -namespace pl = pointlike; - /** * \brief An item to be placed on a bin. * @@ -422,13 +419,9 @@ private: static inline bool vsort(const Vertex& v1, const Vertex& v2) { - Coord &&x1 = getX(v1), &&x2 = getX(v2); - Coord &&y1 = getY(v1), &&y2 = getY(v2); - auto diff = y1 - y2; - if(std::abs(diff) <= std::numeric_limits::epsilon()) - return x1 < x2; - - return diff < 0; + TCompute x1 = getX(v1), x2 = getX(v2); + TCompute y1 = getY(v1), y2 = getY(v2); + return y1 == y2 ? x1 < x2 : y1 < y2; } }; diff --git a/src/libnest2d/include/libnest2d/optimizer.hpp b/src/libnest2d/include/libnest2d/optimizer.hpp index 962a47392f..e4c149f228 100644 --- a/src/libnest2d/include/libnest2d/optimizer.hpp +++ b/src/libnest2d/include/libnest2d/optimizer.hpp @@ -4,7 +4,8 @@ #include #include #include -#include "common.hpp" + +#include namespace libnest2d { namespace opt { @@ -60,6 +61,7 @@ enum class Method { L_SIMPLEX, L_SUBPLEX, G_GENETIC, + G_PARTICLE_SWARM //... }; diff --git a/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt b/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt index 4e16d4fc58..6f51718d8b 100644 --- a/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt +++ b/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt @@ -48,7 +48,7 @@ else() target_link_libraries(nloptOptimizer INTERFACE Nlopt::Nlopt) endif() -#target_sources( NloptOptimizer INTERFACE +#target_sources( nloptOptimizer INTERFACE #${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp #${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp #${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp @@ -57,5 +57,5 @@ endif() target_compile_definitions(nloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT) -# And finally plug the NloptOptimizer into libnest2d -#target_link_libraries(libnest2d INTERFACE NloptOptimizer) +# And finally plug the nloptOptimizer into libnest2d +#target_link_libraries(libnest2d INTERFACE nloptOptimizer) diff --git a/src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt b/src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt deleted file mode 100644 index efbbd9cfb2..0000000000 --- a/src/libnest2d/include/libnest2d/optimizers/optimlib/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -find_package(Armadillo REQUIRED) - -add_library(OptimlibOptimizer INTERFACE) -target_include_directories(OptimlibOptimizer INTERFACE ${ARMADILLO_INCLUDE_DIRS}) -target_link_libraries(OptimlibOptimizer INTERFACE ${ARMADILLO_LIBRARIES}) \ No newline at end of file diff --git a/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp index 7f10be7d73..28aaad5ce1 100644 --- a/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp @@ -7,15 +7,15 @@ namespace libnest2d { namespace placers { -template struct Epsilon {}; +template struct DefaultEpsilon {}; template -struct Epsilon::value, T> > { +struct DefaultEpsilon::value, T> > { static const T Value = 1; }; template -struct Epsilon::value, T> > { +struct DefaultEpsilon::value, T> > { static const T Value = 1e-3; }; @@ -24,7 +24,7 @@ struct BLConfig { DECLARE_MAIN_TYPES(RawShape); Coord min_obj_distance = 0; - Coord epsilon = Epsilon::Value; + Coord epsilon = DefaultEpsilon::Value; bool allow_rotations = false; }; diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 91affe9786..c1f15fe61f 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -103,14 +103,14 @@ Key hash(const _Item& item) { while(deg > N) { ms++; deg -= N; } ls += int(deg); ret.push_back(char(ms)); ret.push_back(char(ls)); - circ += seg.length(); + circ += std::sqrt(seg.template sqlength()); } it = ctr.begin(); nx = std::next(it); while(nx != ctr.end()) { Segment seg(*it++, *nx++); - auto l = int(M * seg.length() / circ); + auto l = int(M * std::sqrt(seg.template sqlength()) / circ); int ms = 'A', ls = 'A'; while(l > N) { ms++; l -= N; } ls += l; @@ -249,6 +249,11 @@ template class EdgeCache { std::vector holes_; double accuracy_ = 1.0; + + static double length(const Edge &e) + { + return std::sqrt(e.template sqlength()); + } void createCache(const RawShape& sh) { { // For the contour @@ -260,7 +265,7 @@ template class EdgeCache { while(next != endit) { contour_.emap.emplace_back(*(first++), *(next++)); - contour_.full_distance += contour_.emap.back().length(); + contour_.full_distance += length(contour_.emap.back()); contour_.distances.emplace_back(contour_.full_distance); } } @@ -275,7 +280,7 @@ template class EdgeCache { while(next != endit) { hc.emap.emplace_back(*(first++), *(next++)); - hc.full_distance += hc.emap.back().length(); + hc.full_distance += length(hc.emap.back()); hc.distances.emplace_back(hc.full_distance); } diff --git a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp index baf1c6a105..16dee513b2 100644 --- a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp +++ b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp @@ -311,19 +311,19 @@ struct range_value { namespace libnest2d { // Now the algorithms that boost can provide... -namespace pointlike { -template<> -inline double distance(const PointImpl& p1, const PointImpl& p2 ) -{ - return boost::geometry::distance(p1, p2); -} +//namespace pointlike { +//template<> +//inline double distance(const PointImpl& p1, const PointImpl& p2 ) +//{ +// return boost::geometry::distance(p1, p2); +//} -template<> -inline double distance(const PointImpl& p, const bp2d::Segment& seg ) -{ - return boost::geometry::distance(p, seg); -} -} +//template<> +//inline double distance(const PointImpl& p, const bp2d::Segment& seg ) +//{ +// return boost::geometry::distance(p, seg); +//} +//} namespace shapelike { // Tell libnest2d how to make string out of a ClipperPolygon object @@ -382,16 +382,9 @@ inline bool touches( const PointImpl& point, const PolygonImpl& shape) } #ifndef DISABLE_BOOST_BOUNDING_BOX -template<> -inline bp2d::Box boundingBox(const PolygonImpl& sh, const PolygonTag&) -{ - bp2d::Box b; - boost::geometry::envelope(sh, b); - return b; -} template<> -inline bp2d::Box boundingBox(const PathImpl& sh, const PolygonTag&) +inline bp2d::Box boundingBox(const PathImpl& sh, const PathTag&) { bp2d::Box b; boost::geometry::envelope(sh, b); @@ -410,9 +403,9 @@ inline bp2d::Box boundingBox(const bp2d::Shapes& shapes, #ifndef DISABLE_BOOST_CONVEX_HULL template<> -inline PolygonImpl convexHull(const PolygonImpl& sh, const PolygonTag&) +inline PathImpl convexHull(const PathImpl& sh, const PathTag&) { - PolygonImpl ret; + PathImpl ret; boost::geometry::convex_hull(sh, ret); return ret; } diff --git a/src/libnest2d/include/libnest2d/utils/rotcalipers.hpp b/src/libnest2d/include/libnest2d/utils/rotcalipers.hpp new file mode 100644 index 0000000000..76f91ba0cf --- /dev/null +++ b/src/libnest2d/include/libnest2d/utils/rotcalipers.hpp @@ -0,0 +1,268 @@ +#ifndef ROTCALIPERS_HPP +#define ROTCALIPERS_HPP + +#include +#include +#include +#include + +#include + +namespace libnest2d { + +template> class RotatedBox { + Pt axis_; + Unit bottom_ = Unit(0), right_ = Unit(0); +public: + + RotatedBox() = default; + RotatedBox(const Pt& axis, Unit b, Unit r): + axis_(axis), bottom_(b), right_(r) {} + + inline long double area() const { + long double asq = pl::magnsq(axis_); + return cast(bottom_) * cast(right_) / asq; + } + + inline long double width() const { + return abs(bottom_) / std::sqrt(pl::magnsq(axis_)); + } + + inline long double height() const { + return abs(right_) / std::sqrt(pl::magnsq(axis_)); + } + + inline Unit bottom_extent() const { return bottom_; } + inline Unit right_extent() const { return right_; } + inline const Pt& axis() const { return axis_; } + + inline Radians angleToX() const { + double ret = std::atan2(getY(axis_), getX(axis_)); + auto s = std::signbit(ret); + if(s) ret += Pi_2; + return -ret; + } +}; + +template , class Unit = TCompute> +Poly removeCollinearPoints(const Poly& sh, Unit eps = Unit(0)) +{ + Poly ret; sl::reserve(ret, sl::contourVertexCount(sh)); + + Pt eprev = *sl::cbegin(sh) - *std::prev(sl::cend(sh)); + + auto it = sl::cbegin(sh); + auto itx = std::next(it); + if(itx != sl::cend(sh)) while (it != sl::cend(sh)) + { + Pt enext = *itx - *it; + + auto dp = pl::dotperp(eprev, enext); + if(abs(dp) > eps) sl::addVertex(ret, *it); + + eprev = enext; + if (++itx == sl::cend(sh)) itx = sl::cbegin(sh); + ++it; + } + + return ret; +} + +// The area of the bounding rectangle with the axis dir and support vertices +template, class R = TCompute> +inline R rectarea(const Pt& w, // the axis + const Pt& vb, const Pt& vr, + const Pt& vt, const Pt& vl) +{ + Unit a = pl::dot(w, vr - vl); + Unit b = pl::dot(-pl::perp(w), vt - vb); + R m = R(a) / pl::magnsq(w); + m = m * b; + return m; +}; + +template, + class R = TCompute, + class It = typename std::vector::const_iterator> +inline R rectarea(const Pt& w, const std::array& rect) +{ + return rectarea(w, *rect[0], *rect[1], *rect[2], *rect[3]); +} + +// This function is only applicable to counter-clockwise oriented convex +// polygons where only two points can be collinear witch each other. +template , + class Ratio = TCompute> +RotatedBox, Unit> minAreaBoundingBox(const RawShape& sh) +{ + using Point = TPoint; + using Iterator = typename TContour::const_iterator; + using pointlike::dot; using pointlike::magnsq; using pointlike::perp; + + // Get the first and the last vertex iterator + auto first = sl::cbegin(sh); + auto last = std::prev(sl::cend(sh)); + + // Check conditions and return undefined box if input is not sane. + if(last == first) return {}; + if(getX(*first) == getX(*last) && getY(*first) == getY(*last)) --last; + if(last - first < 2) return {}; + + RawShape shcpy; // empty at this point + { + Point p = *first, q = *std::next(first), r = *last; + + // Determine orientation from first 3 vertex (should be consistent) + Unit d = (Unit(getY(q)) - getY(p)) * (Unit(getX(r)) - getX(p)) - + (Unit(getX(q)) - getX(p)) * (Unit(getY(r)) - getY(p)); + + if(d > 0) { + // The polygon is clockwise. A flip is needed (for now) + sl::reserve(shcpy, last - first); + auto it = last; while(it != first) sl::addVertex(shcpy, *it--); + sl::addVertex(shcpy, *first); + first = sl::cbegin(shcpy); last = std::prev(sl::cend(shcpy)); + } + } + + // Cyclic iterator increment + auto inc = [&first, &last](Iterator& it) { + if(it == last) it = first; else ++it; + }; + + // Cyclic previous iterator + auto prev = [&first, &last](Iterator it) { + return it == first ? last : std::prev(it); + }; + + // Cyclic next iterator + auto next = [&first, &last](Iterator it) { + return it == last ? first : std::next(it); + }; + + // Establish initial (axis aligned) rectangle support verices by determining + // polygon extremes: + + auto it = first; + Iterator minX = it, maxX = it, minY = it, maxY = it; + + do { // Linear walk through the vertices and save the extreme positions + + Point v = *it, d = v - *minX; + if(getX(d) < 0 || (getX(d) == 0 && getY(d) < 0)) minX = it; + + d = v - *maxX; + if(getX(d) > 0 || (getX(d) == 0 && getY(d) > 0)) maxX = it; + + d = v - *minY; + if(getY(d) < 0 || (getY(d) == 0 && getX(d) > 0)) minY = it; + + d = v - *maxY; + if(getY(d) > 0 || (getY(d) == 0 && getX(d) < 0)) maxY = it; + + } while(++it != std::next(last)); + + // Update the vertices defining the bounding rectangle. The rectangle with + // the smallest rotation is selected and the supporting vertices are + // returned in the 'rect' argument. + auto update = [&next, &inc] + (const Point& w, std::array& rect) + { + Iterator B = rect[0], Bn = next(B); + Iterator R = rect[1], Rn = next(R); + Iterator T = rect[2], Tn = next(T); + Iterator L = rect[3], Ln = next(L); + + Point b = *Bn - *B, r = *Rn - *R, t = *Tn - *T, l = *Ln - *L; + Point pw = perp(w); + using Pt = Point; + + Unit dotwpb = dot( w, b), dotwpr = dot(-pw, r); + Unit dotwpt = dot(-w, t), dotwpl = dot( pw, l); + Unit dw = magnsq(w); + + std::array angles; + angles[0] = (Ratio(dotwpb) / magnsq(b)) * dotwpb; + angles[1] = (Ratio(dotwpr) / magnsq(r)) * dotwpr; + angles[2] = (Ratio(dotwpt) / magnsq(t)) * dotwpt; + angles[3] = (Ratio(dotwpl) / magnsq(l)) * dotwpl; + + using AngleIndex = std::pair; + std::vector A; A.reserve(4); + + for (size_t i = 3, j = 0; j < 4; i = j++) { + if(rect[i] != rect[j] && angles[i] < dw) { + auto iv = std::make_pair(angles[i], i); + auto it = std::lower_bound(A.begin(), A.end(), iv, + [](const AngleIndex& ai, + const AngleIndex& aj) + { + return ai.first > aj.first; + }); + + A.insert(it, iv); + } + } + + // The polygon is supposed to be a rectangle. + if(A.empty()) return false; + + auto amin = A.front().first; + auto imin = A.front().second; + for(auto& a : A) if(a.first == amin) inc(rect[a.second]); + + std::rotate(rect.begin(), rect.begin() + imin, rect.end()); + + return true; + }; + + Point w(1, 0); + Point w_min = w; + Ratio minarea((Unit(getX(*maxX)) - getX(*minX)) * + (Unit(getY(*maxY)) - getY(*minY))); + + std::array rect = {minY, maxX, maxY, minX}; + std::array minrect = rect; + + // An edge might be examined twice in which case the algorithm terminates. + size_t c = 0, count = last - first + 1; + std::vector edgemask(count, false); + + while(c++ < count) + { + // Update the support vertices, if cannot be updated, break the cycle. + if(! update(w, rect)) break; + + size_t eidx = size_t(rect[0] - first); + + if(edgemask[eidx]) break; + edgemask[eidx] = true; + + // get the unnormalized direction vector + w = *rect[0] - *prev(rect[0]); + + // get the area of the rotated rectangle + Ratio rarea = rectarea(w, rect); + + // Update min area and the direction of the min bounding box; + if(rarea <= minarea) { w_min = w; minarea = rarea; minrect = rect; } + } + + Unit a = dot(w_min, *minrect[1] - *minrect[3]); + Unit b = dot(-perp(w_min), *minrect[2] - *minrect[0]); + RotatedBox bb(w_min, a, b); + + return bb; +} + +template Radians minAreaBoundingBoxRotation(const RawShape& sh) +{ + return minAreaBoundingBox(sh).angleToX(); +} + + +} + +#endif // ROTCALIPERS_HPP diff --git a/src/libnest2d/src/libnest2d.cpp b/src/libnest2d/src/libnest2d.cpp new file mode 100644 index 0000000000..0214587874 --- /dev/null +++ b/src/libnest2d/src/libnest2d.cpp @@ -0,0 +1,23 @@ +#include + +namespace libnest2d { + +template class Nester; +template class Nester; + +template PackGroup nest(std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + Coord dist = 0, + const NfpPlacer::Config& pconf, + const FirstFitSelection::Config& sconf); + +template PackGroup nest(std::vector::iterator from, + std::vector::iterator to, + const Box& bin, + ProgressFunction prg, + StopCondition scond, + Coord dist = 0, + const NfpPlacer::Config& pconf, + const FirstFitSelection::Config& sconf); +} diff --git a/src/libnest2d/tests/CMakeLists.txt b/src/libnest2d/tests/CMakeLists.txt index fc3cd309dd..1b7d8e3ae6 100644 --- a/src/libnest2d/tests/CMakeLists.txt +++ b/src/libnest2d/tests/CMakeLists.txt @@ -49,7 +49,12 @@ add_executable(tests_clipper_nlopt target_link_libraries(tests_clipper_nlopt libnest2d ${GTEST_LIBS_TO_LINK} ) -target_include_directories(tests_clipper_nlopt PRIVATE BEFORE - ${GTEST_INCLUDE_DIRS}) +target_include_directories(tests_clipper_nlopt PRIVATE BEFORE ${GTEST_INCLUDE_DIRS}) + +if(NOT LIBNEST2D_HEADER_ONLY) + target_link_libraries(tests_clipper_nlopt ${LIBNAME}) +else() + target_link_libraries(tests_clipper_nlopt libnest2d) +endif() add_test(libnest2d_tests tests_clipper_nlopt) diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 3b0eae1618..5741e87b4f 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -3,11 +3,43 @@ #include #include "printer_parts.h" -#include +//#include #include "../tools/svgtools.hpp" +#include + +#include "boost/multiprecision/integer.hpp" +#include "boost/rational.hpp" + +//#include "../tools/Int128.hpp" + +//#include "gte/Mathematics/GteMinimumAreaBox2.h" + //#include "../tools/libnfpglue.hpp" //#include "../tools/nfp_svgnest_glue.hpp" +namespace libnest2d { +#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) +using LargeInt = __int128; +#else +using LargeInt = boost::multiprecision::int128_t; +template<> struct _NumTag { using Type = ScalarTag; }; +#endif +template struct _NumTag> { using Type = RationalTag; }; + +namespace nfp { + +template +struct NfpImpl +{ + NfpResult operator()(const S &sh, const S &other) + { + return nfpConvexOnly>(sh, other); + } +}; + +} +} + std::vector& prusaParts() { static std::vector ret; @@ -31,8 +63,8 @@ TEST(BasicFunctionality, Angles) ASSERT_DOUBLE_EQ(rad, Pi); ASSERT_DOUBLE_EQ(deg, 180); ASSERT_DOUBLE_EQ(deg2, 180); - ASSERT_DOUBLE_EQ(rad, (Radians) deg); - ASSERT_DOUBLE_EQ( (Degrees) rad, deg); + ASSERT_DOUBLE_EQ(rad, Radians(deg)); + ASSERT_DOUBLE_EQ( Degrees(rad), deg); ASSERT_TRUE(rad == deg); @@ -151,12 +183,12 @@ TEST(GeometryAlgorithms, Distance) { Segment seg(p1, p3); - ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); +// ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); auto result = pointlike::horizontalDistance(p2, seg); - auto check = [](Coord val, Coord expected) { - if(std::is_floating_point::value) + auto check = [](TCompute val, TCompute expected) { + if(std::is_floating_point>::value) ASSERT_DOUBLE_EQ(static_cast(val), static_cast(expected)); else @@ -415,7 +447,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) namespace { using namespace libnest2d; -template +template void exportSVG(std::vector>& result, const Bin& bin, int idx = 0) { @@ -500,6 +532,41 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { } } +TEST(GeometryAlgorithms, convexHull) { + using namespace libnest2d; + + ClipperLib::Path poly = PRINTER_PART_POLYGONS[0]; + + auto chull = sl::convexHull(poly); + + ASSERT_EQ(chull.size(), poly.size()); +} + + +TEST(GeometryAlgorithms, NestTest) { + std::vector input = prusaParts(); + + PackGroup result = libnest2d::nest(input, + Box(250000000, 210000000), + [](unsigned cnt) { + std::cout + << "parts left: " << cnt + << std::endl; + }); + + ASSERT_LE(result.size(), 2); + + int partsum = std::accumulate(result.begin(), + result.end(), + 0, + [](int s, + const decltype(result)::value_type &bin) { + return s += bin.size(); + }); + + ASSERT_EQ(input.size(), partsum); +} + namespace { struct ItemPair { @@ -713,7 +780,7 @@ void testNfp(const std::vector& testdata) { auto& exportfun = exportSVG; - auto onetest = [&](Item& orbiter, Item& stationary, unsigned testidx){ + auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){ testcase++; orbiter.translate({210*SCALE, 0}); @@ -820,7 +887,7 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) { rect2.translate({10, 0}); rect3.translate({25, 0}); - shapelike::Shapes pile; + TMultiShape pile; pile.push_back(rect1.transformedShape()); pile.push_back(rect2.transformedShape()); @@ -833,6 +900,126 @@ TEST(GeometryAlgorithms, mergePileWithPolygon) { ASSERT_EQ(shapelike::area(result.front()), ref.area()); } +namespace { + +long double refMinAreaBox(const PolygonImpl& p) { + + auto it = sl::cbegin(p), itx = std::next(it); + + long double min_area = std::numeric_limits::max(); + + + auto update_min = [&min_area, &it, &itx, &p]() { + Segment s(*it, *itx); + + PolygonImpl rotated = p; + sl::rotate(rotated, -s.angleToXaxis()); + auto bb = sl::boundingBox(rotated); + auto area = cast(sl::area(bb)); + if(min_area > area) min_area = area; + }; + + while(itx != sl::cend(p)) { + update_min(); + ++it; ++itx; + } + + it = std::prev(sl::cend(p)); itx = sl::cbegin(p); + update_min(); + + return min_area; +} + +template struct BoostGCD { + T operator()(const T &a, const T &b) { return boost::gcd(a, b); } +}; + +using Unit = int64_t; +using Ratio = boost::rational;// Rational; + +//double gteMinAreaBox(const PolygonImpl& p) { + +// using GteCoord = ClipperLib::cInt; +// using GtePoint = gte::Vector2; + +// gte::MinimumAreaBox2 mb; + +// std::vector points; +// points.reserve(p.Contour.size()); + +// for(auto& pt : p.Contour) points.emplace_back(GtePoint{GteCoord(pt.X), GteCoord(pt.Y)}); + +// mb(int(points.size()), points.data(), 0, nullptr, true); + +// auto min_area = double(mb.GetArea()); + +// return min_area; +//} + +} + +TEST(RotatingCalipers, MinAreaBBCClk) { +// PolygonImpl poly({{-50, 30}, {-50, -50}, {50, -50}, {50, 50}, {-40, 50}}); + +// PolygonImpl poly({{-50, 0}, {50, 0}, {0, 100}}); + + auto u = [](ClipperLib::cInt n) { return n*1000000; }; + PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}}); + + + long double arearef = refMinAreaBox(poly); + long double area = minAreaBoundingBox(poly).area(); +// double gtearea = gteMinAreaBox(poly); + + ASSERT_LE(std::abs(area - arearef), 500e6 ); +// ASSERT_LE(std::abs(gtearea - arearef), 500 ); +// ASSERT_DOUBLE_EQ(gtearea, arearef); +} + +TEST(RotatingCalipers, AllPrusaMinBB) { + size_t idx = 0; + long double err_epsilon = 500e6l; + + for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) { +// ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx]; +// rinput.pop_back(); +// std::reverse(rinput.begin(), rinput.end()); + +// PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); + PolygonImpl poly(rinput); + + long double arearef = refMinAreaBox(poly); + auto bb = minAreaBoundingBox(rinput); + long double area = cast(bb.area()); +// double area = gteMinAreaBox(poly); + + bool succ = std::abs(arearef - area) < err_epsilon; + std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " + << arearef << " actual: " << area << std::endl; + + ASSERT_TRUE(succ); + } + + for(ClipperLib::Path rinput : STEGOSAUR_POLYGONS) { + rinput.pop_back(); + std::reverse(rinput.begin(), rinput.end()); + + PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); + + + long double arearef = refMinAreaBox(poly); + auto bb = minAreaBoundingBox(poly); + long double area = cast(bb.area()); +// double area = gteMinAreaBox(poly); + + bool succ = std::abs(arearef - area) < err_epsilon; + std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " + << arearef << " actual: " << area << std::endl; + + ASSERT_TRUE(succ); + } +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 833b1ae629..e06a5f52ab 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -163,6 +163,8 @@ add_library(libslic3r STATIC MTUtils.hpp Zipper.hpp Zipper.cpp + MinAreaBoundingBox.hpp + MinAreaBoundingBox.cpp miniz_extension.hpp miniz_extension.cpp SLA/SLABoilerPlate.hpp diff --git a/src/libslic3r/Int128.hpp b/src/libslic3r/Int128.hpp index 56dc5f461d..8dc9e012dd 100644 --- a/src/libslic3r/Int128.hpp +++ b/src/libslic3r/Int128.hpp @@ -37,6 +37,8 @@ * * *******************************************************************************/ +#ifndef SLIC3R_INT128_HPP +#define SLIC3R_INT128_HPP // #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG @@ -48,6 +50,8 @@ #endif #include +#include +#include #if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__) #define HAS_INTRINSIC_128_TYPE @@ -293,3 +297,5 @@ public: return sign_determinant_2x2(p1, q1, p2, q2) * invert; } }; + +#endif // SLIC3R_INT128_HPP diff --git a/src/libslic3r/MinAreaBoundingBox.cpp b/src/libslic3r/MinAreaBoundingBox.cpp new file mode 100644 index 0000000000..6fc1b3327a --- /dev/null +++ b/src/libslic3r/MinAreaBoundingBox.cpp @@ -0,0 +1,142 @@ +#include "MinAreaBoundingBox.hpp" + +#include +#include + +#include + +#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__) +#include +#endif + +#include +#include + +namespace libnest2d { + +template<> struct PointType { using Type = Slic3r::Point; }; +template<> struct CoordType { using Type = coord_t; }; +template<> struct ShapeTag { using Type = PolygonTag; }; +template<> struct ShapeTag { using Type = PolygonTag; }; +template<> struct ShapeTag { using Type = PathTag; }; +template<> struct ShapeTag { using Type = PointTag; }; +template<> struct ContourType { using Type = Slic3r::Points; }; +template<> struct ContourType { using Type = Slic3r::Points; }; + +namespace pointlike { + +template<> inline coord_t x(const Slic3r::Point& p) { return p.x(); } +template<> inline coord_t y(const Slic3r::Point& p) { return p.y(); } +template<> inline coord_t& x(Slic3r::Point& p) { return p.x(); } +template<> inline coord_t& y(Slic3r::Point& p) { return p.y(); } + +} // pointlike + +namespace shapelike { +template<> inline Slic3r::Points& contour(Slic3r::ExPolygon& sh) { return sh.contour.points; } +template<> inline const Slic3r::Points& contour(const Slic3r::ExPolygon& sh) { return sh.contour.points; } +template<> inline Slic3r::Points& contour(Slic3r::Polygon& sh) { return sh.points; } +template<> inline const Slic3r::Points& contour(const Slic3r::Polygon& sh) { return sh.points; } + +template<> Slic3r::Points::iterator begin(Slic3r::Points& pts, const PathTag&) { return pts.begin();} +template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.begin(); } +template<> Slic3r::Points::iterator end(Slic3r::Points& pts, const PathTag&) { return pts.end();} +template<> Slic3r::Points::const_iterator cend(const Slic3r::Points& pts, const PathTag&) { return pts.cend(); } + +template<> inline Slic3r::ExPolygon create(Slic3r::Points&& contour) +{ + Slic3r::ExPolygon expoly; expoly.contour.points.swap(contour); + return expoly; +} + +template<> inline Slic3r::Polygon create(Slic3r::Points&& contour) +{ + Slic3r::Polygon poly; poly.points.swap(contour); + return poly; +} + +} // shapelike +} // libnest2d + +namespace Slic3r { + +// Used as compute type. +using Unit = int64_t; + +#if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__) +using Rational = boost::rational; +#else +using Rational = boost::rational<__int128>; +#endif + +MinAreaBoundigBox::MinAreaBoundigBox(const Polygon &p, PolygonLevel pc) +{ + const Polygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = box.right_extent(); + m_bottom = box.bottom_extent(); + m_axis = box.axis(); +} + +MinAreaBoundigBox::MinAreaBoundigBox(const ExPolygon &p, PolygonLevel pc) +{ + const ExPolygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = box.right_extent(); + m_bottom = box.bottom_extent(); + m_axis = box.axis(); +} + +MinAreaBoundigBox::MinAreaBoundigBox(const Points &pts, PolygonLevel pc) +{ + const Points& chull = pc == pcConvex ? pts : libnest2d::sl::convexHull(pts); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = box.right_extent(); + m_bottom = box.bottom_extent(); + m_axis = box.axis(); +} + +double MinAreaBoundigBox::angle_to_X() const +{ + double ret = std::atan2(m_axis.y(), m_axis.x()); + auto s = std::signbit(ret); + if(s) ret += 2 * PI; + return -ret; +} + +long double MinAreaBoundigBox::width() const +{ + return std::abs(m_bottom) / std::sqrt(libnest2d::pl::magnsq(m_axis)); +} + +long double MinAreaBoundigBox::height() const +{ + return std::abs(m_right) / std::sqrt(libnest2d::pl::magnsq(m_axis)); +} + +long double MinAreaBoundigBox::area() const +{ + long double asq = libnest2d::pl::magnsq(m_axis); + return m_bottom * m_right / asq; +} + +void remove_collinear_points(Polygon &p) +{ + p = libnest2d::removeCollinearPoints(p, Unit(0)); +} + +void remove_collinear_points(ExPolygon &p) +{ + p = libnest2d::removeCollinearPoints(p, Unit(0)); +} + +} diff --git a/src/libslic3r/MinAreaBoundingBox.hpp b/src/libslic3r/MinAreaBoundingBox.hpp new file mode 100644 index 0000000000..30d0e9799d --- /dev/null +++ b/src/libslic3r/MinAreaBoundingBox.hpp @@ -0,0 +1,59 @@ +#ifndef MINAREABOUNDINGBOX_HPP +#define MINAREABOUNDINGBOX_HPP + +#include "libslic3r/Point.hpp" + +namespace Slic3r { + +class Polygon; +class ExPolygon; + +void remove_collinear_points(Polygon& p); +void remove_collinear_points(ExPolygon& p); + +/// A class that holds a rotated bounding box. If instantiated with a polygon +/// type it will hold the minimum area bounding box for the given polygon. +/// If the input polygon is convex, the complexity is linear to the number of +/// points. Otherwise a convex hull of O(n*log(n)) has to be performed. +class MinAreaBoundigBox { + Point m_axis; + long double m_bottom = 0.0l, m_right = 0.0l; +public: + + // Polygons can be convex or simple (convex or concave with possible holes) + enum PolygonLevel { + pcConvex, pcSimple + }; + + // Constructors with various types of geometry data used in Slic3r. + // If the convexity is known apriory, pcConvex can be used to skip + // convex hull calculation. It is very important that the input polygons + // do NOT have any collinear points (except for the first and the last + // vertex being the same -- meaning a closed polygon for boost) + // To make sure this constraint is satisfied, you can call + // remove_collinear_points on the input polygon before handing over here) + explicit MinAreaBoundigBox(const Polygon&, PolygonLevel = pcSimple); + explicit MinAreaBoundigBox(const ExPolygon&, PolygonLevel = pcSimple); + explicit MinAreaBoundigBox(const Points&, PolygonLevel = pcSimple); + + // Returns the angle in radians needed for the box to be aligned with the + // X axis. Rotate the polygon by this angle and it will be aligned. + double angle_to_X() const; + + // The box width + long double width() const; + + // The box height + long double height() const; + + // The box area + long double area() const; + + // The axis of the rotated box. If the angle_to_X is not sufficiently + // precise, use this unnormalized direction vector. + const Point& axis() const { return m_axis; } +}; + +} + +#endif // MINAREABOUNDINGBOX_HPP diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index b088a1f176..b323285ccb 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -9,6 +9,31 @@ #include #include +#include +#include + +namespace libnest2d { +#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) +using LargeInt = __int128; +#else +using LargeInt = boost::multiprecision::int128_t; +template<> struct _NumTag { using Type = ScalarTag; }; +#endif +template struct _NumTag> { using Type = RationalTag; }; + +namespace nfp { + +template +struct NfpImpl +{ + NfpResult operator()(const S &sh, const S &other) + { + return nfpConvexOnly>(sh, other); + } +}; + +} +} namespace Slic3r { @@ -130,7 +155,7 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) { // at the same time, it has to provide reasonable results. std::tuple objfunc(const PointImpl& bincenter, - const shapelike::Shapes& merged_pile, + const TMultiShape& merged_pile, const Box& pilebb, const ItemGroup& items, const Item &item, @@ -301,7 +326,7 @@ protected: using Packer = Nester; using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; - using Pile = sl::Shapes; + using Pile = TMultiShape; Packer m_pck; PConfig m_pconf; // Placement configuration @@ -589,7 +614,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& // Invalid geometries would throw exceptions when arranging if(item.vertexCount() > 3) { - item.rotation(float(Geometry::rotation_diff_z(rotation0, objinst->get_rotation()))), + item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation())); item.translation({ ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 44f77b3f78..193abba060 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -37,7 +37,12 @@ #include "libslic3r/SLA/SLARotfinder.hpp" #include "libslic3r/Utils.hpp" -#include "libnest2d/optimizers/nlopt/genetic.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" @@ -2261,46 +2266,18 @@ void Plater::priv::sla_optimize_rotation() { for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); double mindist = 6.0; // FIXME - double offs = mindist / 2.0 - EPSILON; - + if(rotoptimizing) // wasn't canceled for(ModelInstance * oi : o->instances) { oi->set_rotation({r[X], r[Y], r[Z]}); - - auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix()); - - namespace opt = libnest2d::opt; - opt::StopCriteria stopcr; - stopcr.relative_score_difference = 0.01; - stopcr.max_iterations = 10000; - stopcr.stop_score = 0.0; - opt::GeneticOptimizer solver(stopcr); - Polygon pbed(bed); - - auto bin = pbed.bounding_box(); - double binw = bin.size()(X) * SCALING_FACTOR - offs; - double binh = bin.size()(Y) * SCALING_FACTOR - offs; - - auto result = solver.optimize_min([&trchull, binw, binh](double rot){ - auto chull = trchull; - chull.rotate(rot); - - auto bb = chull.bounding_box(); - double bbw = bb.size()(X) * SCALING_FACTOR; - double bbh = bb.size()(Y) * SCALING_FACTOR; - - auto wdiff = bbw - binw; - auto hdiff = bbh - binh; - double diff = 0; - if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff; - if(wdiff > 0) diff += wdiff; - if(hdiff > 0) diff += hdiff; - - return diff; - }, opt::initvals(0.0), opt::bound(-PI/2, PI/2)); - - double r = std::get<0>(result.optimum); - + + Polygon trchull = o->convex_hull_2d(oi->get_transformation().get_matrix()); + 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); } From d809b4894a910ebcefcd89d8776697a7c38c86f4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 6 Jun 2019 12:21:38 +0200 Subject: [PATCH 054/627] Small addition to qhull dep handling. --- src/qhull/CMakeLists.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/qhull/CMakeLists.txt b/src/qhull/CMakeLists.txt index 262214a5c4..734bb82950 100644 --- a/src/qhull/CMakeLists.txt +++ b/src/qhull/CMakeLists.txt @@ -10,7 +10,7 @@ # see bug report: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=925540 -# find_package(Qhull 7.2 QUIET) +find_package(Qhull 7.2 QUIET) add_library(qhull INTERFACE) @@ -139,9 +139,7 @@ endif(UNIX) ################################################## # LIBDIR is defined in the main xs CMake file: -target_include_directories(${qhull_STATIC} PRIVATE ${LIBDIR}/qhull/src) - +target_include_directories(${qhull_STATIC} BEFORE PUBLIC ${LIBDIR}/qhull/src) target_link_libraries(qhull INTERFACE ${qhull_STATIC}) -target_include_directories(qhull INTERFACE ${LIBDIR}/qhull/src) endif() From 71cc0fdb535bdf1cb4c35429655ed75debf0afce Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 6 Jun 2019 14:14:29 +0200 Subject: [PATCH 055/627] Some code refactoring and improvements --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 79 ++++++-------- src/slic3r/GUI/GUI_ObjectLayers.hpp | 8 +- src/slic3r/GUI/GUI_ObjectList.cpp | 157 +++++++++++++--------------- src/slic3r/GUI/GUI_ObjectList.hpp | 6 +- src/slic3r/GUI/Tab.cpp | 7 +- src/slic3r/GUI/wxExtensions.cpp | 61 ++++++++--- src/slic3r/GUI/wxExtensions.hpp | 11 +- 7 files changed, 171 insertions(+), 158 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 1f0511acb7..ba0012a2b7 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -16,8 +16,6 @@ namespace Slic3r namespace GUI { -#define field_width 8 - ObjectLayers::ObjectLayers(wxWindow* parent) : OG_Settings(parent, true) { @@ -41,27 +39,27 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : m_bmp_add = ScalableBitmap(parent, "add_copies"); } -wxSizer* ObjectLayers::create_layer_without_buttons(const t_layer_config_ranges::value_type& layer) +wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) { - const bool is_last_edited_range = layer.first == m_last_edited_range; + const bool is_last_edited_range = range == m_last_edited_range; // Add control for the "Min Z" - auto temp = new LayerRangeEditor(m_parent, double_to_string(layer.first.first), - [layer, this](coordf_t min_z) + auto temp = new LayerRangeEditor(m_parent, double_to_string(range.first), + [range, this](coordf_t min_z) { - if (fabs(min_z - layer.first.first) < EPSILON) { - m_selection_type = sitUndef; + if (fabs(min_z - range.first) < EPSILON) { + m_selection_type = sitUndef; return false; // LayersList would not be updated/recreated } // data for next focusing - m_last_edited_range = { min_z, layer.first.second }; + m_last_edited_range = { min_z, range.second }; m_selection_type = sitMinZ; - wxGetApp().obj_list()->edit_layer_range(layer.first, m_last_edited_range); + wxGetApp().obj_list()->edit_layer_range(range, m_last_edited_range); return true; // LayersList will be updated/recreated - } ); + }); if (is_last_edited_range && m_selection_type == sitMinZ) { temp->SetFocus(); @@ -72,19 +70,19 @@ wxSizer* ObjectLayers::create_layer_without_buttons(const t_layer_config_ranges: // Add control for the "Max Z" - temp = new LayerRangeEditor(m_parent, double_to_string(layer.first.second), - [layer, this](coordf_t max_z) + temp = new LayerRangeEditor(m_parent, double_to_string(range.second), + [range, this](coordf_t max_z) { - if (fabs(max_z - layer.first.second) < EPSILON) { + if (fabs(max_z - range.second) < EPSILON) { m_selection_type = sitUndef; return false; // LayersList would not be updated/recreated } // data for next focusing - m_last_edited_range = { layer.first.first, max_z }; + m_last_edited_range = { range.first, max_z }; m_selection_type = sitMaxZ; - wxGetApp().obj_list()->edit_layer_range(layer.first, m_last_edited_range); + wxGetApp().obj_list()->edit_layer_range(range, m_last_edited_range); return true; // LayersList will not be updated/recreated }); @@ -92,20 +90,20 @@ wxSizer* ObjectLayers::create_layer_without_buttons(const t_layer_config_ranges: temp->SetFocus(); temp->SetInsertionPointEnd(); } - + m_grid_sizer->Add(temp); // Add control for the "Layer height" - temp = new LayerRangeEditor(m_parent, - double_to_string(layer.second.option("layer_height")->getFloat()), - [layer, this](coordf_t layer_height) + temp = new LayerRangeEditor(m_parent, + double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()), + [range, this](coordf_t layer_height) { - wxGetApp().obj_list()->edit_layer_range(layer.first, layer_height); + wxGetApp().obj_list()->edit_layer_range(range, layer_height); return false; // LayersList would not be updated/recreated }); - auto sizer = new wxBoxSizer(wxHORIZONTAL); + auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(temp); m_grid_sizer->Add(sizer); @@ -116,42 +114,29 @@ void ObjectLayers::create_layers_list() { for (const auto layer : m_object->layer_config_ranges) { - auto sizer = create_layer_without_buttons(layer); + const t_layer_height_range& range = layer.first; + auto sizer = create_layer(range); - wxWindow* parent = m_parent; - auto del_btn = new ScalableButton(parent, wxID_ANY, m_bmp_delete); + auto del_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_delete); del_btn->SetToolTip(_(L("Remove layer"))); - sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, em_unit(parent)); + sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, em_unit(m_parent)); - del_btn->Bind(wxEVT_BUTTON, [this, layer](wxEvent &event) { - wxGetApp().obj_list()->del_layer_range(layer.first); + del_btn->Bind(wxEVT_BUTTON, [this, range](wxEvent &event) { + wxGetApp().obj_list()->del_layer_range(range); }); - auto add_btn = new ScalableButton(parent, wxID_ANY, m_bmp_add); + auto add_btn = new ScalableButton(m_parent, wxID_ANY, m_bmp_add); add_btn->SetToolTip(_(L("Add layer"))); - sizer->Add(add_btn, 0, wxRIGHT, em_unit(parent)); + sizer->Add(add_btn, 0, wxRIGHT, em_unit(m_parent)); - add_btn->Bind(wxEVT_BUTTON, [this, layer](wxEvent &event) { - wxGetApp().obj_list()->add_layer_range(layer.first); + add_btn->Bind(wxEVT_BUTTON, [this, range](wxEvent &event) { + wxGetApp().obj_list()->add_layer_range_after_current(range); }); } } -void ObjectLayers::create_layer(int id) -{ - t_layer_config_ranges::iterator layer_range = m_object->layer_config_ranges.begin(); - - // May be not a best solution #ys_FIXME - while (id > 0 && layer_range != m_object->layer_config_ranges.end()) { - ++layer_range; - id--; - } - - create_layer_without_buttons(*layer_range); -} - void ObjectLayers::update_layers_list() { ObjectList* objects_ctrl = wxGetApp().obj_list(); @@ -187,7 +172,7 @@ void ObjectLayers::update_layers_list() if (type & itLayerRoot) create_layers_list(); else - create_layer(objects_ctrl->GetModel()->GetLayerIdByItem(item)); + create_layer(objects_ctrl->GetModel()->GetLayerRangeByItem(item)); m_parent->Layout(); } @@ -211,7 +196,7 @@ LayerRangeEditor::LayerRangeEditor( wxWindow* parent, std::function edit_fn ) : wxTextCtrl(parent, wxID_ANY, value, wxDefaultPosition, - wxSize(field_width * em_unit(parent), wxDefaultCoord), wxTE_PROCESS_ENTER) + wxSize(8 * em_unit(parent), wxDefaultCoord), wxTE_PROCESS_ENTER) { this->SetFont(wxGetApp().normal_font()); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 55002ff354..562e049728 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -16,9 +16,8 @@ class ModelObject; namespace GUI { class ConfigOptionsGroup; -typedef double coordf_t; -typedef std::pair t_layer_height_range; -typedef std::map t_layer_config_ranges; +typedef double coordf_t; +typedef std::pair t_layer_height_range; class LayerRangeEditor : public wxTextCtrl { @@ -57,8 +56,7 @@ public: ObjectLayers(wxWindow* parent); ~ObjectLayers() {} - wxSizer* create_layer_without_buttons(const t_layer_config_ranges::value_type& layer); - void create_layer(int id); + wxSizer* create_layer(const t_layer_height_range& range); // without_buttons void create_layers_list(); void update_layers_list(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index f68130ddd1..3060a166d5 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -351,30 +351,16 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons const ItemType type = m_objects_model->GetItemType(item); const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : - m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; assert(obj_idx >= 0 || ((type & itVolume) && vol_idx >=0)); return type & itVolume ?(*m_objects)[obj_idx]->volumes[vol_idx]->config : + type & itLayer ?(*m_objects)[obj_idx]->layer_config_ranges[m_objects_model->GetLayerRangeByItem(item)] : (*m_objects)[obj_idx]->config; } -const t_layer_height_range& ObjectList::get_layer_range_from_item(const wxDataViewItem layer_item, const int obj_idx) const -{ - ModelObject* object = (*m_objects)[obj_idx]; - t_layer_config_ranges::iterator layer_range = object->layer_config_ranges.begin(); - int id = m_objects_model->GetLayerIdByItem(layer_item); - - // May be not a best solution #ys_FIXME - while (id > 0 && layer_range != object->layer_config_ranges.end()) { - ++layer_range; - id--; - } - - return layer_range->first; -} - wxDataViewColumn* ObjectList::create_objects_list_extruder_column(int extruders_count) { wxArrayString choices; @@ -457,16 +443,23 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item) { if (m_prevent_update_extruder_in_config) return; - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + + const ItemType item_type = m_objects_model->GetItemType(item); + if (item_type & itObject) { const int obj_idx = m_objects_model->GetIdByItem(item); m_config = &(*m_objects)[obj_idx]->config; } else { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); + const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + if (item_type & itVolume) + { const int volume_id = m_objects_model->GetVolumeIdByItem(item); if (obj_idx < 0 || volume_id < 0) return; m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; + } + else if (item_type & itLayer) + m_config = &get_item_config(item); } wxVariant variant; @@ -669,7 +662,7 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) const wxPoint pt = get_mouse_position_in_control(); HitTest(pt, item, col); if (!item) -#ifdef __WXOSX__ // #ys_FIXME temporary workaround for OSX +#ifdef __WXOSX__ // temporary workaround for OSX // after Yosemite OS X version, HitTest return undefined item item = GetSelection(); if (item) @@ -1668,40 +1661,52 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) ItemType type; m_objects_model->GetItemInfo(item, type, obj_idx, idx); - if (type == itUndef) + if (type & itUndef) return; - if (type == itSettings) - del_settings_from_config(); - else if (type == itInstanceRoot && obj_idx != -1) + if (type & itSettings) + del_settings_from_config(m_objects_model->GetParent(item)); + else if (type & itInstanceRoot && obj_idx != -1) del_instances_from_object(obj_idx); - else if ((type & itLayerRoot) && obj_idx != -1) + else if (type & itLayerRoot && obj_idx != -1) del_layers_from_object(obj_idx); + else if (type & itLayer && obj_idx != -1) + del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item)); else if (idx == -1) return; else if (!del_subobject_from_object(obj_idx, idx, type)) return; // If last volume item with warning was deleted, unmark object item - if (type == itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) + if (type & itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) m_objects_model->DeleteWarningIcon(m_objects_model->GetParent(item)); m_objects_model->Delete(item); } -void ObjectList::del_settings_from_config() +void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item) { - auto opt_keys = m_config->keys(); - if (opt_keys.size() == 1 && opt_keys[0] == "extruder") + const bool is_layer_settings = m_objects_model->GetItemType(parent_item) == itLayer; + + const int opt_cnt = m_config->keys().size(); + if (opt_cnt == 1 && m_config->has("extruder") || + is_layer_settings && opt_cnt == 2 && m_config->has("extruder") && m_config->has("layer_height")) return; + int extruder = -1; if (m_config->has("extruder")) extruder = m_config->option("extruder")->value; + coordf_t layer_height = 0.0; + if (is_layer_settings) + layer_height = m_config->opt_float("layer_height"); + m_config->clear(); if (extruder >= 0) m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); + if (is_layer_settings) + m_config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); } void ObjectList::del_instances_from_object(const int obj_idx) @@ -1718,6 +1723,17 @@ void ObjectList::del_instances_from_object(const int obj_idx) changed_object(obj_idx); } +void ObjectList::del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range) +{ + const auto del_range = object(obj_idx)->layer_config_ranges.find(layer_range); + if (del_range == object(obj_idx)->layer_config_ranges.end()) + return; + + object(obj_idx)->layer_config_ranges.erase(del_range); + + changed_object(obj_idx); +} + void ObjectList::del_layers_from_object(const int obj_idx) { object(obj_idx)->layer_config_ranges.clear(); @@ -1764,15 +1780,6 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con } object->delete_instance(idx); } - else if (type == itLayer) { - t_layer_config_ranges::iterator layer_range = object->layer_config_ranges.begin(); - int id = idx; - while (id > 0 && layer_range != object->layer_config_ranges.end()) { - layer_range++; - id--; - } - object->layer_config_ranges.erase(layer_range); - } else return false; @@ -1982,9 +1989,7 @@ void ObjectList::part_selection_changed() } else if (parent_type & itLayer) { og_name = _(L("Layer range Settings to modify")); - - const t_layer_height_range& layer_height_range = get_layer_range_from_item(parent, obj_idx); - m_config = &(*m_objects)[obj_idx]->layer_config_ranges[layer_height_range]; + m_config = &get_item_config(parent); } update_and_show_settings = true; } @@ -2005,8 +2010,8 @@ void ObjectList::part_selection_changed() og_name = type & itLayerRoot ? _(L("Layers Editing")) : _(L("Layer Editing")); update_and_show_layers = true; - const t_layer_height_range& layer_height_range = get_layer_range_from_item(item, obj_idx); - m_config = &(*m_objects)[obj_idx]->layer_config_ranges[layer_height_range]; + if (type & itLayer) + m_config = &get_item_config(item); } } } @@ -2252,7 +2257,7 @@ void ObjectList::remove() } } -void ObjectList::del_layer_range(const std::pair& range) +void ObjectList::del_layer_range(const t_layer_height_range& range) { const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return; @@ -2260,73 +2265,58 @@ void ObjectList::del_layer_range(const std::pair& range) t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; wxDataViewItem selectable_item = GetSelection(); - int layer_idx = 0; if (ranges.size() == 1) selectable_item = m_objects_model->GetParent(selectable_item); - else { - // May be not a best solution #ys_FIXME - t_layer_config_ranges::iterator layer_selected = ranges.find(range); - t_layer_config_ranges::iterator it = ranges.begin(); - while (it != layer_selected) { - ++it; - layer_idx++; - } - } - wxDataViewItem layer_item = m_objects_model->GetItemByLayerId(obj_idx, layer_idx); + wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, range); del_subobject_item(layer_item); select_item(selectable_item); } -void ObjectList::add_layer_range(const t_layer_height_range& range) +void ObjectList::add_layer_range_after_current(const t_layer_height_range& current_range) { const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return; - wxDataViewItem layers_item = GetSelection(); + const wxDataViewItem layers_item = GetSelection(); t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; - const t_layer_config_ranges::iterator selected_range = ranges.find(range); - const t_layer_config_ranges::iterator last_range = --ranges.end(); + const t_layer_height_range& last_range = (--ranges.end())->first; - if (selected_range->first == last_range->first) + if (current_range == last_range) { - const t_layer_height_range new_range = { last_range->first.second, last_range->first.second + 0.5f }; + const t_layer_height_range& new_range = { last_range.second, last_range.second + 0.5f }; ranges[new_range] = get_default_layer_config(obj_idx); add_layer_item(new_range, layers_item); } else { - int layer_idx = 0; - t_layer_config_ranges::iterator next_range = ++ranges.find(range); + const t_layer_height_range& next_range = (++ranges.find(current_range))->first; - // May be not a best solution #ys_FIXME - t_layer_config_ranges::iterator it = ranges.begin(); - while (it != next_range && it != ranges.end()) { - layer_idx++; - ++it; - } - - if (selected_range->first.second > next_range->first.first) - return; // range devision has no mean + if (current_range.second > next_range.first) + return; // range division has no sense - if (selected_range->first.second == next_range->first.first) + const int layer_idx = m_objects_model->GetItemIdByLayerRange(obj_idx, next_range); + if (layer_idx < 0) + return; + + if (current_range.second == next_range.first) { - const coordf_t delta = (next_range->first.second - next_range->first.first); - if (delta < 0.05f) // next range devision has no mean + const coordf_t delta = (next_range.second - next_range.first); + if (delta < 0.05f) // next range division has no sense return; - const coordf_t midl_layer = next_range->first.first + 0.5f * delta; - // #ys_FIXME May be it should be copied just a "layer_height" option - const /*coordf_t*/auto old_config = next_range->second; - t_layer_height_range new_range = { midl_layer, next_range->first.second }; + const coordf_t midl_layer = next_range.first + 0.5f * delta; + + const auto old_config = ranges.at(next_range); + t_layer_height_range new_range = { midl_layer, next_range.second }; // delete old layer - wxDataViewItem layer_item = m_objects_model->GetItemByLayerId(obj_idx, layer_idx); + wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, next_range); del_subobject_item(layer_item); // create new 2 layers instead of deleted one @@ -2334,13 +2324,13 @@ void ObjectList::add_layer_range(const t_layer_height_range& range) ranges[new_range] = old_config; add_layer_item(new_range, layers_item, layer_idx); - new_range = { selected_range->first.second, midl_layer }; + new_range = { current_range.second, midl_layer }; ranges[new_range] = get_default_layer_config(obj_idx); add_layer_item(new_range, layers_item, layer_idx); } else { - const t_layer_height_range new_range = { selected_range->first.second, next_range->first.first }; + const t_layer_height_range new_range = { current_range.second, next_range.first }; ranges[new_range] = get_default_layer_config(obj_idx); add_layer_item(new_range, layers_item, layer_idx); } @@ -2356,15 +2346,12 @@ void ObjectList::add_layer_item(const t_layer_height_range& range, const wxDataViewItem layers_item, const int layer_idx /* = -1*/) { - const std::string label = (boost::format(" %.2f-%.2f ") % range.first % range.second).str(); - const wxDataViewItem layer_item = m_objects_model->AddLayersChild(layers_item, label, layer_idx); + const wxDataViewItem layer_item = m_objects_model->AddLayersChild(layers_item, range, layer_idx); const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return; -// auto opt_keys = object(obj_idx)->layer_config_ranges[range].keys(); const DynamicPrintConfig& config = object(obj_idx)->layer_config_ranges[range]; -// if (!opt_keys.empty() && !(opt_keys.size() == 2 && opt_keys[0] == "layer_height" && opt_keys[1] == "extruder")) if (config.keys().size() > 2) select_item(m_objects_model->AddSettingsChild(layer_item)); } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index ad7587a011..455f9f7a15 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -229,8 +229,9 @@ public: void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void del_object(const int obj_idx); void del_subobject_item(wxDataViewItem& item); - void del_settings_from_config(); + void del_settings_from_config(const wxDataViewItem& parent_item); void del_instances_from_object(const int obj_idx); + void del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range); void del_layers_from_object(const int obj_idx); bool del_subobject_from_object(const int obj_idx, const int idx, const int type); void split(); @@ -245,7 +246,6 @@ public: wxBoxSizer* get_sizer() {return m_sizer;} int get_selected_obj_idx() const; DynamicPrintConfig& get_item_config(const wxDataViewItem& item) const; - const t_layer_height_range& get_layer_range_from_item(const wxDataViewItem layer_item, const int obj_idx) const; void changed_object(const int obj_idx = -1) const; void part_selection_changed(); @@ -277,7 +277,7 @@ public: // Remove objects/sub-object from the list void remove(); void del_layer_range(const t_layer_height_range& range); - void add_layer_range(const t_layer_height_range& range); + void add_layer_range_after_current(const t_layer_height_range& current_range); void add_layer_item (const t_layer_height_range& range, const wxDataViewItem layers_item, const int layer_idx = -1); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6cd270e5b3..045feb75a0 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3442,9 +3442,9 @@ void TabSLAMaterial::reload_config() void TabSLAMaterial::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) - return; // #ys_FIXME + return; -// #ys_FIXME +// #ys_FIXME. Just a template for this function // m_update_cnt++; // ! something to update // m_update_cnt--; @@ -3542,9 +3542,8 @@ void TabSLAPrint::reload_config() void TabSLAPrint::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) - return; // #ys_FIXME + return; -// #ys_FIXME m_update_cnt++; double head_penetration = m_config->opt_float("support_head_penetration"); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index adc2e6d076..ed49c85138 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -464,12 +464,13 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, - const wxString& label_range, + const t_layer_height_range& layer_range, const int idx /*= -1 */, const wxString& extruder) : m_parent(parent), m_type(itLayer), m_idx(idx), + m_layer_range(layer_range), m_extruder(extruder) { const int children_cnt = parent->GetChildCount(); @@ -481,7 +482,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent for (int i = m_idx; i < children_cnt; i++) parent->GetNthChild(i)->SetIdx(i + 1); } -// m_name = wxString::Format(_(L("Layer %s (mm)")), label_range); + const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; m_bmp = create_scaled_bitmap(nullptr, "layers_white"); // FIXME: pass window ptr @@ -751,7 +752,7 @@ wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_i } wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item, - const std::string& label_range, + const t_layer_height_range& layer_range, const int index /* = -1*/) { ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); @@ -773,7 +774,7 @@ wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_ } // Add layer node - ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, label_range, index); + ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, layer_range, index); if (index < 0) layer_root_node->Append(layer_node); else @@ -1122,7 +1123,7 @@ wxDataViewItem ObjectDataViewModel::GetItemById(const int obj_idx, const int sub if (!item) return wxDataViewItem(0); - auto parent = (ObjectDataViewModelNode*)item.GetID();; + auto parent = (ObjectDataViewModelNode*)item.GetID(); for (size_t i = 0; i < parent->GetChildCount(); i++) if (parent->GetNthChild(i)->m_idx == sub_obj_idx) return wxDataViewItem(parent->GetNthChild(i)); @@ -1140,6 +1141,34 @@ wxDataViewItem ObjectDataViewModel::GetItemByLayerId(int obj_idx, int layer_idx) return GetItemById(obj_idx, layer_idx, itLayerRoot); } +wxDataViewItem ObjectDataViewModel::GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range) +{ + if (obj_idx >= m_objects.size() || obj_idx < 0) { + printf("Error! Out of objects range.\n"); + return wxDataViewItem(0); + } + + auto item = GetItemByType(wxDataViewItem(m_objects[obj_idx]), itLayerRoot); + if (!item) + return wxDataViewItem(0); + + auto parent = (ObjectDataViewModelNode*)item.GetID(); + for (size_t i = 0; i < parent->GetChildCount(); i++) + if (parent->GetNthChild(i)->m_layer_range == layer_range) + return wxDataViewItem(parent->GetNthChild(i)); + + return wxDataViewItem(0); +} + +int ObjectDataViewModel::GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range) +{ + wxDataViewItem item = GetItemByLayerRange(obj_idx, layer_range); + if (!item) + return -1; + + return GetLayerIdByItem(item); +} + int ObjectDataViewModel::GetIdByItem(const wxDataViewItem& item) const { wxASSERT(item.IsOk()); @@ -1182,6 +1211,16 @@ int ObjectDataViewModel::GetLayerIdByItem(const wxDataViewItem& item) const return GetIdByItemAndType(item, itLayer); } +t_layer_height_range ObjectDataViewModel::GetLayerRangeByItem(const wxDataViewItem& item) const +{ + wxASSERT(item.IsOk()); + + ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + if (!node || node->m_type != itLayer) + return { 0.0f, 0.0f }; + return node->GetLayerRange(); +} + void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx) { wxASSERT(item.IsOk()); @@ -1196,9 +1235,10 @@ void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type ObjectDataViewModelNode *parent_node = node->GetParent(); if (!parent_node) return; - if (type & (itInstance | itLayer)) - parent_node = node->GetParent()->GetParent(); - if (!parent_node || parent_node->m_type != itObject) { type = itUndef; return; } + + // get top parent (Object) node + while (parent_node->m_type != itObject) + parent_node = parent_node->GetParent(); auto it = find(m_objects.begin(), m_objects.end(), parent_node); if (it != m_objects.end()) @@ -1366,10 +1406,7 @@ wxDataViewItem ObjectDataViewModel::GetTopParent(const wxDataViewItem &item) con ObjectDataViewModelNode *parent_node = node->GetParent(); while (parent_node->m_type != itObject) - { - node = parent_node; - parent_node = node->GetParent(); - } + parent_node = parent_node->GetParent(); return wxDataViewItem((void*)parent_node); } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 775d89a3b0..3f43b08828 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -20,6 +20,8 @@ namespace Slic3r { enum class ModelVolumeType : int; }; +typedef std::pair t_layer_height_range; + #ifdef __WXMSW__ void msw_rescale_menu(wxMenu* menu); #else /* __WXMSW__ */ @@ -179,6 +181,7 @@ class ObjectDataViewModelNode wxBitmap m_empty_bmp; size_t m_volumes_cnt = 0; std::vector< std::string > m_opt_categories; + t_layer_height_range m_layer_range = { 0.0f, 0.0f }; wxString m_name; wxBitmap& m_bmp = m_empty_bmp; @@ -232,7 +235,7 @@ public: } ObjectDataViewModelNode(ObjectDataViewModelNode* parent, - const wxString& label_range, + const t_layer_height_range& layer_range, const int idx = -1, const wxString& extruder = wxEmptyString ); @@ -325,6 +328,7 @@ public: ItemType GetType() const { return m_type; } void SetIdx(const int& idx); int GetIdx() const { return m_idx; } + t_layer_height_range GetLayerRange() const { return m_layer_range; } // use this function only for childrens void AssignAllVal(ObjectDataViewModelNode& from_node) @@ -397,7 +401,7 @@ public: wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item, - const std::string& label_range, + const t_layer_height_range& layer_range, const int index = -1); wxDataViewItem Delete(const wxDataViewItem &item); wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); @@ -410,6 +414,8 @@ public: wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx); + wxDataViewItem GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); + int GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); int GetIdByItem(const wxDataViewItem& item) const; int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const; int GetObjectIdByItem(const wxDataViewItem& item) const; @@ -480,6 +486,7 @@ public: wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked = false); void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); + t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; }; // ---------------------------------------------------------------------------- From 5d054d90edf00588187deaaec9419d97e4708bf1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 6 Jun 2019 16:11:52 +0200 Subject: [PATCH 056/627] Update patch for qhull to compile on Windows. --- deps/qhull-mods.patch | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/deps/qhull-mods.patch b/deps/qhull-mods.patch index b1ea7159bf..94aeeca2f5 100644 --- a/deps/qhull-mods.patch +++ b/deps/qhull-mods.patch @@ -1,3 +1,16 @@ +From a31ae4781a4afa60e21c70e5b4ae784bcd447c8a Mon Sep 17 00:00:00 2001 +From: tamasmeszaros +Date: Thu, 6 Jun 2019 15:41:43 +0200 +Subject: [PATCH] prusa-slicer changes + +--- + CMakeLists.txt | 44 +++++++++++++++++++++++++++++++++++--- + Config.cmake.in | 2 ++ + src/libqhull_r/qhull_r-exports.def | 2 ++ + src/libqhull_r/user_r.h | 2 +- + 4 files changed, 46 insertions(+), 4 deletions(-) + create mode 100644 Config.cmake.in + diff --git a/CMakeLists.txt b/CMakeLists.txt index 59dff41..20c2ec5 100644 --- a/CMakeLists.txt @@ -70,6 +83,26 @@ index 0000000..bc92bfe @@ -0,0 +1,2 @@ +include("${CMAKE_CURRENT_LIST_DIR}/QhullTargets.cmake") + +diff --git a/src/libqhull_r/qhull_r-exports.def b/src/libqhull_r/qhull_r-exports.def +index 325d57c..72f6ad0 100644 +--- a/src/libqhull_r/qhull_r-exports.def ++++ b/src/libqhull_r/qhull_r-exports.def +@@ -185,6 +185,7 @@ qh_memsetup + qh_memsize + qh_memstatistics + qh_memtotal ++qh_memcheck + qh_merge_degenredundant + qh_merge_nonconvex + qh_mergecycle +@@ -372,6 +373,7 @@ qh_settruncate + qh_setunique + qh_setvoronoi_all + qh_setzero ++qh_setendpointer + qh_sharpnewfacets + qh_skipfacet + qh_skipfilename diff --git a/src/libqhull_r/user_r.h b/src/libqhull_r/user_r.h index fc105b9..7cca65a 100644 --- a/src/libqhull_r/user_r.h @@ -83,3 +116,6 @@ index fc105b9..7cca65a 100644 #if (REALfloat == 1) #define realT float +-- +2.16.2.windows.1 + From 113c6b2ebaa9ceea86ecc056ad47d4a3bab02864 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 6 Jun 2019 16:24:00 +0200 Subject: [PATCH 057/627] Fix static linking to Qhull --- src/qhull/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qhull/CMakeLists.txt b/src/qhull/CMakeLists.txt index 734bb82950..9ca0bdff23 100644 --- a/src/qhull/CMakeLists.txt +++ b/src/qhull/CMakeLists.txt @@ -17,7 +17,7 @@ add_library(qhull INTERFACE) if(Qhull_FOUND) message(STATUS "Using qhull from system.") -if(SLICER_STATIC) +if(SLIC3R_STATIC) target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhullstatic_r) else() target_link_libraries(qhull INTERFACE Qhull::qhullcpp Qhull::qhull_r) From ee2b6abd613a4e5369cc89c7b3f6592db8548523 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 6 Jun 2019 16:49:49 +0200 Subject: [PATCH 058/627] Exclude qhull from default dependency targets, make it optional. --- deps/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index d07c8d1e85..ca4e63fbc1 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -84,7 +84,7 @@ if (MSVC) dep_wxwidgets dep_gtest dep_nlopt - dep_qhull + # dep_qhull # Experimental dep_zlib # on Windows we still need zlib ) From 6da1c9813908e3d832d4554506172aea1ebe9f9f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 6 Jun 2019 18:51:49 +0200 Subject: [PATCH 059/627] WIP: Fixing serial for Boost 1.70.0 and newer --- src/slic3r/Utils/Serial.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Serial.cpp b/src/slic3r/Utils/Serial.cpp index 601719b50e..cd2a01cbfd 100644 --- a/src/slic3r/Utils/Serial.cpp +++ b/src/slic3r/Utils/Serial.cpp @@ -384,7 +384,13 @@ void Serial::reset_line_num() bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec) { - auto &io_service = get_io_service(); + auto& io_service = +#if BOOST_VERSION >= 107000 + //FIXME this is most certainly wrong! + (boost::asio::io_context&)this->get_executor().context(); + #else + this->get_io_service(); +#endif asio::deadline_timer timer(io_service); char c = 0; bool fail = false; From 446e37b1512e507d3f99526e0572c7aacb1c406c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 7 Jun 2019 11:32:46 +0200 Subject: [PATCH 060/627] Implemented extruder selection for Layers --- src/slic3r/GUI/GUI_ObjectList.cpp | 10 ++++++++-- src/slic3r/GUI/wxExtensions.cpp | 7 +++++-- src/slic3r/GUI/wxExtensions.hpp | 3 ++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 3060a166d5..85195f87dc 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2346,12 +2346,18 @@ void ObjectList::add_layer_item(const t_layer_height_range& range, const wxDataViewItem layers_item, const int layer_idx /* = -1*/) { - const wxDataViewItem layer_item = m_objects_model->AddLayersChild(layers_item, range, layer_idx); - const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return; const DynamicPrintConfig& config = object(obj_idx)->layer_config_ranges[range]; + if (!config.has("extruder")) + return; + + const auto layer_item = m_objects_model->AddLayersChild(layers_item, + range, + config.opt_int("extruder"), + layer_idx); + if (config.keys().size() > 2) select_item(m_objects_model->AddSettingsChild(layer_item)); } diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index ed49c85138..05cc265c3b 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -752,12 +752,15 @@ wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_i } wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item, - const t_layer_height_range& layer_range, + const t_layer_height_range& layer_range, + const int extruder/* = 0*/, const int index /* = -1*/) { ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); if (!parent_node) return wxDataViewItem(0); + wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); + // get LayerRoot node ObjectDataViewModelNode *layer_root_node; wxDataViewItem layer_root_item; @@ -774,7 +777,7 @@ wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_ } // Add layer node - ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, layer_range, index); + ObjectDataViewModelNode *layer_node = new ObjectDataViewModelNode(layer_root_node, layer_range, index, extruder_str); if (index < 0) layer_root_node->Append(layer_node); else diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 3f43b08828..8ade7af07a 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -401,7 +401,8 @@ public: wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item, - const t_layer_height_range& layer_range, + const t_layer_height_range& layer_range, + const int extruder = 0, const int index = -1); wxDataViewItem Delete(const wxDataViewItem &item); wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); From 6e891c08862510f2209d1f92333480b52d0f47a9 Mon Sep 17 00:00:00 2001 From: "Stuart P. Bentley" Date: Sat, 8 Jun 2019 13:52:03 -0700 Subject: [PATCH 061/627] Update usage string to match new executable name --- src/PrusaSlicer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index fef1f6e7f0..0aebec420a 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -576,7 +576,7 @@ void CLI::print_help(bool include_print_options, PrinterTechnology printer_techn #endif /* SLIC3R_GUI */ << std::endl << "https://github.com/prusa3d/PrusaSlicer" << std::endl << std::endl - << "Usage: slic3r [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl + << "Usage: prusa-slicer [ ACTIONS ] [ TRANSFORM ] [ OPTIONS ] [ file.stl ... ]" << std::endl << std::endl << "Actions:" << std::endl; cli_actions_config_def.print_cli_help(boost::nowide::cout, false); From 2fa87044be0796625691702485344059b1832d9f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 Jun 2019 10:48:43 +0200 Subject: [PATCH 062/627] Implemented update_object_list_by_printer_technology() --- src/slic3r/GUI/GUI_ObjectList.cpp | 118 +++++++++++++++++++++++++++--- src/slic3r/GUI/GUI_ObjectList.hpp | 5 ++ src/slic3r/GUI/Plater.cpp | 8 +- 3 files changed, 121 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 85195f87dc..dfdaa71712 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1849,24 +1849,41 @@ void ObjectList::layers_editing() // if it doesn't exist now if (!layers_item.IsOk()) { - // create LayerRoot item - layers_item = m_objects_model->AddLayersRoot(obj_item); - t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; - + + // set some default value if (ranges.empty()) ranges[{ 0.0f, 0.6f }] = get_default_layer_config(obj_idx); - // and create Layer item(s) according to the layer_config_ranges - for (const auto range : ranges) - add_layer_item(range.first, layers_item); + // create layer root item + layers_item = add_layer_root_item(obj_item); } + if (!layers_item.IsOk()) + return; // select LayerRoor item and expand select_item(layers_item); Expand(layers_item); } +wxDataViewItem ObjectList::add_layer_root_item(const wxDataViewItem obj_item) +{ + const int obj_idx = m_objects_model->GetIdByItem(obj_item); + if (obj_idx < 0 || + object(obj_idx)->layer_config_ranges.empty() || + printer_technology() == ptSLA) + return wxDataViewItem(0); + + // create LayerRoot item + wxDataViewItem layers_item = m_objects_model->AddLayersRoot(obj_item); + + // and create Layer item(s) according to the layer_config_ranges + for (const auto range : object(obj_idx)->layer_config_ranges) + add_layer_item(range.first, layers_item); + + return layers_item; +} + DynamicPrintConfig ObjectList::get_default_layer_config(const int obj_idx) { DynamicPrintConfig config; @@ -2031,7 +2048,9 @@ void ObjectList::part_selection_changed() if (update_and_show_settings) wxGetApp().obj_settings()->get_og()->set_name(" " + og_name + " "); - if (update_and_show_layers) + if (printer_technology() == ptSLA) + update_and_show_layers = false; + else if (update_and_show_layers) wxGetApp().obj_layers()->get_og()->set_name(" " + og_name + " "); Sidebar& panel = wxGetApp().sidebar(); @@ -2346,7 +2365,7 @@ void ObjectList::add_layer_item(const t_layer_height_range& range, const wxDataViewItem layers_item, const int layer_idx /* = -1*/) { - const int obj_idx = get_selected_obj_idx(); + const int obj_idx = m_objects_model->GetObjectIdByItem(layers_item); if (obj_idx < 0) return; const DynamicPrintConfig& config = object(obj_idx)->layer_config_ranges[range]; @@ -2908,6 +2927,87 @@ void ObjectList::update_settings_items() m_prevent_canvas_selection_update = false; } +// Update settings item for item had it +void ObjectList::update_settings_item_for_item(wxDataViewItem item, wxDataViewItemArray& selections) +{ + const wxDataViewItem& settings_item = m_objects_model->GetSettingsItem(item); + select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); + + // If settings item was deleted from the list, + // it's need to be deleted from selection array, if it was there + if (settings_item != m_objects_model->GetSettingsItem(item) && + selections.Index(settings_item) != wxNOT_FOUND) { + selections.Remove(settings_item); + + // Select item, if settings_item doesn't exist for item anymore, but was selected + if (selections.Index(item) == wxNOT_FOUND) + selections.Add(item); + } +} + +void ObjectList::update_object_list_by_printer_technology() +{ + m_prevent_canvas_selection_update = true; + wxDataViewItemArray sel; + GetSelections(sel); // stash selection + + wxDataViewItemArray object_items; + m_objects_model->GetChildren(wxDataViewItem(0), object_items); + + for (auto& object_item : object_items) { + // Update Settings Item for object + update_settings_item_for_item(object_item, sel); + + // Update settings for Volumes + wxDataViewItemArray all_object_subitems; + m_objects_model->GetChildren(object_item, all_object_subitems); + for (auto item : all_object_subitems) + if (m_objects_model->GetItemType(item) & itVolume) + // update settings for volume + update_settings_item_for_item(item, sel); + + // Update Layers Items + wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item); + if (!layers_item) + layers_item = add_layer_root_item(object_item); + else if (printer_technology() == ptSLA) { + // If layers root item will be deleted from the list, so + // it's need to be deleted from selection array, if it was there + wxDataViewItemArray del_items; + bool some_layers_was_selected = false; + m_objects_model->GetAllChildren(layers_item, del_items); + for (auto& del_item:del_items) + if (sel.Index(del_item) != wxNOT_FOUND) { + some_layers_was_selected = true; + sel.Remove(del_item); + } + if (sel.Index(layers_item) != wxNOT_FOUND) { + some_layers_was_selected = true; + sel.Remove(layers_item); + } + + // delete all "layers" items + m_objects_model->Delete(layers_item); + + // Select object_item, if layers_item doesn't exist for item anymore, but was some of layer items was/were selected + if (some_layers_was_selected) + sel.Add(object_item); + } + else { + wxDataViewItemArray all_obj_layers; + m_objects_model->GetChildren(layers_item, all_obj_layers); + + for (auto item : all_obj_layers) + // update settings for layer + update_settings_item_for_item(item, sel); + } + } + + // restore selection: + SetSelections(sel); + m_prevent_canvas_selection_update = false; +} + void ObjectList::update_object_menu() { append_menu_items_add_volume(&m_menu_object); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 455f9f7a15..29a8096d4c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -236,6 +236,9 @@ public: bool del_subobject_from_object(const int obj_idx, const int idx, const int type); void split(); void layers_editing(); + + wxDataViewItem add_layer_root_item(const wxDataViewItem obj_item); + DynamicPrintConfig get_default_layer_config(const int obj_idx); bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); bool is_splittable(); @@ -305,6 +308,8 @@ public: void last_volume_is_deleted(const int obj_idx); bool has_multi_part_objects(); void update_settings_items(); + void update_settings_item_for_item(wxDataViewItem item, wxDataViewItemArray& selections); + void update_object_list_by_printer_technology(); void update_object_menu(); void instances_to_separated_object(const int obj_idx, const std::set& inst_idx); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cba15d7a95..8e2764bb35 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2733,8 +2733,14 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) // update plater with new config wxGetApp().plater()->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 + * and for SLA presets they should be deleted + */ if (preset_type == Preset::TYPE_PRINTER) - wxGetApp().obj_list()->update_settings_items(); +// wxGetApp().obj_list()->update_settings_items(); + wxGetApp().obj_list()->update_object_list_by_printer_technology(); } void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) From 025f86ca3f14e35d617ddb6c76dc5d463d5bf11e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 10 Jun 2019 11:04:09 +0200 Subject: [PATCH 063/627] Fix of the previous refactoring. --- src/admesh/connect.cpp | 8 +++++--- src/admesh/shared.cpp | 3 ++- src/admesh/util.cpp | 5 +++++ src/libslic3r/TriangleMesh.cpp | 35 +++++++++++++++------------------- 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index b99f93f3d8..d35c48aba9 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -837,10 +837,12 @@ void stl_add_facet(stl_file *stl, const stl_facet *new_facet) { if (stl->error) return; - ++ stl->stats.facets_added; - ++ stl->stats.number_of_facets; - stl->facet_start.emplace_back(*new_facet); + assert(stl->facet_start.size() == stl->stats.number_of_facets); + assert(stl->neighbors_start.size() == stl->stats.number_of_facets); + stl->facet_start.emplace_back(*new_facet); // note that the normal vector is not set here, just initialized to 0. stl->facet_start[stl->stats.number_of_facets].normal = stl_normal::Zero(); stl->neighbors_start.emplace_back(); + ++ stl->stats.facets_added; + ++ stl->stats.number_of_facets; } diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index 8162c6a8d4..bc264ee969 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -33,6 +33,7 @@ void stl_invalidate_shared_vertices(stl_file *stl) { stl->v_indices.clear(); stl->v_shared.clear(); + stl->stats.shared_vertices = 0; } void stl_generate_shared_vertices(stl_file *stl) @@ -46,7 +47,7 @@ void stl_generate_shared_vertices(stl_file *stl) // 3 indices to vertex per face stl->v_indices.assign(stl->stats.number_of_facets, v_indices_struct()); // Shared vertices (3D coordinates) - stl->v_shared.assign(stl->stats.number_of_facets / 2, stl_vertex()); + stl->v_shared.reserve(stl->stats.number_of_facets / 2); stl->stats.shared_vertices = 0; // A degenerate mesh may contain loops: Traversing a fan will end up in an endless loop diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index 61e0d11e7c..d8640e5755 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -456,11 +456,16 @@ bool stl_validate(stl_file *stl) assert(! stl->error); assert(stl->fp == nullptr); assert(! stl->facet_start.empty()); + assert(stl->facet_start.size() == stl->stats.number_of_facets); + assert(stl->neighbors_start.size() == stl->stats.number_of_facets); + assert(stl->facet_start.size() == stl->neighbors_start.size()); assert(stl->heads.empty()); assert(stl->tail == nullptr); assert(! stl->neighbors_start.empty()); assert((stl->v_indices.empty()) == (stl->v_shared.empty())); assert(stl->stats.number_of_facets > 0); + assert(stl->v_shared.size() == stl->stats.shared_vertices); + assert(stl->v_shared.empty() || stl->v_indices.size() == stl->stats.number_of_facets); #ifdef _DEBUG // Verify validity of neighborship data. diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index a974f5af3e..ad12047d24 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -47,7 +47,6 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& f { stl_initialize(&this->stl); stl_file &stl = this->stl; - stl.error = 0; stl.stats.type = inmemory; // count facets and allocate memory @@ -55,7 +54,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& f stl.stats.original_num_facets = stl.stats.number_of_facets; stl_allocate(&stl); - for (uint32_t i = 0; i < stl.stats.number_of_facets; i++) { + for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) { stl_facet facet; facet.vertex[0] = points[facets[i](0)].cast(); facet.vertex[1] = points[facets[i](1)].cast(); @@ -76,16 +75,8 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& f TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) { stl_close(&this->stl); - this->stl = other.stl; - this->repaired = other.repaired; - this->stl.heads.clear(); - this->stl.tail = nullptr; - this->stl.error = other.stl.error; - this->stl.facet_start = other.stl.facet_start; - this->stl.neighbors_start = other.stl.neighbors_start; - this->stl.v_indices = other.stl.v_indices; - this->stl.v_shared = other.stl.v_shared; - this->stl.stats = other.stl.stats; + this->stl = other.stl; + this->repaired = other.repaired; return *this; } @@ -1711,10 +1702,12 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) if (min_z > z || (min_z == z && max_z > z)) { // facet is above the cut plane and does not belong to it - if (upper != NULL) stl_add_facet(&upper->stl, facet); + if (upper != nullptr) + stl_add_facet(&upper->stl, facet); } else if (max_z < z || (max_z == z && min_z < z)) { // facet is below the cut plane and does not belong to it - if (lower != NULL) stl_add_facet(&lower->stl, facet); + if (lower != nullptr) + stl_add_facet(&lower->stl, facet); } else if (min_z < z && max_z > z) { // Facet is cut by the slicing plane. @@ -1761,22 +1754,24 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) quadrilateral[1].vertex[2] = v0v1; if (v0(2) > z) { - if (upper != NULL) stl_add_facet(&upper->stl, &triangle); - if (lower != NULL) { + if (upper != nullptr) + stl_add_facet(&upper->stl, &triangle); + if (lower != nullptr) { stl_add_facet(&lower->stl, &quadrilateral[0]); stl_add_facet(&lower->stl, &quadrilateral[1]); } } else { - if (upper != NULL) { + if (upper != nullptr) { stl_add_facet(&upper->stl, &quadrilateral[0]); stl_add_facet(&upper->stl, &quadrilateral[1]); } - if (lower != NULL) stl_add_facet(&lower->stl, &triangle); + if (lower != nullptr) + stl_add_facet(&lower->stl, &triangle); } } } - if (upper != NULL) { + if (upper != nullptr) { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part"; ExPolygons section; this->make_expolygons_simple(upper_lines, §ion); @@ -1790,7 +1785,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) } } - if (lower != NULL) { + if (lower != nullptr) { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part"; ExPolygons section; this->make_expolygons_simple(lower_lines, §ion); From c7ba8c4daa5163170a3425874aff43c81071483b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 10 Jun 2019 10:47:23 +0200 Subject: [PATCH 064/627] Fixed conflicts after cherry-picking 932e54383d28acbc96f0af6c38c838f2bb23f21d --- src/slic3r/GUI/GLCanvas3D.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b8ca905d27..dbfde926c9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1579,7 +1579,13 @@ void GLCanvas3D::update_volumes_colors_by_extruder() void GLCanvas3D::render() { - wxCHECK_RET(!m_in_render, "GLCanvas3D::render() called recursively"); + if (m_in_render) + { + // if called recursively, return + m_dirty = true; + return; + } + m_in_render = true; Slic3r::ScopeGuard in_render_guard([this]() { m_in_render = false; }); (void)in_render_guard; From 2b6e5a0a7077b2d759ced0a551ba482f6e0469dd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 Jun 2019 15:22:09 +0200 Subject: [PATCH 065/627] Implemented Copy/Paste for Layers. + improved selection (in respect to the Layers) --- src/slic3r/GUI/GUI_ObjectList.cpp | 144 ++++++++++++++++++++++-------- src/slic3r/GUI/GUI_ObjectList.hpp | 20 +++-- src/slic3r/GUI/wxExtensions.cpp | 3 +- 3 files changed, 120 insertions(+), 47 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index dfdaa71712..f6dd6c5915 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -581,6 +581,56 @@ void ObjectList::selection_changed() part_selection_changed(); } +void ObjectList::fill_layer_config_ranges_cache() +{ + wxDataViewItemArray sel_layers; + GetSelections(sel_layers); + + const int obj_idx = m_objects_model->GetObjectIdByItem(sel_layers[0]); + if (obj_idx < 0 || (int)m_objects->size() <= obj_idx) + return; + + const t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + m_layer_config_ranges_cache.clear(); + + for (const auto layer_item : sel_layers) + if (m_objects_model->GetItemType(layer_item) & itLayer) { + auto range = m_objects_model->GetLayerRangeByItem(layer_item); + auto it = ranges.find(range); + if (it != ranges.end()) + m_layer_config_ranges_cache[it->first] = it->second; + } +} + +void ObjectList::paste_layers_into_list() +{ + const int obj_idx = m_objects_model->GetObjectIdByItem(GetSelection()); + + if (obj_idx < 0 || (int)m_objects->size() <= obj_idx || + m_layer_config_ranges_cache.empty() || printer_technology() == ptSLA) + return; + + const wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx); + wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item); + if (layers_item) + m_objects_model->Delete(layers_item); + + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + + // and create Layer item(s) according to the layer_config_ranges + for (const auto range : m_layer_config_ranges_cache) + ranges.emplace(range); + + layers_item = add_layer_root_item(object_item); + + changed_object(obj_idx); + + select_item(layers_item); +#ifndef __WXOSX__ + selection_changed(); +#endif //no __WXOSX__ +} + void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes) { if ((obj_idx < 0) || ((int)m_objects->size() <= obj_idx)) @@ -737,10 +787,18 @@ void ObjectList::key_event(wxKeyEvent& event) } else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/)) select_item_all_children(); - else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) - wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); - else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL)) - wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); + else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) { + if (m_selection_mode & smLayer) + fill_layer_config_ranges_cache(); + else + wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); + } + else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL)) { + if (!m_layer_config_ranges_cache.empty()) + paste_layers_into_list(); + else + wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); + } else event.Skip(); } @@ -2104,6 +2162,9 @@ void ObjectList::add_object_to_list(size_t obj_idx) Expand(item); } + // Add layers if it has + add_layer_root_item(item); + #ifndef __WXOSX__ selection_changed(); #endif //__WXMSW__ @@ -2568,22 +2629,18 @@ void ObjectList::update_selections_on_canvas() auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool as_single_selection) { const ItemType& type = m_objects_model->GetItemType(item); - if ( type == itLayerRoot || m_objects_model->GetParent(item) == wxDataViewItem(0) ) { - wxDataViewItem obj_item = type == itLayerRoot ? m_objects_model->GetParent(item) : item; - selection.add_object(m_objects_model->GetIdByItem(obj_item), as_single_selection); - return; - } + const int obj_idx = m_objects_model->GetObjectIdByItem(item); if (type == itVolume) { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); const int vol_idx = m_objects_model->GetVolumeIdByItem(item); selection.add_volume(obj_idx, vol_idx, std::max(instance_idx, 0), as_single_selection); } else if (type == itInstance) { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); const int inst_idx = m_objects_model->GetInstanceIdByItem(item); selection.add_instance(obj_idx, inst_idx, as_single_selection); } + else + selection.add_object(obj_idx, as_single_selection); }; // stores current instance idx before to clear the selection @@ -2654,11 +2711,13 @@ void ObjectList::select_item_all_children() } else { const auto item = GetSelection(); - // Some volume(instance) is selected => select all volumes(instances) inside the current object - if (m_objects_model->GetItemType(item) & (itVolume | itInstance)) + const ItemType item_type = m_objects_model->GetItemType(item); + // Some volume/layer/instance is selected => select all volumes/layers/instances inside the current object + if (item_type & (itVolume | itInstance | itLayer)) m_objects_model->GetChildren(m_objects_model->GetParent(item), sels); - m_selection_mode = m_objects_model->GetItemType(item)&itVolume ? smVolume : smInstance; + m_selection_mode = item_type&itVolume ? smVolume : + item_type&itLayer ? smLayer : smInstance; } SetSelections(sels); @@ -2677,8 +2736,9 @@ void ObjectList::update_selection_mode() } const ItemType type = m_objects_model->GetItemType(GetSelection()); - m_selection_mode = type&itSettings ? smUndef : - type&itVolume ? smVolume : smInstance; + m_selection_mode = type & itSettings ? smUndef : + type & itLayer ? smLayer : + type & itVolume ? smVolume : smInstance; } // check last selected item. If is it possible to select it @@ -2689,33 +2749,37 @@ bool ObjectList::check_last_selection(wxString& msg_str) const bool is_shift_pressed = wxGetKeyState(WXK_SHIFT); - /* We can't mix Parts and Objects/Instances. + /* We can't mix Volumes, Layers and Objects/Instances. * So, show information about it */ const ItemType type = m_objects_model->GetItemType(m_last_selected_item); - // check a case of a selection of the Parts from different Objects - bool impossible_multipart_selection = false; - if (type & itVolume && m_selection_mode == smVolume) - { + // check a case of a selection of the same type items from different Objects + auto impossible_multi_selection = [type, this](const ItemType item_type, const SELECTION_MODE selection_mode) { + if (!(type & item_type && m_selection_mode & selection_mode)) + return false; + wxDataViewItemArray sels; GetSelections(sels); - for (const auto& sel: sels) - if (sel != m_last_selected_item && - m_objects_model->GetParent(sel) != m_objects_model->GetParent(m_last_selected_item)) - { - impossible_multipart_selection = true; - break; - } - } + for (const auto& sel : sels) + if (sel != m_last_selected_item && + m_objects_model->GetTopParent(sel) != m_objects_model->GetTopParent(m_last_selected_item)) + return true; - if (impossible_multipart_selection || + return false; + }; + + if (impossible_multi_selection(itVolume, smVolume) || + impossible_multi_selection(itLayer, smLayer ) || type & itSettings || - type & itVolume && m_selection_mode == smInstance || - !(type & itVolume) && m_selection_mode == smVolume) + type & itVolume && !(m_selection_mode & smVolume ) || + type & itLayer && !(m_selection_mode & smLayer ) || + type & itInstance && !(m_selection_mode & smInstance) + ) { // Inform user why selection isn't complited - const wxString item_type = m_selection_mode == smInstance ? _(L("Object or Instance")) : _(L("Part")); + const wxString item_type = m_selection_mode & smInstance ? _(L("Object or Instance")) : + m_selection_mode & smVolume ? _(L("Part")) : _(L("Layer")); msg_str = wxString::Format( _(L("Unsupported selection")) + "\n\n" + _(L("You started your selection with %s Item.")) + "\n" + @@ -2752,7 +2816,7 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray sels; GetSelections(sels); - if (m_selection_mode == smVolume) + if (m_selection_mode & (smVolume|smLayer)) { // identify correct parent of the initial selected item const wxDataViewItem& parent = m_objects_model->GetParent(m_last_selected_item == sels.front() ? sels.back() : sels.front()); @@ -2761,8 +2825,10 @@ void ObjectList::fix_multiselection_conflicts() wxDataViewItemArray children; // selected volumes from current parent m_objects_model->GetChildren(parent, children); + const ItemType item_type = m_selection_mode & smVolume ? itVolume : itLayer; + for (const auto child : children) - if (IsSelected(child) && m_objects_model->GetItemType(child)&itVolume) + if (IsSelected(child) && m_objects_model->GetItemType(child) & item_type) sels.Add(child); // If some part is selected, unselect all items except of selected parts of the current object @@ -2928,7 +2994,7 @@ void ObjectList::update_settings_items() } // Update settings item for item had it -void ObjectList::update_settings_item_for_item(wxDataViewItem item, wxDataViewItemArray& selections) +void ObjectList::update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections) { const wxDataViewItem& settings_item = m_objects_model->GetSettingsItem(item); select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); @@ -2956,7 +3022,7 @@ void ObjectList::update_object_list_by_printer_technology() for (auto& object_item : object_items) { // Update Settings Item for object - update_settings_item_for_item(object_item, sel); + update_settings_item_and_selection(object_item, sel); // Update settings for Volumes wxDataViewItemArray all_object_subitems; @@ -2964,7 +3030,7 @@ void ObjectList::update_object_list_by_printer_technology() for (auto item : all_object_subitems) if (m_objects_model->GetItemType(item) & itVolume) // update settings for volume - update_settings_item_for_item(item, sel); + update_settings_item_and_selection(item, sel); // Update Layers Items wxDataViewItem layers_item = m_objects_model->GetLayerRootItem(object_item); @@ -2999,7 +3065,7 @@ void ObjectList::update_object_list_by_printer_technology() for (auto item : all_obj_layers) // update settings for layer - update_settings_item_for_item(item, sel); + update_settings_item_and_selection(item, sel); } } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 29a8096d4c..7c7046626d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -33,8 +33,9 @@ typedef std::map< std::string, std::vector< std::pair typedef std::vector ModelVolumePtrs; -typedef double coordf_t; -typedef std::pair t_layer_height_range; +typedef double coordf_t; +typedef std::pair t_layer_height_range; +typedef std::map t_layer_config_ranges; namespace GUI { @@ -67,9 +68,10 @@ class ObjectList : public wxDataViewCtrl { enum SELECTION_MODE { - smUndef, - smVolume, - smInstance + smUndef = 0, + smVolume = 1, + smInstance = 2, + smLayer = 4 } m_selection_mode {smUndef}; struct dragged_item_data @@ -130,7 +132,9 @@ class ObjectList : public wxDataViewCtrl DynamicPrintConfig *m_config {nullptr}; std::vector *m_objects{ nullptr }; - std::vector m_bmp_vector; + std::vector m_bmp_vector; + + t_layer_config_ranges m_layer_config_ranges_cache; int m_selected_object_id = -1; bool m_prevent_list_events = false; // We use this flag to avoid circular event handling Select() @@ -308,7 +312,7 @@ public: void last_volume_is_deleted(const int obj_idx); bool has_multi_part_objects(); void update_settings_items(); - void update_settings_item_for_item(wxDataViewItem item, wxDataViewItemArray& selections); + void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); void update_object_list_by_printer_technology(); void update_object_menu(); @@ -319,6 +323,8 @@ public: void fix_through_netfabb(); void update_item_error_icon(const int obj_idx, int vol_idx) const ; + void fill_layer_config_ranges_cache(); + void paste_layers_into_list(); void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); void paste_objects_into_list(const std::vector& object_idxs); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 05cc265c3b..c23caa6c8c 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1174,7 +1174,8 @@ int ObjectDataViewModel::GetItemIdByLayerRange(const int obj_idx, const t_layer int ObjectDataViewModel::GetIdByItem(const wxDataViewItem& item) const { - wxASSERT(item.IsOk()); + if(!item.IsOk()) + return -1; ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); auto it = find(m_objects.begin(), m_objects.end(), node); From 72046598a966e04caf734867b99e8b360fb05aeb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 Jun 2019 15:49:41 +0200 Subject: [PATCH 066/627] Fixed OSX build --- src/slic3r/GUI/wxExtensions.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 8ade7af07a..1ed5770bb6 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -20,7 +20,8 @@ namespace Slic3r { enum class ModelVolumeType : int; }; -typedef std::pair t_layer_height_range; +typedef double coordf_t; +typedef std::pair t_layer_height_range; #ifdef __WXMSW__ void msw_rescale_menu(wxMenu* menu); From 40b27e8332f5590d3998a2ef6c89bce65a48da27 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 10 Jun 2019 16:53:08 +0200 Subject: [PATCH 067/627] admesh refactoring: Move the hashing structure out of stl_file --- src/admesh/connect.cpp | 1025 ++++++++++------------- src/admesh/stl.h | 24 +- src/admesh/stl_io.cpp | 13 - src/admesh/stlinit.cpp | 4 - src/admesh/util.cpp | 2 - src/libslic3r/Fill/FillRectilinear3.cpp | 2 +- 6 files changed, 459 insertions(+), 611 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index d35c48aba9..03b13343de 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -32,37 +32,363 @@ #include "stl.h" +struct HashEdge { + // Key of a hash edge: sorted vertices of the edge. + uint32_t key[6]; + // Compare two keys. + bool operator==(const HashEdge &rhs) const { return memcmp(key, rhs.key, sizeof(key)) == 0; } + bool operator!=(const HashEdge &rhs) const { return ! (*this == rhs); } + int hash(int M) const { return ((key[0] / 11 + key[1] / 7 + key[2] / 3) ^ (key[3] / 11 + key[4] / 7 + key[5] / 3)) % M; } -static void stl_match_neighbors_nearby(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b); -static void stl_record_neighbors(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b); -static void stl_initialize_facet_check_nearby(stl_file *stl); -static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, const stl_vertex *a, const stl_vertex *b); -static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, - stl_vertex *a, stl_vertex *b, float tolerance); -static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, - void (*match_neighbors)(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b)); -static int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b); -static void stl_free_edges(stl_file *stl); -static void stl_change_vertices(stl_file *stl, int facet_num, int vnot, - stl_vertex new_vertex); -static void stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, - stl_hash_edge *edge_b, int *facet1, int *vertex1, - int *facet2, int *vertex2, - stl_vertex *new_vertex1, stl_vertex *new_vertex2); -extern int stl_check_normal_vector(stl_file *stl, - int facet_num, int normal_fix_flag); + // Index of a facet owning this edge. + int facet_number; + // Index of this edge inside the facet with an index of facet_number. + // If this edge is stored backwards, which_edge is increased by 3. + int which_edge; + struct HashEdge *next; -static inline size_t hash_size_from_nr_faces(const size_t nr_faces) + void load_exact(stl_file *stl, const stl_vertex *a, const stl_vertex *b) + { + { + stl_vertex diff = (*a - *b).cwiseAbs(); + float max_diff = std::max(diff(0), std::max(diff(1), diff(2))); + stl->stats.shortest_edge = std::min(max_diff, stl->stats.shortest_edge); + } + + // Ensure identical vertex ordering of equal edges. + // This method is numerically robust. + if (stl_vertex_lower(*a, *b)) { + } else { + // This edge is loaded backwards. + std::swap(a, b); + this->which_edge += 3; + } + memcpy(&this->key[0], a->data(), sizeof(stl_vertex)); + memcpy(&this->key[3], b->data(), sizeof(stl_vertex)); + // Switch negative zeros to positive zeros, so memcmp will consider them to be equal. + for (size_t i = 0; i < 6; ++ i) { + unsigned char *p = (unsigned char*)(this->key + i); + #ifdef BOOST_LITTLE_ENDIAN + if (p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0x80) + // Negative zero, switch to positive zero. + p[3] = 0; + #else /* BOOST_LITTLE_ENDIAN */ + if (p[0] == 0x80 && p[1] == 0 && p[2] == 0 && p[3] == 0) + // Negative zero, switch to positive zero. + p[0] = 0; + #endif /* BOOST_LITTLE_ENDIAN */ + } + } + + bool load_nearby(const stl_file *stl, const stl_vertex &a, const stl_vertex &b, float tolerance) + { + // Index of a grid cell spaced by tolerance. + typedef Eigen::Matrix Vec3i; + Vec3i vertex1 = ((a - stl->stats.min) / tolerance).cast(); + Vec3i vertex2 = ((b - stl->stats.min) / tolerance).cast(); + static_assert(sizeof(Vec3i) == 12, "size of Vec3i incorrect"); + + if (vertex1 == vertex2) + // Both vertices hash to the same value + return false; + + // Ensure identical vertex ordering of edges, which vertices land into equal grid cells. + // This method is numerically robust. + if ((vertex1[0] != vertex2[0]) ? + (vertex1[0] < vertex2[0]) : + ((vertex1[1] != vertex2[1]) ? + (vertex1[1] < vertex2[1]) : + (vertex1[2] < vertex2[2]))) { + memcpy(&this->key[0], vertex1.data(), sizeof(stl_vertex)); + memcpy(&this->key[3], vertex2.data(), sizeof(stl_vertex)); + } else { + memcpy(&this->key[0], vertex2.data(), sizeof(stl_vertex)); + memcpy(&this->key[3], vertex1.data(), sizeof(stl_vertex)); + this->which_edge += 3; /* this edge is loaded backwards */ + } + return true; + } +}; + +struct HashTableEdges { + HashTableEdges(size_t number_of_faces) { + this->M = (int)hash_size_from_nr_faces(number_of_faces); + this->heads.assign(this->M, nullptr); + this->tail = new HashEdge; + this->tail->next = this->tail; + for (int i = 0; i < this->M; ++ i) + this->heads[i] = this->tail; + } + ~HashTableEdges() { + for (int i = 0; i < this->M; ++ i) { + for (HashEdge *temp = this->heads[i]; this->heads[i] != this->tail; temp = this->heads[i]) { + this->heads[i] = this->heads[i]->next; + delete temp; + ++ this->freed; + } + } + this->heads.clear(); + delete this->tail; + this->tail = nullptr; + } + + void insert_edge(stl_file *stl, const HashEdge &edge, void (*match_neighbors)(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b)) + { + int chain_number = edge.hash(this->M); + HashEdge *link = this->heads[chain_number]; + if (link == this->tail) { + // This list doesn't have any edges currently in it. Add this one. + HashEdge *new_edge = new HashEdge(edge); + ++ this->malloced; + new_edge->next = this->tail; + this->heads[chain_number] = new_edge; + } else if (edges_equal(edge, *link)) { + // This is a match. Record result in neighbors list. + match_neighbors(stl, edge, *link); + // Delete the matched edge from the list. + this->heads[chain_number] = link->next; + delete link; + ++ this->freed; + } else { + // Continue through the rest of the list. + for (;;) { + if (link->next == this->tail) { + // This is the last item in the list. Insert a new edge. + HashEdge *new_edge = new HashEdge; + ++ this->malloced; + *new_edge = edge; + new_edge->next = this->tail; + link->next = new_edge; + ++ this->collisions; + break; + } + if (edges_equal(edge, *link->next)) { + // This is a match. Record result in neighbors list. + match_neighbors(stl, edge, *link->next); + // Delete the matched edge from the list. + HashEdge *temp = link->next; + link->next = link->next->next; + delete temp; + ++ this->freed; + break; + } + // This is not a match. Go to the next link. + link = link->next; + ++ this->collisions; + } + } + } + + // Hash table on edges + std::vector heads; + HashEdge* tail; + int M; + + size_t malloced = 0; + size_t freed = 0; + size_t collisions = 0; + +private: + static inline size_t hash_size_from_nr_faces(const size_t nr_faces) + { + // Good primes for addressing a cca. 30 bit space. + // https://planetmath.org/goodhashtableprimes + static std::vector primes{ 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 }; + // Find a prime number for 50% filling of the shared triangle edges in the mesh. + auto it = std::upper_bound(primes.begin(), primes.end(), nr_faces * 3 * 2 - 1); + return (it == primes.end()) ? primes.back() : *it; + } + + // Edges equal for hashing. Edgesof different facet are allowed to be matched. + static inline bool edges_equal(const HashEdge &edge_a, const HashEdge &edge_b) + { + return edge_a.facet_number != edge_b.facet_number && edge_a == edge_b; + } +}; + +static void record_neighbors(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) { - // Good primes for addressing a cca. 30 bit space. - // https://planetmath.org/goodhashtableprimes - static std::vector primes{ 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 }; - // Find a prime number for 50% filling of the shared triangle edges in the mesh. - auto it = std::upper_bound(primes.begin(), primes.end(), nr_faces * 3 * 2 - 1); - return (it == primes.end()) ? primes.back() : *it; + // Facet a's neighbor is facet b + stl->neighbors_start[edge_a.facet_number].neighbor[edge_a.which_edge % 3] = edge_b.facet_number; /* sets the .neighbor part */ + stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] = (edge_b.which_edge + 2) % 3; /* sets the .which_vertex_not part */ + + // Facet b's neighbor is facet a + stl->neighbors_start[edge_b.facet_number].neighbor[edge_b.which_edge % 3] = edge_a.facet_number; /* sets the .neighbor part */ + stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] = (edge_a.which_edge + 2) % 3; /* sets the .which_vertex_not part */ + + if (((edge_a.which_edge < 3) && (edge_b.which_edge < 3)) || ((edge_a.which_edge > 2) && (edge_b.which_edge > 2))) { + // These facets are oriented in opposite directions, their normals are probably messed up. + stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] += 3; + stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] += 3; + } + + // Count successful connects: + // Total connects: + stl->stats.connected_edges += 2; + // Count individual connects: + switch (stl->neighbors_start[edge_a.facet_number].num_neighbors()) { + case 1: ++ stl->stats.connected_facets_1_edge; break; + case 2: ++ stl->stats.connected_facets_2_edge; break; + case 3: ++ stl->stats.connected_facets_3_edge; break; + default: assert(false); + } + switch (stl->neighbors_start[edge_b.facet_number].num_neighbors()) { + case 1: ++ stl->stats.connected_facets_1_edge; break; + case 2: ++ stl->stats.connected_facets_2_edge; break; + case 3: ++ stl->stats.connected_facets_3_edge; break; + default: assert(false); + } +} + +static void match_neighbors_nearby(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) +{ + record_neighbors(stl, edge_a, edge_b); + + // Which vertices to change + int facet1 = -1; + int facet2 = -1; + int vertex1, vertex2; + stl_vertex new_vertex1, new_vertex2; + { + int v1a; // pair 1, facet a + int v1b; // pair 1, facet b + int v2a; // pair 2, facet a + int v2b; // pair 2, facet b + // Find first pair. + if (edge_a.which_edge < 3) { + v1a = edge_a.which_edge; + v2a = (edge_a.which_edge + 1) % 3; + } else { + v2a = edge_a.which_edge % 3; + v1a = (edge_a.which_edge + 1) % 3; + } + if (edge_b.which_edge < 3) { + v1b = edge_b.which_edge; + v2b = (edge_b.which_edge + 1) % 3; + } else { + v2b = edge_b.which_edge % 3; + v1b = (edge_b.which_edge + 1) % 3; + } + + // Of the first pair, which vertex, if any, should be changed + if (stl->facet_start[edge_a.facet_number].vertex[v1a] != stl->facet_start[edge_b.facet_number].vertex[v1b]) { + // These facets are different. + if ( (stl->neighbors_start[edge_a.facet_number].neighbor[v1a] == -1) + && (stl->neighbors_start[edge_a.facet_number].neighbor[(v1a + 2) % 3] == -1)) { + // This vertex has no neighbors. This is a good one to change. + facet1 = edge_a.facet_number; + vertex1 = v1a; + new_vertex1 = stl->facet_start[edge_b.facet_number].vertex[v1b]; + } else { + facet1 = edge_b.facet_number; + vertex1 = v1b; + new_vertex1 = stl->facet_start[edge_a.facet_number].vertex[v1a]; + } + } + + // Of the second pair, which vertex, if any, should be changed. + if (stl->facet_start[edge_a.facet_number].vertex[v2a] == stl->facet_start[edge_b.facet_number].vertex[v2b]) { + // These facets are different. + if ( (stl->neighbors_start[edge_a.facet_number].neighbor[v2a] == -1) + && (stl->neighbors_start[edge_a.facet_number].neighbor[(v2a + 2) % 3] == -1)) { + // This vertex has no neighbors. This is a good one to change. + facet2 = edge_a.facet_number; + vertex2 = v2a; + new_vertex2 = stl->facet_start[edge_b.facet_number].vertex[v2b]; + } else { + facet2 = edge_b.facet_number; + vertex2 = v2b; + new_vertex2 = stl->facet_start[edge_a.facet_number].vertex[v2a]; + } + } + } + + auto change_vertices = [stl](int facet_num, int vnot, stl_vertex new_vertex) + { + int first_facet = facet_num; + bool direction = false; + + for (;;) { + int pivot_vertex; + int next_edge; + if (vnot > 2) { + if (direction) { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot % 3; + } + else { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } + direction = !direction; + } + else { + if (direction) { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } + else { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot; + } + } +#if 0 + if (stl->facet_start[facet_num].vertex[pivot_vertex](0) == new_vertex(0) && + stl->facet_start[facet_num].vertex[pivot_vertex](1) == new_vertex(1) && + stl->facet_start[facet_num].vertex[pivot_vertex](2) == new_vertex(2)) + printf("Changing vertex %f,%f,%f: Same !!!\r\n", new_vertex(0), new_vertex(1), new_vertex(2)); + else { + if (stl->facet_start[facet_num].vertex[pivot_vertex](0) != new_vertex(0)) + printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", + stl->facet_start[facet_num].vertex[pivot_vertex](0), + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](0)), + new_vertex(0), + *reinterpret_cast(&new_vertex(0))); + if (stl->facet_start[facet_num].vertex[pivot_vertex](1) != new_vertex(1)) + printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", + stl->facet_start[facet_num].vertex[pivot_vertex](1), + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](1)), + new_vertex(1), + *reinterpret_cast(&new_vertex(1))); + if (stl->facet_start[facet_num].vertex[pivot_vertex](2) != new_vertex(2)) + printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", + stl->facet_start[facet_num].vertex[pivot_vertex](2), + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](2)), + new_vertex(2), + *reinterpret_cast(&new_vertex(2))); + } +#endif + stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex; + vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; + facet_num = stl->neighbors_start[facet_num].neighbor[next_edge]; + if (facet_num == -1) + break; + + if (facet_num == first_facet) { + // back to the beginning + printf("Back to the first facet changing vertices: probably a mobius part.\nTry using a smaller tolerance or don't do a nearby check\n"); + return; + } + } + }; + + if (facet1 != -1) { + int vnot1 = (facet1 == edge_a.facet_number) ? + (edge_a.which_edge + 2) % 3 : + (edge_b.which_edge + 2) % 3; + if (((vnot1 + 2) % 3) == vertex1) + vnot1 += 3; + change_vertices(facet1, vnot1, new_vertex1); + } + if (facet2 != -1) { + int vnot2 = (facet2 == edge_a.facet_number) ? + (edge_a.which_edge + 2) % 3 : + (edge_b.which_edge + 2) % 3; + if (((vnot2 + 2) % 3) == vertex2) + vnot2 += 3; + change_vertices(facet2, vnot2, new_vertex2); + } + stl->stats.edges_fixed += 2; } // This function builds the neighbors list. No modifications are made @@ -93,30 +419,21 @@ void stl_check_facets_exact(stl_file *stl) } // Initialize hash table. - stl->stats.malloced = 0; - stl->stats.freed = 0; - stl->stats.collisions = 0; - stl->M = (int)hash_size_from_nr_faces(stl->stats.number_of_facets); + HashTableEdges hash_table(stl->stats.number_of_facets); for (auto &neighbor : stl->neighbors_start) neighbor.reset(); - stl->heads.assign(stl->M, nullptr); - stl->tail = new stl_hash_edge; - stl->tail->next = stl->tail; - for (int i = 0; i < stl->M; ++ i) - stl->heads[i] = stl->tail; // Connect neighbor edges. - for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { const stl_facet &facet = stl->facet_start[i]; for (int j = 0; j < 3; ++ j) { - stl_hash_edge edge; + HashEdge edge; edge.facet_number = i; edge.which_edge = j; - stl_load_edge_exact(stl, &edge, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); - insert_hash_edge(stl, edge, stl_record_neighbors); + edge.load_exact(stl, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); + hash_table.insert_edge(stl, edge, record_neighbors); } } - stl_free_edges(stl); #if 0 printf("Number of faces: %d, number of manifold edges: %d, number of connected edges: %d, number of unconnected edges: %d\r\n", @@ -125,444 +442,33 @@ void stl_check_facets_exact(stl_file *stl) #endif } -static void stl_load_edge_exact(stl_file *stl, stl_hash_edge *edge, const stl_vertex *a, const stl_vertex *b) { - - if (stl->error) return; - - { - stl_vertex diff = (*a - *b).cwiseAbs(); - float max_diff = std::max(diff(0), std::max(diff(1), diff(2))); - stl->stats.shortest_edge = std::min(max_diff, stl->stats.shortest_edge); - } - - // Ensure identical vertex ordering of equal edges. - // This method is numerically robust. - if (stl_vertex_lower(*a, *b)) { - } else { - std::swap(a, b); - edge->which_edge += 3; /* this edge is loaded backwards */ - } - memcpy(&edge->key[0], a->data(), sizeof(stl_vertex)); - memcpy(&edge->key[3], b->data(), sizeof(stl_vertex)); - // Switch negative zeros to positive zeros, so memcmp will consider them to be equal. - for (size_t i = 0; i < 6; ++ i) { - unsigned char *p = (unsigned char*)(edge->key + i); -#ifdef BOOST_LITTLE_ENDIAN - if (p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0x80) - // Negative zero, switch to positive zero. - p[3] = 0; -#else /* BOOST_LITTLE_ENDIAN */ - if (p[0] == 0x80 && p[1] == 0 && p[2] == 0 && p[3] == 0) - // Negative zero, switch to positive zero. - p[0] = 0; -#endif /* BOOST_LITTLE_ENDIAN */ - } -} - -static void insert_hash_edge(stl_file *stl, stl_hash_edge edge, - void (*match_neighbors)(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b)) -{ - if (stl->error) return; - - int chain_number = edge.hash(stl->M); - stl_hash_edge *link = stl->heads[chain_number]; - - stl_hash_edge *new_edge; - stl_hash_edge *temp; - if(link == stl->tail) { - /* This list doesn't have any edges currently in it. Add this one. */ - new_edge = new stl_hash_edge; - if(new_edge == NULL) perror("insert_hash_edge"); - stl->stats.malloced++; - *new_edge = edge; - new_edge->next = stl->tail; - stl->heads[chain_number] = new_edge; - return; - } else if(!stl_compare_function(&edge, link)) { - /* This is a match. Record result in neighbors list. */ - match_neighbors(stl, &edge, link); - /* Delete the matched edge from the list. */ - stl->heads[chain_number] = link->next; - delete link; - stl->stats.freed++; - return; - } else { - /* Continue through the rest of the list */ - for(;;) { - if(link->next == stl->tail) { - /* This is the last item in the list. Insert a new edge. */ - new_edge = new stl_hash_edge; - if(new_edge == NULL) perror("insert_hash_edge"); - stl->stats.malloced++; - *new_edge = edge; - new_edge->next = stl->tail; - link->next = new_edge; - stl->stats.collisions++; - return; - } else if(!stl_compare_function(&edge, link->next)) { - /* This is a match. Record result in neighbors list. */ - match_neighbors(stl, &edge, link->next); - - /* Delete the matched edge from the list. */ - temp = link->next; - link->next = link->next->next; - delete temp; - stl->stats.freed++; - return; - } else { - /* This is not a match. Go to the next link */ - link = link->next; - stl->stats.collisions++; - } - } - } -} - -// Return 1 if the edges are not matched. -static inline int stl_compare_function(stl_hash_edge *edge_a, stl_hash_edge *edge_b) -{ - // Don't match edges of the same facet - return (edge_a->facet_number == edge_b->facet_number) || (*edge_a != *edge_b); -} - void stl_check_facets_nearby(stl_file *stl, float tolerance) -{ - if (stl->error) - return; - - if( (stl->stats.connected_facets_1_edge == stl->stats.number_of_facets) - && (stl->stats.connected_facets_2_edge == stl->stats.number_of_facets) - && (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets)) { - /* No need to check any further. All facets are connected */ - return; - } - - stl_initialize_facet_check_nearby(stl); - - for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { - //FIXME is the copy necessary? - stl_facet facet = stl->facet_start[i]; - for (int j = 0; j < 3; j++) { - if(stl->neighbors_start[i].neighbor[j] == -1) { - stl_hash_edge edge; - edge.facet_number = i; - edge.which_edge = j; - if(stl_load_edge_nearby(stl, &edge, &facet.vertex[j], - &facet.vertex[(j + 1) % 3], - tolerance)) { - /* only insert edges that have different keys */ - insert_hash_edge(stl, edge, stl_match_neighbors_nearby); - } - } - } - } - - stl_free_edges(stl); -} - -static int stl_load_edge_nearby(stl_file *stl, stl_hash_edge *edge, stl_vertex *a, stl_vertex *b, float tolerance) -{ - // Index of a grid cell spaced by tolerance. - typedef Eigen::Matrix Vec3i; - Vec3i vertex1 = ((*a - stl->stats.min) / tolerance).cast(); - Vec3i vertex2 = ((*b - stl->stats.min) / tolerance).cast(); - static_assert(sizeof(Vec3i) == 12, "size of Vec3i incorrect"); - - if (vertex1 == vertex2) - // Both vertices hash to the same value - return 0; - - // Ensure identical vertex ordering of edges, which vertices land into equal grid cells. - // This method is numerically robust. - if ((vertex1[0] != vertex2[0]) ? - (vertex1[0] < vertex2[0]) : - ((vertex1[1] != vertex2[1]) ? - (vertex1[1] < vertex2[1]) : - (vertex1[2] < vertex2[2]))) { - memcpy(&edge->key[0], vertex1.data(), sizeof(stl_vertex)); - memcpy(&edge->key[3], vertex2.data(), sizeof(stl_vertex)); - } else { - memcpy(&edge->key[0], vertex2.data(), sizeof(stl_vertex)); - memcpy(&edge->key[3], vertex1.data(), sizeof(stl_vertex)); - edge->which_edge += 3; /* this edge is loaded backwards */ - } - return 1; -} - -static void stl_free_edges(stl_file *stl) -{ - if (stl->error) - return; - - if(stl->stats.malloced != stl->stats.freed) { - for (int i = 0; i < stl->M; i++) { - for (stl_hash_edge *temp = stl->heads[i]; stl->heads[i] != stl->tail; temp = stl->heads[i]) { - stl->heads[i] = stl->heads[i]->next; - delete temp; - ++ stl->stats.freed; - } - } - } - stl->heads.clear(); - delete stl->tail; - stl->tail = nullptr; -} - -static void stl_initialize_facet_check_nearby(stl_file *stl) { if (stl->error) return; - stl->stats.malloced = 0; - stl->stats.freed = 0; - stl->stats.collisions = 0; + if ( (stl->stats.connected_facets_1_edge == stl->stats.number_of_facets) + && (stl->stats.connected_facets_2_edge == stl->stats.number_of_facets) + && (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets)) { + // No need to check any further. All facets are connected. + return; + } - /* tolerance = STL_MAX(stl->stats.shortest_edge, tolerance);*/ - /* tolerance = STL_MAX((stl->stats.bounding_diameter / 500000.0), tolerance);*/ - /* tolerance *= 0.5;*/ - stl->M = (int)hash_size_from_nr_faces(stl->stats.number_of_facets); - - stl->heads.assign(stl->M, nullptr); - stl->tail = new stl_hash_edge; - stl->tail->next = stl->tail; - - for (int i = 0; i < stl->M; ++ i) - stl->heads[i] = stl->tail; -} - -static void -stl_record_neighbors(stl_file *stl, - stl_hash_edge *edge_a, stl_hash_edge *edge_b) { - int i; - int j; - - if (stl->error) return; - - /* Facet a's neighbor is facet b */ - stl->neighbors_start[edge_a->facet_number].neighbor[edge_a->which_edge % 3] = edge_b->facet_number; /* sets the .neighbor part */ - stl->neighbors_start[edge_a->facet_number].which_vertex_not[edge_a->which_edge % 3] = (edge_b->which_edge + 2) % 3; /* sets the .which_vertex_not part */ - - /* Facet b's neighbor is facet a */ - stl->neighbors_start[edge_b->facet_number].neighbor[edge_b->which_edge % 3] = edge_a->facet_number; /* sets the .neighbor part */ - stl->neighbors_start[edge_b->facet_number].which_vertex_not[edge_b->which_edge % 3] = (edge_a->which_edge + 2) % 3; /* sets the .which_vertex_not part */ - - if( ((edge_a->which_edge < 3) && (edge_b->which_edge < 3)) - || ((edge_a->which_edge > 2) && (edge_b->which_edge > 2))) { - /* these facets are oriented in opposite directions. */ - /* their normals are probably messed up. */ - stl->neighbors_start[edge_a->facet_number].which_vertex_not[edge_a->which_edge % 3] += 3; - stl->neighbors_start[edge_b->facet_number].which_vertex_not[edge_b->which_edge % 3] += 3; - } - - - /* Count successful connects */ - /* Total connects */ - stl->stats.connected_edges += 2; - /* Count individual connects */ - i = ((stl->neighbors_start[edge_a->facet_number].neighbor[0] == -1) + - (stl->neighbors_start[edge_a->facet_number].neighbor[1] == -1) + - (stl->neighbors_start[edge_a->facet_number].neighbor[2] == -1)); - j = ((stl->neighbors_start[edge_b->facet_number].neighbor[0] == -1) + - (stl->neighbors_start[edge_b->facet_number].neighbor[1] == -1) + - (stl->neighbors_start[edge_b->facet_number].neighbor[2] == -1)); - if(i == 2) { - stl->stats.connected_facets_1_edge +=1; - } else if(i == 1) { - stl->stats.connected_facets_2_edge +=1; - } else { - stl->stats.connected_facets_3_edge +=1; - } - if(j == 2) { - stl->stats.connected_facets_1_edge +=1; - } else if(j == 1) { - stl->stats.connected_facets_2_edge +=1; - } else { - stl->stats.connected_facets_3_edge +=1; - } -} - -static void stl_match_neighbors_nearby(stl_file *stl, stl_hash_edge *edge_a, stl_hash_edge *edge_b) -{ - int facet1; - int facet2; - int vertex1; - int vertex2; - int vnot1; - int vnot2; - stl_vertex new_vertex1; - stl_vertex new_vertex2; - - if (stl->error) return; - - stl_record_neighbors(stl, edge_a, edge_b); - stl_which_vertices_to_change(stl, edge_a, edge_b, &facet1, &vertex1, - &facet2, &vertex2, &new_vertex1, &new_vertex2); - if(facet1 != -1) { - if(facet1 == edge_a->facet_number) { - vnot1 = (edge_a->which_edge + 2) % 3; - } else { - vnot1 = (edge_b->which_edge + 2) % 3; - } - if(((vnot1 + 2) % 3) == vertex1) { - vnot1 += 3; - } - stl_change_vertices(stl, facet1, vnot1, new_vertex1); - } - if(facet2 != -1) { - if(facet2 == edge_a->facet_number) { - vnot2 = (edge_a->which_edge + 2) % 3; - } else { - vnot2 = (edge_b->which_edge + 2) % 3; - } - if(((vnot2 + 2) % 3) == vertex2) { - vnot2 += 3; - } - stl_change_vertices(stl, facet2, vnot2, new_vertex2); - } - stl->stats.edges_fixed += 2; -} - - -static void stl_change_vertices(stl_file *stl, int facet_num, int vnot, stl_vertex new_vertex) { - int first_facet; - int direction; - int next_edge; - int pivot_vertex; - - if (stl->error) return; - - first_facet = facet_num; - direction = 0; - - for(;;) { - if(vnot > 2) { - if(direction == 0) { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - direction = 1; - } else { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot % 3; - direction = 0; - } - } else { - if(direction == 0) { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot; - } else { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - } - } -#if 0 - if (stl->facet_start[facet_num].vertex[pivot_vertex](0) == new_vertex(0) && - stl->facet_start[facet_num].vertex[pivot_vertex](1) == new_vertex(1) && - stl->facet_start[facet_num].vertex[pivot_vertex](2) == new_vertex(2)) - printf("Changing vertex %f,%f,%f: Same !!!\r\n", - new_vertex(0), new_vertex(1), new_vertex(2)); - else { - if (stl->facet_start[facet_num].vertex[pivot_vertex](0) != new_vertex(0)) - printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", - stl->facet_start[facet_num].vertex[pivot_vertex](0), - *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](0)), - new_vertex(0), - *reinterpret_cast(&new_vertex(0))); - if (stl->facet_start[facet_num].vertex[pivot_vertex](1) != new_vertex(1)) - printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", - stl->facet_start[facet_num].vertex[pivot_vertex](1), - *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](1)), - new_vertex(1), - *reinterpret_cast(&new_vertex(1))); - if (stl->facet_start[facet_num].vertex[pivot_vertex](2) != new_vertex(2)) - printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", - stl->facet_start[facet_num].vertex[pivot_vertex](2), - *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](2)), - new_vertex(2), - *reinterpret_cast(&new_vertex(2))); - } -#endif - stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex; - vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; - facet_num = stl->neighbors_start[facet_num].neighbor[next_edge]; - - if(facet_num == -1) { - break; - } - - if(facet_num == first_facet) { - /* back to the beginning */ - printf("\ -Back to the first facet changing vertices: probably a mobius part.\n\ -Try using a smaller tolerance or don't do a nearby check\n"); - return; - } - } -} - -static void -stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, - stl_hash_edge *edge_b, int *facet1, int *vertex1, - int *facet2, int *vertex2, - stl_vertex *new_vertex1, stl_vertex *new_vertex2) { - int v1a; /* pair 1, facet a */ - int v1b; /* pair 1, facet b */ - int v2a; /* pair 2, facet a */ - int v2b; /* pair 2, facet b */ - - /* Find first pair */ - if(edge_a->which_edge < 3) { - v1a = edge_a->which_edge; - v2a = (edge_a->which_edge + 1) % 3; - } else { - v2a = edge_a->which_edge % 3; - v1a = (edge_a->which_edge + 1) % 3; - } - if(edge_b->which_edge < 3) { - v1b = edge_b->which_edge; - v2b = (edge_b->which_edge + 1) % 3; - } else { - v2b = edge_b->which_edge % 3; - v1b = (edge_b->which_edge + 1) % 3; - } - - // Of the first pair, which vertex, if any, should be changed - if(stl->facet_start[edge_a->facet_number].vertex[v1a] == - stl->facet_start[edge_b->facet_number].vertex[v1b]) { - // These facets are already equal. No need to change. - *facet1 = -1; - } else { - if( (stl->neighbors_start[edge_a->facet_number].neighbor[v1a] == -1) - && (stl->neighbors_start[edge_a->facet_number].neighbor[(v1a + 2) % 3] == -1)) { - /* This vertex has no neighbors. This is a good one to change */ - *facet1 = edge_a->facet_number; - *vertex1 = v1a; - *new_vertex1 = stl->facet_start[edge_b->facet_number].vertex[v1b]; - } else { - *facet1 = edge_b->facet_number; - *vertex1 = v1b; - *new_vertex1 = stl->facet_start[edge_a->facet_number].vertex[v1a]; - } - } - - /* Of the second pair, which vertex, if any, should be changed */ - if(stl->facet_start[edge_a->facet_number].vertex[v2a] == - stl->facet_start[edge_b->facet_number].vertex[v2b]) { - // These facets are already equal. No need to change. - *facet2 = -1; - } else { - if( (stl->neighbors_start[edge_a->facet_number].neighbor[v2a] == -1) - && (stl->neighbors_start[edge_a->facet_number].neighbor[(v2a + 2) % 3] == -1)) { - /* This vertex has no neighbors. This is a good one to change */ - *facet2 = edge_a->facet_number; - *vertex2 = v2a; - *new_vertex2 = stl->facet_start[edge_b->facet_number].vertex[v2b]; - } else { - *facet2 = edge_b->facet_number; - *vertex2 = v2b; - *new_vertex2 = stl->facet_start[edge_a->facet_number].vertex[v2a]; - } - } + HashTableEdges hash_table(stl->stats.number_of_facets); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + //FIXME is the copy necessary? + stl_facet facet = stl->facet_start[i]; + for (int j = 0; j < 3; j++) { + if (stl->neighbors_start[i].neighbor[j] == -1) { + HashEdge edge; + edge.facet_number = i; + edge.which_edge = j; + if (edge.load_nearby(stl, facet.vertex[j], facet.vertex[(j + 1) % 3], tolerance)) + // Only insert edges that have different keys. + hash_table.insert_edge(stl, edge, match_neighbors_nearby); + } + } + } } void stl_remove_unconnected_facets(stl_file *stl) @@ -728,109 +634,88 @@ void stl_remove_unconnected_facets(stl_file *stl) } } -void -stl_fill_holes(stl_file *stl) { - stl_facet facet; - stl_facet new_facet; - int neighbors_initial[3]; - stl_hash_edge edge; - int first_facet; - int direction; - int facet_num; - int vnot; - int next_edge; - int pivot_vertex; - int next_facet; - int j; - int k; +void stl_fill_holes(stl_file *stl) +{ + if (stl->error) + return; - if (stl->error) return; + // Insert all unconnected edges into hash list. + HashTableEdges hash_table(stl->stats.number_of_facets); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + stl_facet facet = stl->facet_start[i]; + for (int j = 0; j < 3; ++ j) { + if(stl->neighbors_start[i].neighbor[j] != -1) + continue; + HashEdge edge; + edge.facet_number = i; + edge.which_edge = j; + edge.load_exact(stl, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); + hash_table.insert_edge(stl, edge, record_neighbors); + } + } - /* Insert all unconnected edges into hash list */ - stl_initialize_facet_check_nearby(stl); - for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { - facet = stl->facet_start[i]; - for(j = 0; j < 3; j++) { - if(stl->neighbors_start[i].neighbor[j] != -1) continue; - edge.facet_number = i; - edge.which_edge = j; - stl_load_edge_exact(stl, &edge, &facet.vertex[j], - &facet.vertex[(j + 1) % 3]); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + stl_facet facet = stl->facet_start[i]; + int neighbors_initial[3] = { stl->neighbors_start[i].neighbor[0], stl->neighbors_start[i].neighbor[1], stl->neighbors_start[i].neighbor[2] }; + int first_facet = i; + for (int j = 0; j < 3; ++ j) { + if (stl->neighbors_start[i].neighbor[j] != -1) + continue; - insert_hash_edge(stl, edge, stl_record_neighbors); - } - } + stl_facet new_facet; + new_facet.vertex[0] = facet.vertex[j]; + new_facet.vertex[1] = facet.vertex[(j + 1) % 3]; + bool direction = neighbors_initial[(j + 2) % 3] == -1; + int facet_num = i; + int vnot = (j + 2) % 3; - for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { - facet = stl->facet_start[i]; - neighbors_initial[0] = stl->neighbors_start[i].neighbor[0]; - neighbors_initial[1] = stl->neighbors_start[i].neighbor[1]; - neighbors_initial[2] = stl->neighbors_start[i].neighbor[2]; - first_facet = i; - for(j = 0; j < 3; j++) { - if(stl->neighbors_start[i].neighbor[j] != -1) continue; + for (;;) { + int pivot_vertex = 0; + int next_edge = 0; + if (vnot > 2) { + if (direction) { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot % 3; + } else { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } + direction = ! direction; + } else { + if(direction == 0) { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot; + } else { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } + } - new_facet.vertex[0] = facet.vertex[j]; - new_facet.vertex[1] = facet.vertex[(j + 1) % 3]; - if(neighbors_initial[(j + 2) % 3] == -1) { - direction = 1; - } else { - direction = 0; - } + int next_facet = stl->neighbors_start[facet_num].neighbor[next_edge]; + if (next_facet == -1) { + new_facet.vertex[2] = stl->facet_start[facet_num].vertex[vnot % 3]; + stl_add_facet(stl, &new_facet); + for (int k = 0; k < 3; ++ k) { + HashEdge edge; + edge.facet_number = stl->stats.number_of_facets - 1; + edge.which_edge = k; + edge.load_exact(stl, &new_facet.vertex[k], &new_facet.vertex[(k + 1) % 3]); + hash_table.insert_edge(stl, edge, record_neighbors); + } + break; + } - facet_num = i; - vnot = (j + 2) % 3; + vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; + facet_num = next_facet; - for(;;) { - if(vnot > 2) { - if(direction == 0) { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - direction = 1; - } else { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot % 3; - direction = 0; - } - } else { - if(direction == 0) { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot; - } else { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - } - } - next_facet = stl->neighbors_start[facet_num].neighbor[next_edge]; - - if(next_facet == -1) { - new_facet.vertex[2] = stl->facet_start[facet_num]. - vertex[vnot % 3]; - stl_add_facet(stl, &new_facet); - for(k = 0; k < 3; k++) { - edge.facet_number = stl->stats.number_of_facets - 1; - edge.which_edge = k; - stl_load_edge_exact(stl, &edge, &new_facet.vertex[k], - &new_facet.vertex[(k + 1) % 3]); - - insert_hash_edge(stl, edge, stl_record_neighbors); - } - break; - } else { - vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; - facet_num = next_facet; - } - - if(facet_num == first_facet) { - /* back to the beginning */ - printf("\ -Back to the first facet filling holes: probably a mobius part.\n\ -Try using a smaller tolerance or don't do a nearby check\n"); - return; - } - } - } - } + if (facet_num == first_facet) { + // back to the beginning + printf("Back to the first facet filling holes: probably a mobius part.\nTry using a smaller tolerance or don't do a nearby check\n"); + return; + } + } + } + } } void stl_add_facet(stl_file *stl, const stl_facet *new_facet) diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 3459511212..bd49ad59f5 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -74,21 +74,6 @@ struct stl_edge { int facet_number; }; -struct stl_hash_edge { - // Key of a hash edge: sorted vertices of the edge. - uint32_t key[6]; - // Compare two keys. - bool operator==(const stl_hash_edge &rhs) { return memcmp(key, rhs.key, sizeof(key)) == 0; } - bool operator!=(const stl_hash_edge &rhs) { return ! (*this == rhs); } - int hash(int M) const { return ((key[0] / 11 + key[1] / 7 + key[2] / 3) ^ (key[3] / 11 + key[4] / 7 + key[5] / 3)) % M; } - // Index of a facet owning this edge. - int facet_number; - // Index of this edge inside the facet with an index of facet_number. - // If this edge is stored backwards, which_edge is increased by 3. - int which_edge; - struct stl_hash_edge *next; -}; - struct stl_neighbors { stl_neighbors() { reset(); } void reset() { @@ -99,8 +84,10 @@ struct stl_neighbors { which_vertex_not[1] = -1; which_vertex_not[2] = -1; } + int num_neighbors_missing() const { return (this->neighbor[0] == -1) + (this->neighbor[1] == -1) + (this->neighbor[2] == -1); } + int num_neighbors() const { return 3 - this->num_neighbors_missing(); } - // Index of a neighbor facet. + // Index of a neighbor facet. int neighbor[3]; // Index of an opposite vertex at the neighbor face. char which_vertex_not[3]; @@ -151,10 +138,6 @@ struct stl_file { FILE *fp; std::vector facet_start; std::vector neighbors_start; - // Hash table on edges - std::vector heads; - stl_hash_edge* tail; - int M; // Indexed face set std::vector v_indices; std::vector v_shared; @@ -177,7 +160,6 @@ extern void stl_check_facets_nearby(stl_file *stl, float tolerance); extern void stl_remove_unconnected_facets(stl_file *stl); extern void stl_write_vertex(stl_file *stl, int facet, int vertex); extern void stl_write_facet(stl_file *stl, char *label, int facet); -extern void stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge); extern void stl_write_neighbor(stl_file *stl, int facet); extern void stl_write_quad_object(stl_file *stl, char *file); extern void stl_verify_neighbors(stl_file *stl); diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp index 81060c0a3c..1e2e1479e0 100644 --- a/src/admesh/stl_io.cpp +++ b/src/admesh/stl_io.cpp @@ -257,19 +257,6 @@ stl_write_facet(stl_file *stl, char *label, int facet) { stl_write_vertex(stl, facet, 2); } -void -stl_write_edge(stl_file *stl, char *label, stl_hash_edge edge) { - if (stl->error) return; - printf("edge (%d)/(%d) %s\n", edge.facet_number, edge.which_edge, label); - if(edge.which_edge < 3) { - stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3); - stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3); - } else { - stl_write_vertex(stl, edge.facet_number, (edge.which_edge + 1) % 3); - stl_write_vertex(stl, edge.facet_number, edge.which_edge % 3); - } -} - void stl_write_neighbor(stl_file *stl, int facet) { if (stl->error) return; diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index 81b7914c3a..b7ba21b779 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -50,8 +50,6 @@ void stl_open(stl_file *stl, const char *file) void stl_initialize(stl_file *stl) { stl->fp = nullptr; - stl->tail = nullptr; - stl->M = 0; stl->error = 0; stl->facet_start.clear(); stl->neighbors_start.clear(); @@ -64,8 +62,6 @@ void stl_initialize(stl_file *stl) void stl_close(stl_file *stl) { assert(stl->fp == nullptr); - assert(stl->heads.empty()); - assert(stl->tail == nullptr); stl_initialize(stl); } diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index d8640e5755..91293d048e 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -459,8 +459,6 @@ bool stl_validate(stl_file *stl) assert(stl->facet_start.size() == stl->stats.number_of_facets); assert(stl->neighbors_start.size() == stl->stats.number_of_facets); assert(stl->facet_start.size() == stl->neighbors_start.size()); - assert(stl->heads.empty()); - assert(stl->tail == nullptr); assert(! stl->neighbors_start.empty()); assert((stl->v_indices.empty()) == (stl->v_shared.empty())); assert(stl->stats.number_of_facets > 0); diff --git a/src/libslic3r/Fill/FillRectilinear3.cpp b/src/libslic3r/Fill/FillRectilinear3.cpp index 8a0b90ead3..dab5842982 100644 --- a/src/libslic3r/Fill/FillRectilinear3.cpp +++ b/src/libslic3r/Fill/FillRectilinear3.cpp @@ -15,7 +15,7 @@ #include "FillRectilinear3.hpp" - #define SLIC3R_DEBUG +// #define SLIC3R_DEBUG // Make assert active if SLIC3R_DEBUG #ifdef SLIC3R_DEBUG From a1c38794fbc12c0d88c59ac85230f058d0779b64 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 10 Jun 2019 17:17:36 +0200 Subject: [PATCH 068/627] Refactored admesh to get rid of the error and fp members of stl_file. --- src/admesh/connect.cpp | 15 --- src/admesh/normals.cpp | 118 +++++++---------- src/admesh/shared.cpp | 33 ++--- src/admesh/stl.h | 74 ++++------- src/admesh/stl_io.cpp | 111 ++++++---------- src/admesh/stlinit.cpp | 228 ++++++++++++--------------------- src/admesh/util.cpp | 39 +----- src/libslic3r/Format/PRUS.cpp | 1 - src/libslic3r/Format/STL.cpp | 3 +- src/libslic3r/TriangleMesh.cpp | 5 +- src/libslic3r/TriangleMesh.hpp | 14 +- xs/xsp/TriangleMesh.xsp | 1 - 12 files changed, 213 insertions(+), 429 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index 03b13343de..9553e7c4d5 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -396,9 +396,6 @@ static void match_neighbors_nearby(stl_file *stl, const HashEdge &edge_a, const // floats of the first edge matches all six floats of the second edge. void stl_check_facets_exact(stl_file *stl) { - if (stl->error) - return; - stl->stats.connected_edges = 0; stl->stats.connected_facets_1_edge = 0; stl->stats.connected_facets_2_edge = 0; @@ -444,9 +441,6 @@ void stl_check_facets_exact(stl_file *stl) void stl_check_facets_nearby(stl_file *stl, float tolerance) { - if (stl->error) - return; - if ( (stl->stats.connected_facets_1_edge == stl->stats.number_of_facets) && (stl->stats.connected_facets_2_edge == stl->stats.number_of_facets) && (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets)) { @@ -476,9 +470,6 @@ void stl_remove_unconnected_facets(stl_file *stl) // A couple of things need to be done here. One is to remove any completely unconnected facets (0 edges connected) since these are // useless and could be completely wrong. The second thing that needs to be done is to remove any degenerate facets that were created during // stl_check_facets_nearby(). - if (stl->error) - return; - auto remove_facet = [stl](int facet_number) { ++ stl->stats.facets_removed; @@ -526,7 +517,6 @@ void stl_remove_unconnected_facets(stl_file *stl) { // Update statistics on face connectivity. auto stl_update_connects_remove_1 = [stl](int facet_num) { - assert(! stl->error); //FIXME when decreasing 3_edge, should I increase 2_edge etc? switch ((stl->neighbors_start[facet_num].neighbor[0] == -1) + (stl->neighbors_start[facet_num].neighbor[1] == -1) + (stl->neighbors_start[facet_num].neighbor[2] == -1)) { case 0: // Facet has 3 neighbors @@ -636,9 +626,6 @@ void stl_remove_unconnected_facets(stl_file *stl) void stl_fill_holes(stl_file *stl) { - if (stl->error) - return; - // Insert all unconnected edges into hash list. HashTableEdges hash_table(stl->stats.number_of_facets); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { @@ -720,8 +707,6 @@ void stl_fill_holes(stl_file *stl) void stl_add_facet(stl_file *stl, const stl_facet *new_facet) { - if (stl->error) - return; assert(stl->facet_start.size() == stl->stats.number_of_facets); assert(stl->neighbors_start.size() == stl->stats.number_of_facets); stl->facet_start.emplace_back(*new_facet); diff --git a/src/admesh/normals.cpp b/src/admesh/normals.cpp index e11f1a3c16..f20d9e68e4 100644 --- a/src/admesh/normals.cpp +++ b/src/admesh/normals.cpp @@ -29,61 +29,43 @@ static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); -static void -stl_reverse_facet(stl_file *stl, int facet_num) { - stl_vertex tmp_vertex; - /* int tmp_neighbor;*/ - int neighbor[3]; - int vnot[3]; +static void reverse_facet(stl_file *stl, int facet_num) +{ + stl->stats.facets_reversed += 1; - stl->stats.facets_reversed += 1; + int neighbor[3] = { stl->neighbors_start[facet_num].neighbor[0], stl->neighbors_start[facet_num].neighbor[1], stl->neighbors_start[facet_num].neighbor[2] }; + int vnot[3] = { stl->neighbors_start[facet_num].which_vertex_not[0], stl->neighbors_start[facet_num].which_vertex_not[1], stl->neighbors_start[facet_num].which_vertex_not[2] }; - neighbor[0] = stl->neighbors_start[facet_num].neighbor[0]; - neighbor[1] = stl->neighbors_start[facet_num].neighbor[1]; - neighbor[2] = stl->neighbors_start[facet_num].neighbor[2]; - vnot[0] = stl->neighbors_start[facet_num].which_vertex_not[0]; - vnot[1] = stl->neighbors_start[facet_num].which_vertex_not[1]; - vnot[2] = stl->neighbors_start[facet_num].which_vertex_not[2]; + // reverse the facet + stl_vertex tmp_vertex = stl->facet_start[facet_num].vertex[0]; + stl->facet_start[facet_num].vertex[0] = + stl->facet_start[facet_num].vertex[1]; + stl->facet_start[facet_num].vertex[1] = tmp_vertex; - /* reverse the facet */ - tmp_vertex = stl->facet_start[facet_num].vertex[0]; - stl->facet_start[facet_num].vertex[0] = - stl->facet_start[facet_num].vertex[1]; - stl->facet_start[facet_num].vertex[1] = tmp_vertex; + // fix the vnots of the neighboring facets + if (neighbor[0] != -1) + stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = (stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] + 3) % 6; + if (neighbor[1] != -1) + stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = (stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6; + if (neighbor[2] != -1) + stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] = (stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6; - /* fix the vnots of the neighboring facets */ - if(neighbor[0] != -1) - stl->neighbors_start[neighbor[0]].which_vertex_not[(vnot[0] + 1) % 3] = - (stl->neighbors_start[neighbor[0]]. - which_vertex_not[(vnot[0] + 1) % 3] + 3) % 6; - if(neighbor[1] != -1) - stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = - (stl->neighbors_start[neighbor[1]]. - which_vertex_not[(vnot[1] + 1) % 3] + 4) % 6; - if(neighbor[2] != -1) - stl->neighbors_start[neighbor[2]].which_vertex_not[(vnot[2] + 1) % 3] = - (stl->neighbors_start[neighbor[2]]. - which_vertex_not[(vnot[2] + 1) % 3] + 2) % 6; + // swap the neighbors of the facet that is being reversed + stl->neighbors_start[facet_num].neighbor[1] = neighbor[2]; + stl->neighbors_start[facet_num].neighbor[2] = neighbor[1]; - /* swap the neighbors of the facet that is being reversed */ - stl->neighbors_start[facet_num].neighbor[1] = neighbor[2]; - stl->neighbors_start[facet_num].neighbor[2] = neighbor[1]; + // swap the vnots of the facet that is being reversed + stl->neighbors_start[facet_num].which_vertex_not[1] = vnot[2]; + stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1]; - /* swap the vnots of the facet that is being reversed */ - stl->neighbors_start[facet_num].which_vertex_not[1] = vnot[2]; - stl->neighbors_start[facet_num].which_vertex_not[2] = vnot[1]; - - /* reverse the values of the vnots of the facet that is being reversed */ - stl->neighbors_start[facet_num].which_vertex_not[0] = - (stl->neighbors_start[facet_num].which_vertex_not[0] + 3) % 6; - stl->neighbors_start[facet_num].which_vertex_not[1] = - (stl->neighbors_start[facet_num].which_vertex_not[1] + 3) % 6; - stl->neighbors_start[facet_num].which_vertex_not[2] = - (stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6; + // reverse the values of the vnots of the facet that is being reversed + stl->neighbors_start[facet_num].which_vertex_not[0] = (stl->neighbors_start[facet_num].which_vertex_not[0] + 3) % 6; + stl->neighbors_start[facet_num].which_vertex_not[1] = (stl->neighbors_start[facet_num].which_vertex_not[1] + 3) % 6; + stl->neighbors_start[facet_num].which_vertex_not[2] = (stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6; } -void -stl_fix_normal_directions(stl_file *stl) { +void stl_fix_normal_directions(stl_file *stl) +{ /* int edge_num;*/ /* int vnot;*/ int checked = 0; @@ -104,8 +86,6 @@ stl_fix_normal_directions(stl_file *stl) { int id; int force_exit = 0; - if (stl->error) return; - // this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209 if (stl->stats.number_of_facets == 0) return; @@ -125,7 +105,7 @@ stl_fix_normal_directions(stl_file *stl) { Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances of it being wrong randomly are low if most of the triangles are right: */ if (stl_check_normal_vector(stl, 0, 0) == 2) { - stl_reverse_facet(stl, 0); + reverse_facet(stl, 0); reversed_ids[reversed_count++] = 0; } @@ -144,12 +124,12 @@ stl_fix_normal_directions(stl_file *stl) { if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] == 1) { /* trying to modify a facet already marked as fixed, revert all changes made until now and exit (fixes: #716, #574, #413, #269, #262, #259, #230, #228, #206) */ for (id = reversed_count - 1; id >= 0; --id) { - stl_reverse_facet(stl, reversed_ids[id]); + reverse_facet(stl, reversed_ids[id]); } force_exit = 1; break; } else { - stl_reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]); + reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]); reversed_ids[reversed_count++] = stl->neighbors_start[facet_num].neighbor[j]; } } @@ -193,7 +173,7 @@ stl_fix_normal_directions(stl_file *stl) { /* This is the first facet of the next part. */ facet_num = i; if(stl_check_normal_vector(stl, i, 0) == 2) { - stl_reverse_facet(stl, i); + reverse_facet(stl, i); reversed_ids[reversed_count++] = i; } @@ -209,7 +189,8 @@ stl_fix_normal_directions(stl_file *stl) { delete tail; } -static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag) { +static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag) +{ /* Returns 0 if the normal is within tolerance */ /* Returns 1 if the normal is not within tolerance, but direction is OK */ /* Returns 2 if the normal is not within tolerance and backwards */ @@ -260,26 +241,19 @@ static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_ return 4; } -void stl_fix_normal_values(stl_file *stl) { - int i; - - if (stl->error) return; - - for(i = 0; i < stl->stats.number_of_facets; i++) { - stl_check_normal_vector(stl, i, 1); - } +void stl_fix_normal_values(stl_file *stl) +{ + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + stl_check_normal_vector(stl, i, 1); } void stl_reverse_all_facets(stl_file *stl) { - if (stl->error) - return; - - stl_normal normal; - for(int i = 0; i < stl->stats.number_of_facets; i++) { - stl_reverse_facet(stl, i); - stl_calculate_normal(normal, &stl->facet_start[i]); - stl_normalize_vector(normal); - stl->facet_start[i].normal = normal; - } + stl_normal normal; + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + reverse_facet(stl, i); + stl_calculate_normal(normal, &stl->facet_start[i]); + stl_normalize_vector(normal); + stl->facet_start[i].normal = normal; + } } diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index bc264ee969..a757d88164 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -38,15 +38,10 @@ void stl_invalidate_shared_vertices(stl_file *stl) void stl_generate_shared_vertices(stl_file *stl) { - if (stl->error) - return; - - /* make sure this function is idempotent and does not leak memory */ - stl_invalidate_shared_vertices(stl); - // 3 indices to vertex per face stl->v_indices.assign(stl->stats.number_of_facets, v_indices_struct()); // Shared vertices (3D coordinates) + stl->v_shared.clear(); stl->v_shared.reserve(stl->stats.number_of_facets / 2); stl->stats.shared_vertices = 0; @@ -139,11 +134,8 @@ void stl_generate_shared_vertices(stl_file *stl) } } -void stl_write_off(stl_file *stl, const char *file) +bool stl_write_off(stl_file *stl, const char *file) { - if (stl->error) - return; - /* Open the file */ FILE *fp = boost::nowide::fopen(file, "w"); if (fp == nullptr) { @@ -151,8 +143,7 @@ void stl_write_off(stl_file *stl, const char *file) sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); - stl->error = 1; - return; + return false; } fprintf(fp, "OFF\n"); @@ -162,13 +153,11 @@ void stl_write_off(stl_file *stl, const char *file) for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) fprintf(fp, "\t3 %d %d %d\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); fclose(fp); + return true; } -void stl_write_vrml(stl_file *stl, const char *file) +bool stl_write_vrml(stl_file *stl, const char *file) { - if (stl->error) - return; - /* Open the file */ FILE *fp = boost::nowide::fopen(file, "w"); if (fp == nullptr) { @@ -176,8 +165,7 @@ void stl_write_vrml(stl_file *stl, const char *file) sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); - stl->error = 1; - return; + return false; } fprintf(fp, "#VRML V1.0 ascii\n\n"); @@ -210,12 +198,11 @@ void stl_write_vrml(stl_file *stl, const char *file) fprintf(fp, "\t}\n"); fprintf(fp, "}\n"); fclose(fp); + return true; } -void stl_write_obj (stl_file *stl, const char *file) +bool stl_write_obj(stl_file *stl, const char *file) { - if (stl->error) - return; FILE *fp = boost::nowide::fopen(file, "w"); if (fp == nullptr) { @@ -223,8 +210,7 @@ void stl_write_obj (stl_file *stl, const char *file) sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); perror(error_msg); free(error_msg); - stl->error = 1; - return; + return false; } for (int i = 0; i < stl->stats.shared_vertices; ++ i) @@ -232,4 +218,5 @@ void stl_write_obj (stl_file *stl, const char *file) for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1); fclose(fp); + return true; } diff --git a/src/admesh/stl.h b/src/admesh/stl.h index bd49ad59f5..51153ede7a 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -127,15 +127,9 @@ struct stl_stats { int normals_fixed; int number_of_parts; int shared_vertices; - - // hash table statistics - int malloced; - int freed; - int collisions; }; struct stl_file { - FILE *fp; std::vector facet_start; std::vector neighbors_start; // Indexed face set @@ -143,17 +137,15 @@ struct stl_file { std::vector v_shared; // Statistics stl_stats stats; - char error; }; -extern void stl_open(stl_file *stl, const char *file); -extern void stl_close(stl_file *stl); +extern bool stl_open(stl_file *stl, const char *file); extern void stl_stats_out(stl_file *stl, FILE *file, char *input_file); -extern void stl_print_neighbors(stl_file *stl, char *file); +extern bool stl_print_neighbors(stl_file *stl, char *file); extern void stl_put_little_int(FILE *fp, int value_in); extern void stl_put_little_float(FILE *fp, float value_in); -extern void stl_write_ascii(stl_file *stl, const char *file, const char *label); -extern void stl_write_binary(stl_file *stl, const char *file, const char *label); +extern bool stl_write_ascii(stl_file *stl, const char *file, const char *label); +extern bool stl_write_binary(stl_file *stl, const char *file, const char *label); extern void stl_write_binary_block(stl_file *stl, FILE *fp); extern void stl_check_facets_exact(stl_file *stl); extern void stl_check_facets_nearby(stl_file *stl, float tolerance); @@ -161,7 +153,7 @@ extern void stl_remove_unconnected_facets(stl_file *stl); extern void stl_write_vertex(stl_file *stl, int facet, int vertex); extern void stl_write_facet(stl_file *stl, char *label, int facet); extern void stl_write_neighbor(stl_file *stl, int facet); -extern void stl_write_quad_object(stl_file *stl, char *file); +extern bool stl_write_quad_object(stl_file *stl, char *file); extern void stl_verify_neighbors(stl_file *stl); extern void stl_fill_holes(stl_file *stl); extern void stl_fix_normal_directions(stl_file *stl); @@ -183,34 +175,28 @@ extern void stl_get_size(stl_file *stl); template extern void stl_transform(stl_file *stl, T *trafo3x4) { - if (stl->error) - return; + for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) { + stl_facet &face = stl->facet_start[i_face]; + for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) { + stl_vertex &v_dst = face.vertex[i_vertex]; + stl_vertex v_src = v_dst; + v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]); + v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]); + v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]); + } + stl_vertex &v_dst = face.normal; + stl_vertex v_src = v_dst; + v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2)); + v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2)); + v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2)); + } - for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) { - stl_facet &face = stl->facet_start[i_face]; - for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) { - stl_vertex &v_dst = face.vertex[i_vertex]; - stl_vertex v_src = v_dst; - v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]); - v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]); - v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]); - } - stl_vertex &v_dst = face.normal; - stl_vertex v_src = v_dst; - v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2)); - v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2)); - v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2)); - } - - stl_get_size(stl); + stl_get_size(stl); } template inline void stl_transform(stl_file *stl, const Eigen::Transform& t) { - if (stl->error) - return; - const Eigen::Matrix r = t.matrix().template block<3, 3>(0, 0); for (size_t i = 0; i < stl->stats.number_of_facets; ++i) { stl_facet &f = stl->facet_start[i]; @@ -225,9 +211,6 @@ inline void stl_transform(stl_file *stl, const Eigen::Transform inline void stl_transform(stl_file *stl, const Eigen::Matrix& m) { - if (stl->error) - return; - for (size_t i = 0; i < stl->stats.number_of_facets; ++i) { stl_facet &f = stl->facet_start[i]; for (size_t j = 0; j < 3; ++j) @@ -238,13 +221,12 @@ inline void stl_transform(stl_file *stl, const Eigen::Matrixvertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]); } @@ -263,17 +245,13 @@ extern void stl_calculate_volume(stl_file *stl); extern void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int tolerance_flag, float tolerance, int increment_flag, float increment, int nearby_flag, int iterations, int remove_unconnected_flag, int fill_holes_flag, int normal_directions_flag, int normal_values_flag, int reverse_all_flag, int verbose_flag); -extern void stl_initialize(stl_file *stl); -extern void stl_count_facets(stl_file *stl, const char *file); +extern void stl_reset(stl_file *stl); extern void stl_allocate(stl_file *stl); extern void stl_read(stl_file *stl, int first_facet, bool first); extern void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first); extern void stl_reallocate(stl_file *stl); extern void stl_add_facet(stl_file *stl, const stl_facet *new_facet); -extern void stl_clear_error(stl_file *stl); -extern int stl_get_error(stl_file *stl); -extern void stl_exit_on_error(stl_file *stl); // Validate the mesh, assert on error. extern bool stl_validate(stl_file *stl); diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp index 1e2e1479e0..8f809d3799 100644 --- a/src/admesh/stl_io.cpp +++ b/src/admesh/stl_io.cpp @@ -33,10 +33,8 @@ #define SEEK_END 2 #endif -void -stl_stats_out(stl_file *stl, FILE *file, char *input_file) { - if (stl->error) return; - +void stl_stats_out(stl_file *stl, FILE *file, char *input_file) +{ /* this is here for Slic3r, without our config.h it won't use this part of the code anyway */ #ifndef VERSION @@ -107,12 +105,10 @@ Backwards edges : %5d\n", stl->stats.backwards_edges); Normals fixed : %5d\n", stl->stats.normals_fixed); } -void -stl_write_ascii(stl_file *stl, const char *file, const char *label) { +bool stl_write_ascii(stl_file *stl, const char *file, const char *label) +{ char *error_msg; - if (stl->error) return; - /* Open the file */ FILE *fp = boost::nowide::fopen(file, "w"); if(fp == NULL) { @@ -122,8 +118,7 @@ stl_write_ascii(stl_file *stl, const char *file, const char *label) { file); perror(error_msg); free(error_msg); - stl->error = 1; - return; + return false; } fprintf(fp, "solid %s\n", label); @@ -149,15 +144,14 @@ stl_write_ascii(stl_file *stl, const char *file, const char *label) { fprintf(fp, "endsolid %s\n", label); fclose(fp); + return true; } -void -stl_print_neighbors(stl_file *stl, char *file) { +bool stl_print_neighbors(stl_file *stl, char *file) +{ FILE *fp; char *error_msg; - if (stl->error) return; - /* Open the file */ fp = boost::nowide::fopen(file, "w"); if(fp == NULL) { @@ -167,8 +161,7 @@ stl_print_neighbors(stl_file *stl, char *file) { file); perror(error_msg); free(error_msg); - stl->error = 1; - return; + return false; } for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { @@ -182,6 +175,7 @@ stl_print_neighbors(stl_file *stl, char *file) { (int)stl->neighbors_start[i].which_vertex_not[2]); } fclose(fp); + return true; } #ifndef BOOST_LITTLE_ENDIAN @@ -195,13 +189,11 @@ void stl_internal_reverse_quads(char *buf, size_t cnt) } #endif -void -stl_write_binary(stl_file *stl, const char *file, const char *label) { +bool stl_write_binary(stl_file *stl, const char *file, const char *label) +{ FILE *fp; char *error_msg; - if (stl->error) return; - /* Open the file */ fp = boost::nowide::fopen(file, "wb"); if(fp == NULL) { @@ -211,8 +203,7 @@ stl_write_binary(stl_file *stl, const char *file, const char *label) { file); perror(error_msg); free(error_msg); - stl->error = 1; - return; + return false; } fprintf(fp, "%s", label); @@ -237,40 +228,38 @@ stl_write_binary(stl_file *stl, const char *file, const char *label) { } #endif /* BOOST_LITTLE_ENDIAN */ fclose(fp); + return true; } -void -stl_write_vertex(stl_file *stl, int facet, int vertex) { - if (stl->error) return; - printf(" vertex %d/%d % .8E % .8E % .8E\n", vertex, facet, +void stl_write_vertex(stl_file *stl, int facet, int vertex) +{ + printf(" vertex %d/%d % .8E % .8E % .8E\n", vertex, facet, stl->facet_start[facet].vertex[vertex](0), stl->facet_start[facet].vertex[vertex](1), stl->facet_start[facet].vertex[vertex](2)); } -void -stl_write_facet(stl_file *stl, char *label, int facet) { - if (stl->error) return; - printf("facet (%d)/ %s\n", facet, label); - stl_write_vertex(stl, facet, 0); - stl_write_vertex(stl, facet, 1); - stl_write_vertex(stl, facet, 2); +void stl_write_facet(stl_file *stl, char *label, int facet) +{ + printf("facet (%d)/ %s\n", facet, label); + stl_write_vertex(stl, facet, 0); + stl_write_vertex(stl, facet, 1); + stl_write_vertex(stl, facet, 2); } -void -stl_write_neighbor(stl_file *stl, int facet) { - if (stl->error) return; - printf("Neighbors %d: %d, %d, %d ; %d, %d, %d\n", facet, - stl->neighbors_start[facet].neighbor[0], - stl->neighbors_start[facet].neighbor[1], - stl->neighbors_start[facet].neighbor[2], - stl->neighbors_start[facet].which_vertex_not[0], - stl->neighbors_start[facet].which_vertex_not[1], - stl->neighbors_start[facet].which_vertex_not[2]); +void stl_write_neighbor(stl_file *stl, int facet) +{ + printf("Neighbors %d: %d, %d, %d ; %d, %d, %d\n", facet, + stl->neighbors_start[facet].neighbor[0], + stl->neighbors_start[facet].neighbor[1], + stl->neighbors_start[facet].neighbor[2], + stl->neighbors_start[facet].which_vertex_not[0], + stl->neighbors_start[facet].which_vertex_not[1], + stl->neighbors_start[facet].which_vertex_not[2]); } -void -stl_write_quad_object(stl_file *stl, char *file) { +bool stl_write_quad_object(stl_file *stl, char *file) +{ FILE *fp; char *error_msg; stl_vertex connect_color = stl_vertex::Zero(); @@ -279,8 +268,6 @@ stl_write_quad_object(stl_file *stl, char *file) { stl_vertex uncon_3_color = stl_vertex::Zero(); stl_vertex color; - if (stl->error) return; - /* Open the file */ fp = boost::nowide::fopen(file, "w"); if(fp == NULL) { @@ -290,8 +277,7 @@ stl_write_quad_object(stl_file *stl, char *file) { file); perror(error_msg); free(error_msg); - stl->error = 1; - return; + return false; } fprintf(fp, "CQUAD\n"); @@ -326,15 +312,14 @@ stl_write_quad_object(stl_file *stl, char *file) { stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); } fclose(fp); + return true; } -void stl_write_dxf(stl_file *stl, const char *file, char *label) +bool stl_write_dxf(stl_file *stl, const char *file, char *label) { FILE *fp; char *error_msg; - if (stl->error) return; - /* Open the file */ fp = boost::nowide::fopen(file, "w"); if(fp == NULL) { @@ -344,8 +329,7 @@ void stl_write_dxf(stl_file *stl, const char *file, char *label) file); perror(error_msg); free(error_msg); - stl->error = 1; - return; + return false; } fprintf(fp, "999\n%s\n", label); @@ -375,22 +359,5 @@ void stl_write_dxf(stl_file *stl, const char *file, char *label) fprintf(fp, "0\nENDSEC\n0\nEOF\n"); fclose(fp); -} - -void -stl_clear_error(stl_file *stl) { - stl->error = 0; -} - -void -stl_exit_on_error(stl_file *stl) { - if (!stl->error) return; - stl->error = 0; - stl_close(stl); - exit(1); -} - -int -stl_get_error(stl_file *stl) { - return stl->error; + return true; } diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index b7ba21b779..088842d514 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -35,42 +35,8 @@ #error "SEEK_SET not defined" #endif -void stl_open(stl_file *stl, const char *file) +static FILE* stl_open_count_facets(stl_file *stl, const char *file) { - stl_initialize(stl); - stl_count_facets(stl, file); - stl_allocate(stl); - stl_read(stl, 0, true); - if (stl->fp != nullptr) { - fclose(stl->fp); - stl->fp = nullptr; - } -} - -void stl_initialize(stl_file *stl) -{ - stl->fp = nullptr; - stl->error = 0; - stl->facet_start.clear(); - stl->neighbors_start.clear(); - stl->v_indices.clear(); - stl->v_shared.clear(); - memset(&stl->stats, 0, sizeof(stl_stats)); - stl->stats.volume = -1.0; -} - -void stl_close(stl_file *stl) -{ - assert(stl->fp == nullptr); - stl_initialize(stl); -} - -#ifndef BOOST_LITTLE_ENDIAN -extern void stl_internal_reverse_quads(char *buf, size_t cnt); -#endif /* BOOST_LITTLE_ENDIAN */ - -void -stl_count_facets(stl_file *stl, const char *file) { long file_size; uint32_t header_num_facets; uint32_t num_facets; @@ -80,30 +46,27 @@ stl_count_facets(stl_file *stl, const char *file) { int num_lines = 1; char *error_msg; - if (stl->error) return; - /* Open the file in binary mode first */ - stl->fp = boost::nowide::fopen(file, "rb"); - if(stl->fp == NULL) { + FILE *fp = boost::nowide::fopen(file, "rb"); + if (fp == nullptr) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", file); perror(error_msg); free(error_msg); - stl->error = 1; - return; + return nullptr; } /* Find size of file */ - fseek(stl->fp, 0, SEEK_END); - file_size = ftell(stl->fp); + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); /* Check for binary or ASCII file */ - fseek(stl->fp, HEADER_SIZE, SEEK_SET); - if (!fread(chtest, sizeof(chtest), 1, stl->fp)) { + fseek(fp, HEADER_SIZE, SEEK_SET); + if (!fread(chtest, sizeof(chtest), 1, fp)) { perror("The input is an empty file"); - stl->error = 1; - return; + fclose(fp); + return nullptr; } stl->stats.type = ascii; for(s = 0; s < sizeof(chtest); s++) { @@ -112,7 +75,7 @@ stl_count_facets(stl_file *stl, const char *file) { break; } } - rewind(stl->fp); + rewind(fp); /* Get the header and the number of facets in the .STL file */ /* If the .STL file is binary, then do the following */ @@ -121,18 +84,18 @@ stl_count_facets(stl_file *stl, const char *file) { if(((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) || (file_size < STL_MIN_FILE_SIZE)) { fprintf(stderr, "The file %s has the wrong size.\n", file); - stl->error = 1; - return; + fclose(fp); + return nullptr; } num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET; /* Read the header */ - if (fread(stl->stats.header, LABEL_SIZE, 1, stl->fp) > 79) { + if (fread(stl->stats.header, LABEL_SIZE, 1, fp) > 79) { stl->stats.header[80] = '\0'; } /* Read the int following the header. This should contain # of facets */ - bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp) != 0; + bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, fp) != 0; #ifndef BOOST_LITTLE_ENDIAN // Convert from little endian to big endian. stl_internal_reverse_quads((char*)&header_num_facets, 4); @@ -147,23 +110,23 @@ stl_count_facets(stl_file *stl, const char *file) { /* Reopen the file in text mode (for getting correct newlines on Windows) */ // fix to silence a warning about unused return value. // obviously if it fails we have problems.... - stl->fp = boost::nowide::freopen(file, "r", stl->fp); + fp = boost::nowide::freopen(file, "r", fp); // do another null check to be safe - if(stl->fp == NULL) { + if(fp == nullptr) { error_msg = (char*) malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", file); perror(error_msg); free(error_msg); - stl->error = 1; - return; + fclose(fp); + return nullptr; } /* Find the number of facets */ char linebuf[100]; - while (fgets(linebuf, 100, stl->fp) != NULL) { + while (fgets(linebuf, 100, fp) != nullptr) { /* don't count short lines */ if (strlen(linebuf) <= 4) continue; @@ -173,11 +136,11 @@ stl_count_facets(stl_file *stl, const char *file) { ++num_lines; } - rewind(stl->fp); + rewind(fp); /* Get the header */ for(i = 0; - (i < 80) && (stl->stats.header[i] = getc(stl->fp)) != '\n'; i++); + (i < 80) && (stl->stats.header[i] = getc(fp)) != '\n'; i++); stl->stats.header[i] = '\0'; /* Lose the '\n' */ stl->stats.header[80] = '\0'; @@ -185,84 +148,20 @@ stl_count_facets(stl_file *stl, const char *file) { } stl->stats.number_of_facets += num_facets; stl->stats.original_num_facets = stl->stats.number_of_facets; + return fp; } -void stl_allocate(stl_file *stl) -{ - if (stl->error) - return; - // Allocate memory for the entire .STL file. - stl->facet_start.assign(stl->stats.number_of_facets, stl_facet()); - // Allocate memory for the neighbors list. - stl->neighbors_start.assign(stl->stats.number_of_facets, stl_neighbors()); -} - -void -stl_open_merge(stl_file *stl, char *file_to_merge) { - int num_facets_so_far; - stl_type origStlType; - FILE *origFp; - stl_file stl_to_merge; - - if (stl->error) return; - - /* Record how many facets we have so far from the first file. We will start putting - facets in the next position. Since we're 0-indexed, it'l be the same position. */ - num_facets_so_far = stl->stats.number_of_facets; - - /* Record the file type we started with: */ - origStlType=stl->stats.type; - /* Record the file pointer too: */ - origFp=stl->fp; - - /* Initialize the sturucture with zero stats, header info and sizes: */ - stl_initialize(&stl_to_merge); - stl_count_facets(&stl_to_merge, file_to_merge); - - /* Copy what we need to into stl so that we can read the file_to_merge directly into it - using stl_read: Save the rest of the valuable info: */ - stl->stats.type=stl_to_merge.stats.type; - stl->fp=stl_to_merge.fp; - - /* Add the number of facets we already have in stl with what we we found in stl_to_merge but - haven't read yet. */ - stl->stats.number_of_facets=num_facets_so_far+stl_to_merge.stats.number_of_facets; - - /* Allocate enough room for stl->stats.number_of_facets facets and neighbors: */ - stl_reallocate(stl); - - /* Read the file to merge directly into stl, adding it to what we have already. - Start at num_facets_so_far, the index to the first unused facet. Also say - that this isn't our first time so we should augment stats like min and max - instead of erasing them. */ - stl_read(stl, num_facets_so_far, false); - - /* Restore the stl information we overwrote (for stl_read) so that it still accurately - reflects the subject part: */ - stl->stats.type=origStlType; - stl->fp=origFp; -} - -void stl_reallocate(stl_file *stl) -{ - if (stl->error) - return; - stl->facet_start.resize(stl->stats.number_of_facets); - stl->neighbors_start.resize(stl->stats.number_of_facets); -} - -/* Reads the contents of the file pointed to by stl->fp into the stl structure, +/* Reads the contents of the file pointed to by fp into the stl structure, starting at facet first_facet. The second argument says if it's our first time running this for the stl and therefore we should reset our max and min stats. */ -void stl_read(stl_file *stl, int first_facet, bool first) { +static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first) +{ stl_facet facet; - if (stl->error) return; - if(stl->stats.type == binary) { - fseek(stl->fp, HEADER_SIZE, SEEK_SET); + fseek(fp, HEADER_SIZE, SEEK_SET); } else { - rewind(stl->fp); + rewind(fp); } char normal_buf[3][32]; @@ -271,10 +170,8 @@ void stl_read(stl_file *stl, int first_facet, bool first) { /* Read a single facet from a binary .STL file */ { /* we assume little-endian architecture! */ - if (fread(&facet, 1, SIZEOF_STL_FACET, stl->fp) != SIZEOF_STL_FACET) { - stl->error = 1; - return; - } + if (fread(&facet, 1, SIZEOF_STL_FACET, fp) != SIZEOF_STL_FACET) + return false; #ifndef BOOST_LITTLE_ENDIAN // Convert the loaded little endian data to big endian. stl_internal_reverse_quads((char*)&facet, 48); @@ -284,27 +181,26 @@ void stl_read(stl_file *stl, int first_facet, bool first) { { // skip solid/endsolid // (in this order, otherwise it won't work when they are paired in the middle of a file) - fscanf(stl->fp, "endsolid%*[^\n]\n"); - fscanf(stl->fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid") + fscanf(fp, "endsolid%*[^\n]\n"); + fscanf(fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid") // Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs. - int res_normal = fscanf(stl->fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); + int res_normal = fscanf(fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); assert(res_normal == 3); - int res_outer_loop = fscanf(stl->fp, " outer loop"); + int res_outer_loop = fscanf(fp, " outer loop"); assert(res_outer_loop == 0); - int res_vertex1 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); + int res_vertex1 = fscanf(fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); assert(res_vertex1 == 3); - int res_vertex2 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); + int res_vertex2 = fscanf(fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); assert(res_vertex2 == 3); - int res_vertex3 = fscanf(stl->fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); + int res_vertex3 = fscanf(fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); assert(res_vertex3 == 3); - int res_endloop = fscanf(stl->fp, " endloop"); + int res_endloop = fscanf(fp, " endloop"); assert(res_endloop == 0); // There is a leading and trailing white space around endfacet to eat up all leading and trailing white spaces including numerous tabs and new lines. - int res_endfacet = fscanf(stl->fp, " endfacet "); + int res_endfacet = fscanf(fp, " endfacet "); if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { perror("Something is syntactically very wrong with this ASCII STL!"); - stl->error = 1; - return; + return false; } // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. @@ -338,13 +234,51 @@ void stl_read(stl_file *stl, int first_facet, bool first) { } stl->stats.size = stl->stats.max - stl->stats.min; stl->stats.bounding_diameter = stl->stats.size.norm(); + return true; +} + +bool stl_open(stl_file *stl, const char *file) +{ + stl_reset(stl); + FILE *fp = stl_open_count_facets(stl, file); + if (fp == nullptr) + return false; + stl_allocate(stl); + bool result = stl_read(stl, fp, 0, true); + fclose(fp); + return result; +} + +void stl_reset(stl_file *stl) +{ + stl->facet_start.clear(); + stl->neighbors_start.clear(); + stl->v_indices.clear(); + stl->v_shared.clear(); + memset(&stl->stats, 0, sizeof(stl_stats)); + stl->stats.volume = -1.0; +} + +#ifndef BOOST_LITTLE_ENDIAN +extern void stl_internal_reverse_quads(char *buf, size_t cnt); +#endif /* BOOST_LITTLE_ENDIAN */ + +void stl_allocate(stl_file *stl) +{ + // Allocate memory for the entire .STL file. + stl->facet_start.assign(stl->stats.number_of_facets, stl_facet()); + // Allocate memory for the neighbors list. + stl->neighbors_start.assign(stl->stats.number_of_facets, stl_neighbors()); +} + +void stl_reallocate(stl_file *stl) +{ + stl->facet_start.resize(stl->stats.number_of_facets); + stl->neighbors_start.resize(stl->stats.number_of_facets); } void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first) { - if (stl->error) - return; - // While we are going through all of the facets, let's find the // maximum and minimum values for x, y, and z diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index 91293d048e..db05cbc09f 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -34,9 +34,6 @@ static float get_volume(stl_file *stl); void stl_verify_neighbors(stl_file *stl) { - if (stl->error) - return; - stl->stats.backwards_edges = 0; for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { @@ -69,9 +66,6 @@ void stl_verify_neighbors(stl_file *stl) void stl_translate(stl_file *stl, float x, float y, float z) { - if (stl->error) - return; - stl_vertex new_min(x, y, z); stl_vertex shift = new_min - stl->stats.min; for (int i = 0; i < stl->stats.number_of_facets; ++ i) @@ -85,9 +79,6 @@ void stl_translate(stl_file *stl, float x, float y, float z) /* Translates the stl by x,y,z, relatively from wherever it is currently */ void stl_translate_relative(stl_file *stl, float x, float y, float z) { - if (stl->error) - return; - stl_vertex shift(x, y, z); for (int i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) @@ -99,9 +90,6 @@ void stl_translate_relative(stl_file *stl, float x, float y, float z) void stl_scale_versor(stl_file *stl, const stl_vertex &versor) { - if (stl->error) - return; - // Scale extents. auto s = versor.array(); stl->stats.min.array() *= s; @@ -120,9 +108,6 @@ void stl_scale_versor(stl_file *stl, const stl_vertex &versor) static void calculate_normals(stl_file *stl) { - if (stl->error) - return; - stl_normal normal; for(uint32_t i = 0; i < stl->stats.number_of_facets; i++) { stl_calculate_normal(normal, &stl->facet_start[i]); @@ -139,8 +124,6 @@ stl_rotate_x(stl_file *stl, float angle) { double c = cos(radian_angle); double s = sin(radian_angle); - if (stl->error) return; - for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl_rotate(&stl->facet_start[i].vertex[j](1), @@ -159,8 +142,6 @@ stl_rotate_y(stl_file *stl, float angle) { double c = cos(radian_angle); double s = sin(radian_angle); - if (stl->error) return; - for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl_rotate(&stl->facet_start[i].vertex[j](2), @@ -179,8 +160,6 @@ stl_rotate_z(stl_file *stl, float angle) { double c = cos(radian_angle); double s = sin(radian_angle); - if (stl->error) return; - for(i = 0; i < stl->stats.number_of_facets; i++) { for(j = 0; j < 3; j++) { stl_rotate(&stl->facet_start[i].vertex[j](0), @@ -203,7 +182,7 @@ stl_rotate(float *x, float *y, const double c, const double s) { void stl_get_size(stl_file *stl) { - if (stl->error || stl->stats.number_of_facets == 0) + if (stl->stats.number_of_facets == 0) return; stl->stats.min = stl->facet_start[0].vertex[0]; stl->stats.max = stl->stats.min; @@ -220,9 +199,6 @@ void stl_get_size(stl_file *stl) void stl_mirror_xy(stl_file *stl) { - if (stl->error) - return; - for(int i = 0; i < stl->stats.number_of_facets; i++) { for(int j = 0; j < 3; j++) { stl->facet_start[i].vertex[j](2) *= -1.0; @@ -239,8 +215,6 @@ void stl_mirror_xy(stl_file *stl) void stl_mirror_yz(stl_file *stl) { - if (stl->error) return; - for (int i = 0; i < stl->stats.number_of_facets; i++) { for (int j = 0; j < 3; j++) { stl->facet_start[i].vertex[j](0) *= -1.0; @@ -257,9 +231,6 @@ void stl_mirror_yz(stl_file *stl) void stl_mirror_xz(stl_file *stl) { - if (stl->error) - return; - for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) stl->facet_start[i].vertex[j](1) *= -1.0; @@ -274,9 +245,6 @@ void stl_mirror_xz(stl_file *stl) static float get_volume(stl_file *stl) { - if (stl->error) - return 0; - // Choose a point, any point as the reference. stl_vertex p0 = stl->facet_start[0].vertex[0]; float volume = 0.f; @@ -291,7 +259,6 @@ static float get_volume(stl_file *stl) void stl_calculate_volume(stl_file *stl) { - if (stl->error) return; stl->stats.volume = get_volume(stl); if(stl->stats.volume < 0.0) { stl_reverse_all_facets(stl); @@ -346,8 +313,6 @@ void stl_repair(stl_file *stl, int i; int last_edges_fixed = 0; - if (stl->error) return; - if(exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag || fill_holes_flag || normal_directions_flag) { if (verbose_flag) @@ -453,8 +418,6 @@ All facets connected. No further nearby check necessary.\n"); // Check validity of the mesh, assert on error. bool stl_validate(stl_file *stl) { - assert(! stl->error); - assert(stl->fp == nullptr); assert(! stl->facet_start.empty()); assert(stl->facet_start.size() == stl->stats.number_of_facets); assert(stl->neighbors_start.size() == stl->stats.number_of_facets); diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index d983b1098b..03ea71a83e 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -161,7 +161,6 @@ static void extract_model_from_archive( else { // Header has been extracted. Now read the faces. stl_file &stl = mesh.stl; - stl.error = 0; stl.stats.type = inmemory; stl.stats.number_of_facets = header.nTriangles; stl.stats.original_num_facets = header.nTriangles; diff --git a/src/libslic3r/Format/STL.cpp b/src/libslic3r/Format/STL.cpp index b00623d1d6..932906fe0e 100644 --- a/src/libslic3r/Format/STL.cpp +++ b/src/libslic3r/Format/STL.cpp @@ -17,8 +17,7 @@ namespace Slic3r { bool load_stl(const char *path, Model *model, const char *object_name_in) { TriangleMesh mesh; - mesh.ReadSTLFile(path); - if (mesh.stl.error) { + if (! mesh.ReadSTLFile(path)) { // die "Failed to open $file\n" if !-e $path; return false; } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index ad12047d24..27163eb575 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -45,7 +45,7 @@ namespace Slic3r { TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets) : repaired(false) { - stl_initialize(&this->stl); + stl_reset(&this->stl); stl_file &stl = this->stl; stl.stats.type = inmemory; @@ -74,7 +74,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& f TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) { - stl_close(&this->stl); + stl_reset(&this->stl); this->stl = other.stl; this->repaired = other.repaired; return *this; @@ -426,7 +426,6 @@ TriangleMeshPtrs TriangleMesh::split() const mesh->stl.stats.type = inmemory; mesh->stl.stats.number_of_facets = (uint32_t)facets.size(); mesh->stl.stats.original_num_facets = mesh->stl.stats.number_of_facets; - stl_clear_error(&mesh->stl); stl_allocate(&mesh->stl); // Assign the facets to the new mesh. diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 4de1f5989e..c16f4a6640 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -21,18 +21,18 @@ typedef std::vector TriangleMeshPtrs; class TriangleMesh { public: - TriangleMesh() : repaired(false) { stl_initialize(&this->stl); } + TriangleMesh() : repaired(false) { stl_reset(&this->stl); } TriangleMesh(const Pointf3s &points, const std::vector &facets); - TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; } - TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); } + TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_reset(&this->stl); *this = other; } + TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_reset(&this->stl); this->swap(other); } ~TriangleMesh() { clear(); } TriangleMesh& operator=(const TriangleMesh &other); TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; } - void clear() { stl_close(&this->stl); this->repaired = false; } + void clear() { stl_reset(&this->stl); this->repaired = false; } void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); } - void ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); } - void write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); } - void write_binary(const char* output_file) { stl_write_binary(&this->stl, output_file, ""); } + bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); } + bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); } + bool write_binary(const char* output_file) { return stl_write_binary(&this->stl, output_file, ""); } void repair(); float volume(); void check_topology(); diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index a188e0b8dc..3cfbaa54de 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -46,7 +46,6 @@ TriangleMesh::ReadFromPerl(vertices, facets) SV* facets CODE: stl_file &stl = THIS->stl; - stl.error = 0; stl.stats.type = inmemory; // count facets and allocate memory From 65238a89b10fb18e7f9b647f5026da9276d32d58 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 10 Jun 2019 17:36:15 +0200 Subject: [PATCH 069/627] admesh refactoring: Removed the shared_vertices counter as it is now contained inside v_shared std::vector --- src/admesh/shared.cpp | 14 +++++--------- src/admesh/stl.h | 2 -- src/admesh/util.cpp | 1 - src/libslic3r/Format/3mf.cpp | 6 +++--- src/libslic3r/Format/AMF.cpp | 4 ++-- src/libslic3r/Model.cpp | 2 +- src/libslic3r/TriangleMesh.cpp | 4 ++-- xs/xsp/TriangleMesh.xsp | 4 ++-- 8 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index a757d88164..e9f0754982 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -33,7 +33,6 @@ void stl_invalidate_shared_vertices(stl_file *stl) { stl->v_indices.clear(); stl->v_shared.clear(); - stl->stats.shared_vertices = 0; } void stl_generate_shared_vertices(stl_file *stl) @@ -43,7 +42,6 @@ void stl_generate_shared_vertices(stl_file *stl) // Shared vertices (3D coordinates) stl->v_shared.clear(); stl->v_shared.reserve(stl->stats.number_of_facets / 2); - stl->stats.shared_vertices = 0; // A degenerate mesh may contain loops: Traversing a fan will end up in an endless loop // while never reaching the starting face. To avoid these endless loops, traversed faces at each fan traversal @@ -91,7 +89,7 @@ void stl_generate_shared_vertices(stl_file *stl) next_edge = pivot_vertex; } } - stl->v_indices[facet_in_fan_idx].vertex[pivot_vertex] = stl->stats.shared_vertices; + stl->v_indices[facet_in_fan_idx].vertex[pivot_vertex] = stl->v_shared.size() - 1; fan_traversal_facet_visited[facet_in_fan_idx] = fan_traversal_stamp; // next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge! @@ -128,8 +126,6 @@ void stl_generate_shared_vertices(stl_file *stl) facet_in_fan_idx = next_facet; } } - - ++ stl->stats.shared_vertices; } } } @@ -147,8 +143,8 @@ bool stl_write_off(stl_file *stl, const char *file) } fprintf(fp, "OFF\n"); - fprintf(fp, "%d %d 0\n", stl->stats.shared_vertices, stl->stats.number_of_facets); - for (int i = 0; i < stl->stats.shared_vertices; ++ i) + fprintf(fp, "%d %d 0\n", stl->v_shared.size(), stl->stats.number_of_facets); + for (int i = 0; i < stl->v_shared.size(); ++ i) fprintf(fp, "\t%f %f %f\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) fprintf(fp, "\t3 %d %d %d\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); @@ -184,7 +180,7 @@ bool stl_write_vrml(stl_file *stl, const char *file) fprintf(fp, "\t\t\tpoint [\n"); int i = 0; - for (; i < (stl->stats.shared_vertices - 1); i++) + for (; i + 1 < stl->v_shared.size(); ++ i) fprintf(fp, "\t\t\t\t%f %f %f,\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); fprintf(fp, "\t\t\t\t%f %f %f]\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); fprintf(fp, "\t\t}\n"); @@ -213,7 +209,7 @@ bool stl_write_obj(stl_file *stl, const char *file) return false; } - for (int i = 0; i < stl->stats.shared_vertices; ++ i) + for (size_t i = 0; i < stl->v_shared.size(); ++ i) fprintf(fp, "v %f %f %f\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1); diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 51153ede7a..4aee6048f1 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -109,7 +109,6 @@ struct stl_stats { float bounding_diameter; float shortest_edge; float volume; - unsigned number_of_blocks; int connected_edges; int connected_facets_1_edge; int connected_facets_2_edge; @@ -126,7 +125,6 @@ struct stl_stats { int backwards_edges; int normals_fixed; int number_of_parts; - int shared_vertices; }; struct stl_file { diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index db05cbc09f..685b641b48 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -425,7 +425,6 @@ bool stl_validate(stl_file *stl) assert(! stl->neighbors_start.empty()); assert((stl->v_indices.empty()) == (stl->v_shared.empty())); assert(stl->stats.number_of_facets > 0); - assert(stl->v_shared.size() == stl->stats.shared_vertices); assert(stl->v_shared.empty() || stl->v_indices.size() == stl->stats.number_of_facets); #ifdef _DEBUG diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index c3916a14e7..d67106656f 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1888,17 +1888,17 @@ namespace Slic3r { if (stl.v_shared.empty()) stl_generate_shared_vertices(&stl); - if (stl.stats.shared_vertices == 0) + if (stl.v_shared.empty()) { add_error("Found invalid mesh"); return false; } - vertices_count += stl.stats.shared_vertices; + vertices_count += stl.v_shared.size(); const Transform3d& matrix = volume->get_matrix(); - for (int i = 0; i < stl.stats.shared_vertices; ++i) + for (size_t i = 0; i < stl.v_shared.size(); ++i) { stream << " <" << VERTEX_TAG << " "; Vec3f v = (matrix * stl.v_shared[i].cast()).cast(); diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index f48b5b58cf..e81ced3adb 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -929,7 +929,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) if (stl.v_shared.empty()) stl_generate_shared_vertices(&stl); const Transform3d& matrix = volume->get_matrix(); - for (size_t i = 0; i < stl.stats.shared_vertices; ++i) { + for (size_t i = 0; i < stl.v_shared.size(); ++i) { stream << " \n"; stream << " \n"; Vec3f v = (matrix * stl.v_shared[i].cast()).cast(); @@ -939,7 +939,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) stream << " \n"; stream << " \n"; } - num_vertices += stl.stats.shared_vertices; + num_vertices += stl.v_shared.size(); } stream << " \n"; for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) { diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 64fbb9a2a8..6da7fc0c3a 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -919,7 +919,7 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const } } else { // Using the shared vertices should be a bit quicker than using the STL faces. - for (int i = 0; i < stl.stats.shared_vertices; ++ i) { + for (size_t i = 0; i < stl.v_shared.size(); ++ i) { Vec3d p = trafo * stl.v_shared[i].cast(); pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 27163eb575..003affc271 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -484,8 +484,8 @@ Polygon TriangleMesh::convex_hull() { this->require_shared_vertices(); Points pp; - pp.reserve(this->stl.stats.shared_vertices); - for (int i = 0; i < this->stl.stats.shared_vertices; ++ i) { + pp.reserve(this->stl.v_shared.size()); + for (size_t i = 0; i < this->stl.v_shared.size(); ++ i) { const stl_vertex &v = this->stl.v_shared[i]; pp.emplace_back(Point::new_scale(v(0), v(1))); } diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index 3cfbaa54de..53b0520278 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -104,8 +104,8 @@ TriangleMesh::vertices() // vertices AV* vertices = newAV(); - av_extend(vertices, THIS->stl.stats.shared_vertices); - for (int i = 0; i < THIS->stl.stats.shared_vertices; i++) { + av_extend(vertices, THIS->stl.v_shared.size()); + for (size_t i = 0; i < THIS->stl.v_shared.size(); i++) { AV* vertex = newAV(); av_store(vertices, i, newRV_noinc((SV*)vertex)); av_extend(vertex, 2); From 6defabea537ba871070fc405a14c2173cb92fc89 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 10 Jun 2019 18:30:54 +0200 Subject: [PATCH 070/627] admesh refactoring: separation of the shared vertices / indices into an indexed_triangle_set structure --- src/admesh/shared.cpp | 111 +++++++++++++++++++++++--------- src/admesh/stl.h | 26 ++++---- src/admesh/stlinit.cpp | 2 - src/admesh/util.cpp | 50 -------------- src/libslic3r/Format/3mf.cpp | 24 +++---- src/libslic3r/Format/AMF.cpp | 17 +++-- src/libslic3r/Model.cpp | 9 +-- src/libslic3r/TriangleMesh.cpp | 46 ++++++------- src/libslic3r/TriangleMesh.hpp | 3 +- src/slic3r/GUI/PresetBundle.cpp | 4 +- xs/xsp/TriangleMesh.xsp | 24 +++---- 11 files changed, 154 insertions(+), 162 deletions(-) diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index e9f0754982..7da2841b05 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -29,19 +29,13 @@ #include "stl.h" -void stl_invalidate_shared_vertices(stl_file *stl) -{ - stl->v_indices.clear(); - stl->v_shared.clear(); -} - -void stl_generate_shared_vertices(stl_file *stl) +void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its) { // 3 indices to vertex per face - stl->v_indices.assign(stl->stats.number_of_facets, v_indices_struct()); + its.indices.assign(stl->stats.number_of_facets, v_indices_struct()); // Shared vertices (3D coordinates) - stl->v_shared.clear(); - stl->v_shared.reserve(stl->stats.number_of_facets / 2); + its.vertices.clear(); + its.vertices.reserve(stl->stats.number_of_facets / 2); // A degenerate mesh may contain loops: Traversing a fan will end up in an endless loop // while never reaching the starting face. To avoid these endless loops, traversed faces at each fan traversal @@ -51,11 +45,11 @@ void stl_generate_shared_vertices(stl_file *stl) for (uint32_t facet_idx = 0; facet_idx < stl->stats.number_of_facets; ++ facet_idx) { for (int j = 0; j < 3; ++ j) { - if (stl->v_indices[facet_idx].vertex[j] != -1) + if (its.indices[facet_idx].vertex[j] != -1) // Shared vertex was already assigned. continue; // Create a new shared vertex. - stl->v_shared.emplace_back(stl->facet_start[facet_idx].vertex[j]); + its.vertices.emplace_back(stl->facet_start[facet_idx].vertex[j]); // Traverse the fan around the j-th vertex of the i-th face, assign the newly created shared vertex index to all the neighboring triangles in the triangle fan. int facet_in_fan_idx = facet_idx; bool edge_direction = false; @@ -89,7 +83,7 @@ void stl_generate_shared_vertices(stl_file *stl) next_edge = pivot_vertex; } } - stl->v_indices[facet_in_fan_idx].vertex[pivot_vertex] = stl->v_shared.size() - 1; + its.indices[facet_in_fan_idx].vertex[pivot_vertex] = its.vertices.size() - 1; fan_traversal_facet_visited[facet_in_fan_idx] = fan_traversal_stamp; // next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge! @@ -130,7 +124,7 @@ void stl_generate_shared_vertices(stl_file *stl) } } -bool stl_write_off(stl_file *stl, const char *file) +bool its_write_off(const indexed_triangle_set &its, const char *file) { /* Open the file */ FILE *fp = boost::nowide::fopen(file, "w"); @@ -143,16 +137,16 @@ bool stl_write_off(stl_file *stl, const char *file) } fprintf(fp, "OFF\n"); - fprintf(fp, "%d %d 0\n", stl->v_shared.size(), stl->stats.number_of_facets); - for (int i = 0; i < stl->v_shared.size(); ++ i) - fprintf(fp, "\t%f %f %f\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); - for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) - fprintf(fp, "\t3 %d %d %d\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); + fprintf(fp, "%d %d 0\n", (int)its.vertices.size(), (int)its.indices.size()); + for (int i = 0; i < its.vertices.size(); ++ i) + fprintf(fp, "\t%f %f %f\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); + for (uint32_t i = 0; i < its.indices.size(); ++ i) + fprintf(fp, "\t3 %d %d %d\n", its.indices[i].vertex[0], its.indices[i].vertex[1], its.indices[i].vertex[2]); fclose(fp); return true; } -bool stl_write_vrml(stl_file *stl, const char *file) +bool its_write_vrml(const indexed_triangle_set &its, const char *file) { /* Open the file */ FILE *fp = boost::nowide::fopen(file, "w"); @@ -180,16 +174,16 @@ bool stl_write_vrml(stl_file *stl, const char *file) fprintf(fp, "\t\t\tpoint [\n"); int i = 0; - for (; i + 1 < stl->v_shared.size(); ++ i) - fprintf(fp, "\t\t\t\t%f %f %f,\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); - fprintf(fp, "\t\t\t\t%f %f %f]\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); + for (; i + 1 < its.vertices.size(); ++ i) + fprintf(fp, "\t\t\t\t%f %f %f,\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); + fprintf(fp, "\t\t\t\t%f %f %f]\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); fprintf(fp, "\t\t}\n"); fprintf(fp, "\t\tDEF STLTriangles IndexedFaceSet {\n"); fprintf(fp, "\t\t\tcoordIndex [\n"); - for (int i = 0; i + 1 < (int)stl->stats.number_of_facets; ++ i) - fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); - fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", stl->v_indices[i].vertex[0], stl->v_indices[i].vertex[1], stl->v_indices[i].vertex[2]); + for (size_t i = 0; i + 1 < its.indices.size(); ++ i) + fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", its.indices[i].vertex[0], its.indices[i].vertex[1], its.indices[i].vertex[2]); + fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", its.indices[i].vertex[0], its.indices[i].vertex[1], its.indices[i].vertex[2]); fprintf(fp, "\t\t}\n"); fprintf(fp, "\t}\n"); fprintf(fp, "}\n"); @@ -197,7 +191,7 @@ bool stl_write_vrml(stl_file *stl, const char *file) return true; } -bool stl_write_obj(stl_file *stl, const char *file) +bool its_write_obj(const indexed_triangle_set &its, const char *file) { FILE *fp = boost::nowide::fopen(file, "w"); @@ -209,10 +203,65 @@ bool stl_write_obj(stl_file *stl, const char *file) return false; } - for (size_t i = 0; i < stl->v_shared.size(); ++ i) - fprintf(fp, "v %f %f %f\n", stl->v_shared[i](0), stl->v_shared[i](1), stl->v_shared[i](2)); - for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) - fprintf(fp, "f %d %d %d\n", stl->v_indices[i].vertex[0]+1, stl->v_indices[i].vertex[1]+1, stl->v_indices[i].vertex[2]+1); + for (size_t i = 0; i < its.vertices.size(); ++ i) + fprintf(fp, "v %f %f %f\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); + for (size_t i = 0; i < its.indices.size(); ++ i) + fprintf(fp, "f %d %d %d\n", its.indices[i].vertex[0]+1, its.indices[i].vertex[1]+1, its.indices[i].vertex[2]+1); fclose(fp); return true; } + + +// Check validity of the mesh, assert on error. +bool stl_validate(const stl_file *stl, const indexed_triangle_set &its) +{ + assert(! stl->facet_start.empty()); + assert(stl->facet_start.size() == stl->stats.number_of_facets); + assert(stl->neighbors_start.size() == stl->stats.number_of_facets); + assert(stl->facet_start.size() == stl->neighbors_start.size()); + assert(! stl->neighbors_start.empty()); + assert((its.indices.empty()) == (its.vertices.empty())); + assert(stl->stats.number_of_facets > 0); + assert(its.vertices.empty() || its.indices.size() == stl->stats.number_of_facets); + +#ifdef _DEBUG + // Verify validity of neighborship data. + for (int facet_idx = 0; facet_idx < (int)stl->stats.number_of_facets; ++ facet_idx) { + const stl_neighbors &nbr = stl->neighbors_start[facet_idx]; + const int *vertices = (its.indices.empty()) ? nullptr : its.indices[facet_idx].vertex; + for (int nbr_idx = 0; nbr_idx < 3; ++ nbr_idx) { + int nbr_face = stl->neighbors_start[facet_idx].neighbor[nbr_idx]; + assert(nbr_face < (int)stl->stats.number_of_facets); + if (nbr_face != -1) { + int nbr_vnot = nbr.which_vertex_not[nbr_idx]; + assert(nbr_vnot >= 0 && nbr_vnot < 6); + // Neighbor of the neighbor is the original face. + assert(stl->neighbors_start[nbr_face].neighbor[(nbr_vnot + 1) % 3] == facet_idx); + int vnot_back = stl->neighbors_start[nbr_face].which_vertex_not[(nbr_vnot + 1) % 3]; + assert(vnot_back >= 0 && vnot_back < 6); + assert((nbr_vnot < 3) == (vnot_back < 3)); + assert(vnot_back % 3 == (nbr_idx + 2) % 3); + if (vertices != nullptr) { + // Has shared vertices. + if (nbr_vnot < 3) { + // Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are correctly oriented. + assert((its.indices[nbr_face].vertex[(nbr_vnot + 1) % 3] == vertices[(nbr_idx + 1) % 3] && its.indices[nbr_face].vertex[(nbr_vnot + 2) % 3] == vertices[nbr_idx])); + } else { + // Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are incorrectly oriented, one of them is flipped. + assert((its.indices[nbr_face].vertex[(nbr_vnot + 2) % 3] == vertices[(nbr_idx + 1) % 3] && its.indices[nbr_face].vertex[(nbr_vnot + 1) % 3] == vertices[nbr_idx])); + } + } + } + } + } +#endif /* _DEBUG */ + + return true; +} + +// Check validity of the mesh, assert on error. +bool stl_validate(const stl_file *stl) +{ + indexed_triangle_set its; + return stl_validate(stl, its); +} diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 4aee6048f1..bb5d252960 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -130,21 +130,22 @@ struct stl_stats { struct stl_file { std::vector facet_start; std::vector neighbors_start; - // Indexed face set - std::vector v_indices; - std::vector v_shared; // Statistics stl_stats stats; }; +struct indexed_triangle_set +{ + void clear() { indices.clear(); vertices.clear(); } + std::vector indices; + std::vector vertices; +}; + extern bool stl_open(stl_file *stl, const char *file); extern void stl_stats_out(stl_file *stl, FILE *file, char *input_file); extern bool stl_print_neighbors(stl_file *stl, char *file); -extern void stl_put_little_int(FILE *fp, int value_in); -extern void stl_put_little_float(FILE *fp, float value_in); extern bool stl_write_ascii(stl_file *stl, const char *file, const char *label); extern bool stl_write_binary(stl_file *stl, const char *file, const char *label); -extern void stl_write_binary_block(stl_file *stl, FILE *fp); extern void stl_check_facets_exact(stl_file *stl); extern void stl_check_facets_nearby(stl_file *stl, float tolerance); extern void stl_remove_unconnected_facets(stl_file *stl); @@ -219,12 +220,12 @@ inline void stl_transform(stl_file *stl, const Eigen::Matrixvertex[1] - facet->vertex[0]).cross(facet->vertex[2] - facet->vertex[0]); } @@ -251,6 +252,7 @@ extern void stl_reallocate(stl_file *stl); extern void stl_add_facet(stl_file *stl, const stl_facet *new_facet); // Validate the mesh, assert on error. -extern bool stl_validate(stl_file *stl); +extern bool stl_validate(const stl_file *stl); +extern bool stl_validate(const stl_file *stl, const indexed_triangle_set &its); #endif diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index 088842d514..44477511f9 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -253,8 +253,6 @@ void stl_reset(stl_file *stl) { stl->facet_start.clear(); stl->neighbors_start.clear(); - stl->v_indices.clear(); - stl->v_shared.clear(); memset(&stl->stats, 0, sizeof(stl_stats)); stl->stats.volume = -1.0; } diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index 685b641b48..f4e4dbf0a7 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -73,7 +73,6 @@ void stl_translate(stl_file *stl, float x, float y, float z) stl->facet_start[i].vertex[j] += shift; stl->stats.min = new_min; stl->stats.max += shift; - stl_invalidate_shared_vertices(stl); } /* Translates the stl by x,y,z, relatively from wherever it is currently */ @@ -85,7 +84,6 @@ void stl_translate_relative(stl_file *stl, float x, float y, float z) stl->facet_start[i].vertex[j] += shift; stl->stats.min += shift; stl->stats.max += shift; - stl_invalidate_shared_vertices(stl); } void stl_scale_versor(stl_file *stl, const stl_vertex &versor) @@ -103,7 +101,6 @@ void stl_scale_versor(stl_file *stl, const stl_vertex &versor) for (int i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) stl->facet_start[i].vertex[j].array() *= s; - stl_invalidate_shared_vertices(stl); } static void calculate_normals(stl_file *stl) @@ -414,50 +411,3 @@ All facets connected. No further nearby check necessary.\n"); stl_verify_neighbors(stl); } } - -// Check validity of the mesh, assert on error. -bool stl_validate(stl_file *stl) -{ - assert(! stl->facet_start.empty()); - assert(stl->facet_start.size() == stl->stats.number_of_facets); - assert(stl->neighbors_start.size() == stl->stats.number_of_facets); - assert(stl->facet_start.size() == stl->neighbors_start.size()); - assert(! stl->neighbors_start.empty()); - assert((stl->v_indices.empty()) == (stl->v_shared.empty())); - assert(stl->stats.number_of_facets > 0); - assert(stl->v_shared.empty() || stl->v_indices.size() == stl->stats.number_of_facets); - -#ifdef _DEBUG - // Verify validity of neighborship data. - for (int facet_idx = 0; facet_idx < (int)stl->stats.number_of_facets; ++ facet_idx) { - const stl_neighbors &nbr = stl->neighbors_start[facet_idx]; - const int *vertices = (stl->v_indices.empty()) ? nullptr : stl->v_indices[facet_idx].vertex; - for (int nbr_idx = 0; nbr_idx < 3; ++ nbr_idx) { - int nbr_face = stl->neighbors_start[facet_idx].neighbor[nbr_idx]; - assert(nbr_face < (int)stl->stats.number_of_facets); - if (nbr_face != -1) { - int nbr_vnot = nbr.which_vertex_not[nbr_idx]; - assert(nbr_vnot >= 0 && nbr_vnot < 6); - // Neighbor of the neighbor is the original face. - assert(stl->neighbors_start[nbr_face].neighbor[(nbr_vnot + 1) % 3] == facet_idx); - int vnot_back = stl->neighbors_start[nbr_face].which_vertex_not[(nbr_vnot + 1) % 3]; - assert(vnot_back >= 0 && vnot_back < 6); - assert((nbr_vnot < 3) == (vnot_back < 3)); - assert(vnot_back % 3 == (nbr_idx + 2) % 3); - if (vertices != nullptr) { - // Has shared vertices. - if (nbr_vnot < 3) { - // Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are correctly oriented. - assert((stl->v_indices[nbr_face].vertex[(nbr_vnot + 1) % 3] == vertices[(nbr_idx + 1) % 3] && stl->v_indices[nbr_face].vertex[(nbr_vnot + 2) % 3] == vertices[nbr_idx])); - } else { - // Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are incorrectly oriented, one of them is flipped. - assert((stl->v_indices[nbr_face].vertex[(nbr_vnot + 2) % 3] == vertices[(nbr_idx + 1) % 3] && stl->v_indices[nbr_face].vertex[(nbr_vnot + 1) % 3] == vertices[nbr_idx])); - } - } - } - } - } -#endif /* _DEBUG */ - - return true; -} diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index d67106656f..8298fe2220 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1881,27 +1881,23 @@ namespace Slic3r { volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first; - if (!volume->mesh.repaired) - volume->mesh.repair(); + volume->mesh.require_shared_vertices(); - stl_file& stl = volume->mesh.stl; - if (stl.v_shared.empty()) - stl_generate_shared_vertices(&stl); - - if (stl.v_shared.empty()) + const indexed_triangle_set &its = volume->mesh.its; + if (its.vertices.empty()) { add_error("Found invalid mesh"); return false; } - vertices_count += stl.v_shared.size(); + vertices_count += its.vertices.size(); const Transform3d& matrix = volume->get_matrix(); - for (size_t i = 0; i < stl.v_shared.size(); ++i) + for (size_t i = 0; i < its.vertices.size(); ++i) { stream << " <" << VERTEX_TAG << " "; - Vec3f v = (matrix * stl.v_shared[i].cast()).cast(); + Vec3f v = (matrix * its.vertices[i].cast()).cast(); stream << "x=\"" << v(0) << "\" "; stream << "y=\"" << v(1) << "\" "; stream << "z=\"" << v(2) << "\" />\n"; @@ -1920,19 +1916,19 @@ namespace Slic3r { VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume); assert(volume_it != volumes_offsets.end()); - stl_file& stl = volume->mesh.stl; + const indexed_triangle_set &its = volume->mesh.its; // updates triangle offsets volume_it->second.first_triangle_id = triangles_count; - triangles_count += stl.stats.number_of_facets; + triangles_count += its.indices.size(); volume_it->second.last_triangle_id = triangles_count - 1; - for (uint32_t i = 0; i < stl.stats.number_of_facets; ++i) + for (size_t i = 0; i < its.indices.size(); ++ i) { stream << " <" << TRIANGLE_TAG << " "; for (int j = 0; j < 3; ++j) { - stream << "v" << j + 1 << "=\"" << stl.v_indices[i].vertex[j] + volume_it->second.first_vertex_id << "\" "; + stream << "v" << j + 1 << "=\"" << its.indices[i].vertex[j] + volume_it->second.first_vertex_id << "\" "; } stream << "/>\n"; } diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index e81ced3adb..dcd9138641 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -923,23 +923,22 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) int num_vertices = 0; for (ModelVolume *volume : object->volumes) { vertices_offsets.push_back(num_vertices); - if (! volume->mesh.repaired) + if (! volume->mesh.repaired) throw std::runtime_error("store_amf() requires repair()"); - auto &stl = volume->mesh.stl; - if (stl.v_shared.empty()) - stl_generate_shared_vertices(&stl); + volume->mesh.require_shared_vertices(); + const indexed_triangle_set &its = volume->mesh.its; const Transform3d& matrix = volume->get_matrix(); - for (size_t i = 0; i < stl.v_shared.size(); ++i) { + for (size_t i = 0; i < its.vertices.size(); ++i) { stream << " \n"; stream << " \n"; - Vec3f v = (matrix * stl.v_shared[i].cast()).cast(); + Vec3f v = (matrix * its.vertices[i].cast()).cast(); stream << " " << v(0) << "\n"; stream << " " << v(1) << "\n"; stream << " " << v(2) << "\n"; stream << " \n"; stream << " \n"; } - num_vertices += stl.v_shared.size(); + num_vertices += its.vertices.size(); } stream << " \n"; for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) { @@ -956,10 +955,10 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) if (volume->is_modifier()) stream << " 1\n"; stream << " " << ModelVolume::type_to_string(volume->type()) << "\n"; - for (int i = 0; i < (int)volume->mesh.stl.stats.number_of_facets; ++i) { + for (size_t i = 0; i < (int)volume->mesh.its.indices.size(); ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) - stream << " " << volume->mesh.stl.v_indices[i].vertex[j] + vertices_offset << "\n"; + stream << " " << volume->mesh.its.indices[i].vertex[j] + vertices_offset << "\n"; stream << " \n"; } stream << " \n"; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 6da7fc0c3a..770581c034 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -908,10 +908,11 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const Points pts; for (const ModelVolume *v : this->volumes) if (v->is_model_part()) { - const stl_file &stl = v->mesh.stl; Transform3d trafo = trafo_instance * v->get_matrix(); - if (stl.v_shared.empty()) { + const indexed_triangle_set &its = v->mesh.its; + if (its.vertices.empty()) { // Using the STL faces. + const stl_file& stl = v->mesh.stl; for (const stl_facet &facet : stl.facet_start) for (size_t j = 0; j < 3; ++ j) { Vec3d p = trafo * facet.vertex[j].cast(); @@ -919,8 +920,8 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const } } else { // Using the shared vertices should be a bit quicker than using the STL faces. - for (size_t i = 0; i < stl.v_shared.size(); ++ i) { - Vec3d p = trafo * stl.v_shared[i].cast(); + for (size_t i = 0; i < its.vertices.size(); ++ i) { + Vec3d p = trafo * its.vertices[i].cast(); pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); } } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 003affc271..ae35c8a5bc 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -238,20 +238,20 @@ bool TriangleMesh::needed_repair() const void TriangleMesh::WriteOBJFile(const char* output_file) { - stl_generate_shared_vertices(&stl); - stl_write_obj(&stl, output_file); + stl_generate_shared_vertices(&stl, its); + its_write_obj(its, output_file); } void TriangleMesh::scale(float factor) { stl_scale(&(this->stl), factor); - stl_invalidate_shared_vertices(&this->stl); + this->its.clear(); } void TriangleMesh::scale(const Vec3d &versor) { stl_scale_versor(&this->stl, versor.cast()); - stl_invalidate_shared_vertices(&this->stl); + this->its.clear(); } void TriangleMesh::translate(float x, float y, float z) @@ -259,7 +259,7 @@ void TriangleMesh::translate(float x, float y, float z) if (x == 0.f && y == 0.f && z == 0.f) return; stl_translate_relative(&(this->stl), x, y, z); - stl_invalidate_shared_vertices(&this->stl); + this->its.clear(); } void TriangleMesh::translate(const Vec3f &displacement) @@ -282,7 +282,7 @@ void TriangleMesh::rotate(float angle, const Axis &axis) } else if (axis == Z) { stl_rotate_z(&(this->stl), angle); } - stl_invalidate_shared_vertices(&this->stl); + this->its.clear(); } void TriangleMesh::rotate(float angle, const Vec3d& axis) @@ -305,13 +305,13 @@ void TriangleMesh::mirror(const Axis &axis) } else if (axis == Z) { stl_mirror_xy(&this->stl); } - stl_invalidate_shared_vertices(&this->stl); + this->its.clear(); } void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) { stl_transform(&stl, t); - stl_invalidate_shared_vertices(&stl); + this->its.clear(); if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) { // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. this->repair(); @@ -322,7 +322,7 @@ void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) { stl_transform(&stl, m); - stl_invalidate_shared_vertices(&stl); + this->its.clear(); if (fix_left_handed && m.determinant() < 0.) { // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. this->repair(); @@ -443,7 +443,7 @@ void TriangleMesh::merge(const TriangleMesh &mesh) { // reset stats and metadata int number_of_facets = this->stl.stats.number_of_facets; - stl_invalidate_shared_vertices(&this->stl); + this->its.clear(); this->repaired = false; // update facet count and allocate more memory @@ -484,9 +484,9 @@ Polygon TriangleMesh::convex_hull() { this->require_shared_vertices(); Points pp; - pp.reserve(this->stl.v_shared.size()); - for (size_t i = 0; i < this->stl.v_shared.size(); ++ i) { - const stl_vertex &v = this->stl.v_shared[i]; + pp.reserve(this->its.vertices.size()); + for (size_t i = 0; i < this->its.vertices.size(); ++ i) { + const stl_vertex &v = this->its.vertices[i]; pp.emplace_back(Point::new_scale(v(0), v(1))); } return Slic3r::Geometry::convex_hull(pp); @@ -504,14 +504,14 @@ BoundingBoxf3 TriangleMesh::bounding_box() const BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) const { BoundingBoxf3 bbox; - if (stl.v_shared.empty()) { + if (this->its.vertices.empty()) { // Using the STL faces. for (const stl_facet &facet : this->stl.facet_start) for (size_t j = 0; j < 3; ++ j) bbox.merge(trafo * facet.vertex[j].cast()); } else { // Using the shared vertices should be a bit quicker than using the STL faces. - for (const stl_vertex &v : this->stl.v_shared) + for (const stl_vertex &v : this->its.vertices) bbox.merge(trafo * v.cast()); } return bbox; @@ -576,11 +576,11 @@ void TriangleMesh::require_shared_vertices() assert(stl_validate(&this->stl)); if (! this->repaired) this->repair(); - if (this->stl.v_shared.empty()) { + if (this->its.vertices.empty()) { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices"; - stl_generate_shared_vertices(&(this->stl)); + stl_generate_shared_vertices(&this->stl, this->its); } - assert(stl_validate(&this->stl)); + assert(stl_validate(&this->stl, this->its)); BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; } @@ -592,9 +592,9 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac throw_on_cancel(); facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1); - v_scaled_shared.assign(_mesh->stl.v_shared.size(), stl_vertex()); + v_scaled_shared.assign(_mesh->its.vertices.size(), stl_vertex()); for (size_t i = 0; i < v_scaled_shared.size(); ++ i) - this->v_scaled_shared[i] = _mesh->stl.v_shared[i] / float(SCALING_FACTOR); + this->v_scaled_shared[i] = _mesh->its.vertices[i] / float(SCALING_FACTOR); // Create a mapping from triangle edge into face. struct EdgeToFace { @@ -614,8 +614,8 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) for (int i = 0; i < 3; ++ i) { EdgeToFace &e2f = edges_map[facet_idx*3+i]; - e2f.vertex_low = this->mesh->stl.v_indices[facet_idx].vertex[i]; - e2f.vertex_high = this->mesh->stl.v_indices[facet_idx].vertex[(i + 1) % 3]; + e2f.vertex_low = this->mesh->its.indices[facet_idx].vertex[i]; + e2f.vertex_high = this->mesh->its.indices[facet_idx].vertex[(i + 1) % 3]; e2f.face = facet_idx; // 1 based indexing, to be always strictly positive. e2f.face_edge = i + 1; @@ -852,7 +852,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( // Reorder vertices so that the first one is the one with lowest Z. // This is needed to get all intersection lines in a consistent order // (external on the right of the line) - const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; + const int *vertices = this->mesh->its.indices[facet_idx].vertex; int i = (facet.vertex[1].z() == min_z) ? 1 : ((facet.vertex[2].z() == min_z) ? 2 : 0); // These are used only if the cut plane is tilted: diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index c16f4a6640..75082cfdbd 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -69,12 +69,13 @@ public: void reset_repair_stats(); bool needed_repair() const; void require_shared_vertices(); - bool has_shared_vertices() const { return ! stl.v_shared.empty(); } + bool has_shared_vertices() const { return ! this->its.vertices.empty(); } size_t facets_count() const { return this->stl.stats.number_of_facets; } bool empty() const { return this->facets_count() == 0; } bool is_splittable() const; stl_file stl; + indexed_triangle_set its; bool repaired; private: diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index fb3b6f7a47..45092f257a 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -781,7 +781,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool if (i == 0) suffix[0] = 0; else - sprintf(suffix, "%d", i); + sprintf(suffix, "%d", (int)i); std::string new_name = name + suffix; loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name), new_name, std::move(cfg), i == 0); @@ -1379,7 +1379,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst for (size_t i = 0; i < this->filament_presets.size(); ++ i) { char suffix[64]; if (i > 0) - sprintf(suffix, "_%d", i); + sprintf(suffix, "_%d", (int)i); else suffix[0] = 0; c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl; diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index 53b0520278..3e90bfefdf 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -98,20 +98,18 @@ SV* TriangleMesh::vertices() CODE: if (!THIS->repaired) CONFESS("vertices() requires repair()"); - - if (THIS->stl.v_shared.empty()) - stl_generate_shared_vertices(&(THIS->stl)); + THIS->require_shared_vertices(); // vertices AV* vertices = newAV(); - av_extend(vertices, THIS->stl.v_shared.size()); - for (size_t i = 0; i < THIS->stl.v_shared.size(); i++) { + av_extend(vertices, THIS->its.vertices.size()); + for (size_t i = 0; i < THIS->its.vertices.size(); i++) { AV* vertex = newAV(); av_store(vertices, i, newRV_noinc((SV*)vertex)); av_extend(vertex, 2); - av_store(vertex, 0, newSVnv(THIS->stl.v_shared[i](0))); - av_store(vertex, 1, newSVnv(THIS->stl.v_shared[i](1))); - av_store(vertex, 2, newSVnv(THIS->stl.v_shared[i](2))); + av_store(vertex, 0, newSVnv(THIS->its.vertices[i](0))); + av_store(vertex, 1, newSVnv(THIS->its.vertices[i](1))); + av_store(vertex, 2, newSVnv(THIS->its.vertices[i](2))); } RETVAL = newRV_noinc((SV*)vertices); @@ -122,9 +120,7 @@ SV* TriangleMesh::facets() CODE: if (!THIS->repaired) CONFESS("facets() requires repair()"); - - if (THIS->stl.v_shared.empty()) - stl_generate_shared_vertices(&(THIS->stl)); + THIS->require_shared_vertices(); // facets AV* facets = newAV(); @@ -133,9 +129,9 @@ TriangleMesh::facets() AV* facet = newAV(); av_store(facets, i, newRV_noinc((SV*)facet)); av_extend(facet, 2); - av_store(facet, 0, newSVnv(THIS->stl.v_indices[i].vertex[0])); - av_store(facet, 1, newSVnv(THIS->stl.v_indices[i].vertex[1])); - av_store(facet, 2, newSVnv(THIS->stl.v_indices[i].vertex[2])); + av_store(facet, 0, newSVnv(THIS->its.indices[i].vertex[0])); + av_store(facet, 1, newSVnv(THIS->its.indices[i].vertex[1])); + av_store(facet, 2, newSVnv(THIS->its.indices[i].vertex[2])); } RETVAL = newRV_noinc((SV*)facets); From 313ec7424a9714ba6de64a94b7d4b89ebae05f91 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 10 Jun 2019 19:45:38 +0200 Subject: [PATCH 071/627] admesh refactoring: replaced various diagnostics outputs with boost::log --- src/admesh/connect.cpp | 23 ++- src/admesh/shared.cpp | 16 +- src/admesh/stl.h | 2 +- src/admesh/stl_io.cpp | 283 +++++++++++-------------- src/admesh/stlinit.cpp | 356 +++++++++++++++----------------- src/admesh/util.cpp | 204 +++++++++--------- src/slic3r/GUI/PresetBundle.cpp | 2 +- 7 files changed, 405 insertions(+), 481 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index 9553e7c4d5..5ae03597ee 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include "stl.h" @@ -124,7 +125,9 @@ struct HashTableEdges { for (HashEdge *temp = this->heads[i]; this->heads[i] != this->tail; temp = this->heads[i]) { this->heads[i] = this->heads[i]->next; delete temp; +#ifndef NDEBUG ++ this->freed; +#endif /* NDEBUG */ } } this->heads.clear(); @@ -139,7 +142,9 @@ struct HashTableEdges { if (link == this->tail) { // This list doesn't have any edges currently in it. Add this one. HashEdge *new_edge = new HashEdge(edge); +#ifndef NDEBUG ++ this->malloced; +#endif /* NDEBUG */ new_edge->next = this->tail; this->heads[chain_number] = new_edge; } else if (edges_equal(edge, *link)) { @@ -148,18 +153,24 @@ struct HashTableEdges { // Delete the matched edge from the list. this->heads[chain_number] = link->next; delete link; +#ifndef NDEBUG ++ this->freed; +#endif /* NDEBUG */ } else { // Continue through the rest of the list. for (;;) { if (link->next == this->tail) { // This is the last item in the list. Insert a new edge. HashEdge *new_edge = new HashEdge; +#ifndef NDEBUG ++ this->malloced; +#endif /* NDEBUG */ *new_edge = edge; new_edge->next = this->tail; link->next = new_edge; +#ifndef NDEBUG ++ this->collisions; +#endif /* NDEBUG */ break; } if (edges_equal(edge, *link->next)) { @@ -169,12 +180,16 @@ struct HashTableEdges { HashEdge *temp = link->next; link->next = link->next->next; delete temp; +#ifndef NDEBUG ++ this->freed; +#endif /* NDEBUG */ break; } // This is not a match. Go to the next link. link = link->next; +#ifndef NDEBUG ++ this->collisions; +#endif /* NDEBUG */ } } } @@ -184,9 +199,11 @@ struct HashTableEdges { HashEdge* tail; int M; +#ifndef NDEBUG size_t malloced = 0; size_t freed = 0; size_t collisions = 0; +#endif /* NDEBUG */ private: static inline size_t hash_size_from_nr_faces(const size_t nr_faces) @@ -366,7 +383,7 @@ static void match_neighbors_nearby(stl_file *stl, const HashEdge &edge_a, const if (facet_num == first_facet) { // back to the beginning - printf("Back to the first facet changing vertices: probably a mobius part.\nTry using a smaller tolerance or don't do a nearby check\n"); + BOOST_LOG_TRIVIAL(info) << "Back to the first facet changing vertices: probably a mobius part. Try using a smaller tolerance or don't do a nearby check."; return; } } @@ -506,7 +523,7 @@ void stl_remove_unconnected_facets(stl_file *stl) if (neighbors.neighbor[i] != -1) { int &other_face_idx = stl->neighbors_start[neighbors.neighbor[i]].neighbor[(neighbors.which_vertex_not[i] + 1) % 3]; if (other_face_idx != stl->stats.number_of_facets) { - printf("in remove_facet: neighbor = %d numfacets = %d this is wrong\n", other_face_idx, stl->stats.number_of_facets); + BOOST_LOG_TRIVIAL(info) << "in remove_facet: neighbor = " << other_face_idx << " numfacets = " << stl->stats.number_of_facets << " this is wrong"; return; } other_face_idx = facet_number; @@ -697,7 +714,7 @@ void stl_fill_holes(stl_file *stl) if (facet_num == first_facet) { // back to the beginning - printf("Back to the first facet filling holes: probably a mobius part.\nTry using a smaller tolerance or don't do a nearby check\n"); + BOOST_LOG_TRIVIAL(info) << "Back to the first facet filling holes: probably a mobius part. Try using a smaller tolerance or don't do a nearby check."; return; } } diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index 7da2841b05..fe6d5e6564 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -25,6 +25,7 @@ #include +#include #include #include "stl.h" @@ -129,10 +130,7 @@ bool its_write_off(const indexed_triangle_set &its, const char *file) /* Open the file */ FILE *fp = boost::nowide::fopen(file, "w"); if (fp == nullptr) { - char *error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); - perror(error_msg); - free(error_msg); + BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing"; return false; } @@ -151,10 +149,7 @@ bool its_write_vrml(const indexed_triangle_set &its, const char *file) /* Open the file */ FILE *fp = boost::nowide::fopen(file, "w"); if (fp == nullptr) { - char *error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); - perror(error_msg); - free(error_msg); + BOOST_LOG_TRIVIAL(error) << "stl_write_vrml: Couldn't open " << file << " for writing"; return false; } @@ -196,10 +191,7 @@ bool its_write_obj(const indexed_triangle_set &its, const char *file) FILE *fp = boost::nowide::fopen(file, "w"); if (fp == nullptr) { - char* error_msg = (char*)malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", file); - perror(error_msg); - free(error_msg); + BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing"; return false; } diff --git a/src/admesh/stl.h b/src/admesh/stl.h index bb5d252960..500d6bfdb1 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -242,7 +242,7 @@ inline bool stl_vertex_lower(const stl_vertex &a, const stl_vertex &b) { } extern void stl_calculate_volume(stl_file *stl); -extern void stl_repair(stl_file *stl, int fixall_flag, int exact_flag, int tolerance_flag, float tolerance, int increment_flag, float increment, int nearby_flag, int iterations, int remove_unconnected_flag, int fill_holes_flag, int normal_directions_flag, int normal_values_flag, int reverse_all_flag, int verbose_flag); +extern void stl_repair(stl_file *stl, bool fixall_flag, bool exact_flag, bool tolerance_flag, float tolerance, bool increment_flag, float increment, bool nearby_flag, int iterations, bool remove_unconnected_flag, bool fill_holes_flag, bool normal_directions_flag, bool normal_values_flag, bool reverse_all_flag, bool verbose_flag); extern void stl_reset(stl_file *stl); extern void stl_allocate(stl_file *stl); diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp index 8f809d3799..dc4e4a7db3 100644 --- a/src/admesh/stl_io.cpp +++ b/src/admesh/stl_io.cpp @@ -24,6 +24,7 @@ #include #include "stl.h" +#include #include #include @@ -107,65 +108,47 @@ Normals fixed : %5d\n", stl->stats.normals_fixed); bool stl_write_ascii(stl_file *stl, const char *file, const char *label) { - char *error_msg; + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == NULL) { + BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing"; + return false; + } - /* Open the file */ - FILE *fp = boost::nowide::fopen(file, "w"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - return false; - } + fprintf(fp, "solid %s\n", label); - fprintf(fp, "solid %s\n", label); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + fprintf(fp, " facet normal % .8E % .8E % .8E\n", + stl->facet_start[i].normal(0), stl->facet_start[i].normal(1), + stl->facet_start[i].normal(2)); + fprintf(fp, " outer loop\n"); + fprintf(fp, " vertex % .8E % .8E % .8E\n", + stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), + stl->facet_start[i].vertex[0](2)); + fprintf(fp, " vertex % .8E % .8E % .8E\n", + stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), + stl->facet_start[i].vertex[1](2)); + fprintf(fp, " vertex % .8E % .8E % .8E\n", + stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), + stl->facet_start[i].vertex[2](2)); + fprintf(fp, " endloop\n"); + fprintf(fp, " endfacet\n"); + } - for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { - fprintf(fp, " facet normal % .8E % .8E % .8E\n", - stl->facet_start[i].normal(0), stl->facet_start[i].normal(1), - stl->facet_start[i].normal(2)); - fprintf(fp, " outer loop\n"); - fprintf(fp, " vertex % .8E % .8E % .8E\n", - stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), - stl->facet_start[i].vertex[0](2)); - fprintf(fp, " vertex % .8E % .8E % .8E\n", - stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), - stl->facet_start[i].vertex[1](2)); - fprintf(fp, " vertex % .8E % .8E % .8E\n", - stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2)); - fprintf(fp, " endloop\n"); - fprintf(fp, " endfacet\n"); - } - - fprintf(fp, "endsolid %s\n", label); - - fclose(fp); - return true; + fprintf(fp, "endsolid %s\n", label); + fclose(fp); + return true; } bool stl_print_neighbors(stl_file *stl, char *file) { - FILE *fp; - char *error_msg; + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == NULL) { + BOOST_LOG_TRIVIAL(error) << "stl_print_neighbors: Couldn't open " << file << " for writing"; + return false; + } - /* Open the file */ - fp = boost::nowide::fopen(file, "w"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_print_neighbors: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - return false; - } - - for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { - fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n", + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + fprintf(fp, "%d, %d,%d, %d,%d, %d,%d\n", i, stl->neighbors_start[i].neighbor[0], (int)stl->neighbors_start[i].which_vertex_not[0], @@ -173,62 +156,54 @@ bool stl_print_neighbors(stl_file *stl, char *file) (int)stl->neighbors_start[i].which_vertex_not[1], stl->neighbors_start[i].neighbor[2], (int)stl->neighbors_start[i].which_vertex_not[2]); - } - fclose(fp); - return true; + } + fclose(fp); + return true; } #ifndef BOOST_LITTLE_ENDIAN // Swap a buffer of 32bit data from little endian to big endian and vice versa. void stl_internal_reverse_quads(char *buf, size_t cnt) { - for (size_t i = 0; i < cnt; i += 4) { - std::swap(buf[i], buf[i+3]); - std::swap(buf[i+1], buf[i+2]); - } + for (size_t i = 0; i < cnt; i += 4) { + std::swap(buf[i], buf[i+3]); + std::swap(buf[i+1], buf[i+2]); + } } #endif bool stl_write_binary(stl_file *stl, const char *file, const char *label) { - FILE *fp; - char *error_msg; + FILE *fp = boost::nowide::fopen(file, "wb"); + if (fp == NULL) { + BOOST_LOG_TRIVIAL(error) << "stl_write_binary: Couldn't open " << file << " for writing"; + return false; + } - /* Open the file */ - fp = boost::nowide::fopen(file, "wb"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_binary: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - return false; - } + fprintf(fp, "%s", label); + for (size_t i = strlen(label); i < LABEL_SIZE; ++ i) + putc(0, fp); - fprintf(fp, "%s", label); - for(size_t i = strlen(label); i < LABEL_SIZE; i++) putc(0, fp); - - fseek(fp, LABEL_SIZE, SEEK_SET); + fseek(fp, LABEL_SIZE, SEEK_SET); #ifdef BOOST_LITTLE_ENDIAN - fwrite(&stl->stats.number_of_facets, 4, 1, fp); - for (const stl_facet &facet : stl->facet_start) - fwrite(&facet, SIZEOF_STL_FACET, 1, fp); + fwrite(&stl->stats.number_of_facets, 4, 1, fp); + for (const stl_facet &facet : stl->facet_start) + fwrite(&facet, SIZEOF_STL_FACET, 1, fp); #else /* BOOST_LITTLE_ENDIAN */ - char buffer[50]; - // Convert the number of facets to little endian. - memcpy(buffer, &stl->stats.number_of_facets, 4); - stl_internal_reverse_quads(buffer, 4); - fwrite(buffer, 4, 1, fp); - for (i = 0; i < stl->stats.number_of_facets; ++ i) { - memcpy(buffer, stl->facet_start + i, 50); - // Convert to little endian. - stl_internal_reverse_quads(buffer, 48); - fwrite(buffer, SIZEOF_STL_FACET, 1, fp); - } + char buffer[50]; + // Convert the number of facets to little endian. + memcpy(buffer, &stl->stats.number_of_facets, 4); + stl_internal_reverse_quads(buffer, 4); + fwrite(buffer, 4, 1, fp); + for (i = 0; i < stl->stats.number_of_facets; ++ i) { + memcpy(buffer, stl->facet_start + i, 50); + // Convert to little endian. + stl_internal_reverse_quads(buffer, 48); + fwrite(buffer, SIZEOF_STL_FACET, 1, fp); + } #endif /* BOOST_LITTLE_ENDIAN */ - fclose(fp); - return true; + fclose(fp); + return true; } void stl_write_vertex(stl_file *stl, int facet, int vertex) @@ -260,53 +235,39 @@ void stl_write_neighbor(stl_file *stl, int facet) bool stl_write_quad_object(stl_file *stl, char *file) { - FILE *fp; - char *error_msg; - stl_vertex connect_color = stl_vertex::Zero(); - stl_vertex uncon_1_color = stl_vertex::Zero(); - stl_vertex uncon_2_color = stl_vertex::Zero(); - stl_vertex uncon_3_color = stl_vertex::Zero(); - stl_vertex color; + stl_vertex connect_color = stl_vertex::Zero(); + stl_vertex uncon_1_color = stl_vertex::Zero(); + stl_vertex uncon_2_color = stl_vertex::Zero(); + stl_vertex uncon_3_color = stl_vertex::Zero(); + stl_vertex color; - /* Open the file */ - fp = boost::nowide::fopen(file, "w"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_quad_object: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - return false; - } + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == NULL) { + BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing"; + return false; + } - fprintf(fp, "CQUAD\n"); - for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { - int j = ((stl->neighbors_start[i].neighbor[0] == -1) + - (stl->neighbors_start[i].neighbor[1] == -1) + - (stl->neighbors_start[i].neighbor[2] == -1)); - if(j == 0) { - color = connect_color; - } else if(j == 1) { - color = uncon_1_color; - } else if(j == 2) { - color = uncon_2_color; - } else { - color = uncon_3_color; - } - fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", + fprintf(fp, "CQUAD\n"); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + switch (stl->neighbors_start[i].num_neighbors_missing()) { + case 0: color = connect_color; break; + case 1: color = uncon_1_color; break; + case 2: color = uncon_2_color; break; + default: color = uncon_3_color; + } + fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2), color(0), color(1), color(2)); - fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", + fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2), color(0), color(1), color(2)); - fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", + fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); - fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", + fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); @@ -317,47 +278,37 @@ bool stl_write_quad_object(stl_file *stl, char *file) bool stl_write_dxf(stl_file *stl, const char *file, char *label) { - FILE *fp; - char *error_msg; + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == NULL) { + BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing"; + return false; + } - /* Open the file */ - fp = boost::nowide::fopen(file, "w"); - if(fp == NULL) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_write_ascii: Couldn't open %s for writing", - file); - perror(error_msg); - free(error_msg); - return false; - } + fprintf(fp, "999\n%s\n", label); + fprintf(fp, "0\nSECTION\n2\nHEADER\n0\nENDSEC\n"); + fprintf(fp, "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nLAYER\n70\n1\n\ + 0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n0\nENDTAB\n0\nENDSEC\n"); + fprintf(fp, "0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n"); - fprintf(fp, "999\n%s\n", label); - fprintf(fp, "0\nSECTION\n2\nHEADER\n0\nENDSEC\n"); - fprintf(fp, "0\nSECTION\n2\nTABLES\n0\nTABLE\n2\nLAYER\n70\n1\n\ -0\nLAYER\n2\n0\n70\n0\n62\n7\n6\nCONTINUOUS\n0\nENDTAB\n0\nENDSEC\n"); - fprintf(fp, "0\nSECTION\n2\nBLOCKS\n0\nENDSEC\n"); + fprintf(fp, "0\nSECTION\n2\nENTITIES\n"); - fprintf(fp, "0\nSECTION\n2\nENTITIES\n"); + for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { + fprintf(fp, "0\n3DFACE\n8\n0\n"); + fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", + stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), + stl->facet_start[i].vertex[0](2)); + fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", + stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), + stl->facet_start[i].vertex[1](2)); + fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", + stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), + stl->facet_start[i].vertex[2](2)); + fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", + stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), + stl->facet_start[i].vertex[2](2)); + } - for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { - fprintf(fp, "0\n3DFACE\n8\n0\n"); - fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", - stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), - stl->facet_start[i].vertex[0](2)); - fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", - stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), - stl->facet_start[i].vertex[1](2)); - fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", - stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2)); - fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", - stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2)); - } - - fprintf(fp, "0\nENDSEC\n0\nEOF\n"); - - fclose(fp); - return true; + fprintf(fp, "0\nENDSEC\n0\nEOF\n"); + fclose(fp); + return true; } diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index 44477511f9..24fbe9edca 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -37,118 +38,102 @@ static FILE* stl_open_count_facets(stl_file *stl, const char *file) { - long file_size; - uint32_t header_num_facets; - uint32_t num_facets; - int i; - size_t s; - unsigned char chtest[128]; - int num_lines = 1; - char *error_msg; + // Open the file in binary mode first. + FILE *fp = boost::nowide::fopen(file, "rb"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: Couldn't open " << file << " for reading"; + return nullptr; + } + // Find size of file. + fseek(fp, 0, SEEK_END); + long file_size = ftell(fp); - /* Open the file in binary mode first */ - FILE *fp = boost::nowide::fopen(file, "rb"); - if (fp == nullptr) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", - file); - perror(error_msg); - free(error_msg); - return nullptr; - } - /* Find size of file */ - fseek(fp, 0, SEEK_END); - file_size = ftell(fp); + // Check for binary or ASCII file. + fseek(fp, HEADER_SIZE, SEEK_SET); + unsigned char chtest[128]; + if (! fread(chtest, sizeof(chtest), 1, fp)) { + BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: The input is an empty file: " << file; + fclose(fp); + return nullptr; + } + stl->stats.type = ascii; + for (size_t s = 0; s < sizeof(chtest); s++) { + if (chtest[s] > 127) { + stl->stats.type = binary; + break; + } + } + rewind(fp); - /* Check for binary or ASCII file */ - fseek(fp, HEADER_SIZE, SEEK_SET); - if (!fread(chtest, sizeof(chtest), 1, fp)) { - perror("The input is an empty file"); - fclose(fp); - return nullptr; - } - stl->stats.type = ascii; - for(s = 0; s < sizeof(chtest); s++) { - if(chtest[s] > 127) { - stl->stats.type = binary; - break; - } - } - rewind(fp); + uint32_t num_facets = 0; - /* Get the header and the number of facets in the .STL file */ - /* If the .STL file is binary, then do the following */ - if(stl->stats.type == binary) { - /* Test if the STL file has the right size */ - if(((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) - || (file_size < STL_MIN_FILE_SIZE)) { - fprintf(stderr, "The file %s has the wrong size.\n", file); - fclose(fp); - return nullptr; - } - num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET; + // Get the header and the number of facets in the .STL file. + // If the .STL file is binary, then do the following: + if (stl->stats.type == binary) { + // Test if the STL file has the right size. + if (((file_size - HEADER_SIZE) % SIZEOF_STL_FACET != 0) || (file_size < STL_MIN_FILE_SIZE)) { + BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: The file " << file << " has the wrong size."; + fclose(fp); + return nullptr; + } + num_facets = (file_size - HEADER_SIZE) / SIZEOF_STL_FACET; - /* Read the header */ - if (fread(stl->stats.header, LABEL_SIZE, 1, fp) > 79) { - stl->stats.header[80] = '\0'; - } + // Read the header. + if (fread(stl->stats.header, LABEL_SIZE, 1, fp) > 79) + stl->stats.header[80] = '\0'; - /* Read the int following the header. This should contain # of facets */ - bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, fp) != 0; + // Read the int following the header. This should contain # of facets. + uint32_t header_num_facets; + bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, fp) != 0; #ifndef BOOST_LITTLE_ENDIAN - // Convert from little endian to big endian. - stl_internal_reverse_quads((char*)&header_num_facets, 4); + // Convert from little endian to big endian. + stl_internal_reverse_quads((char*)&header_num_facets, 4); #endif /* BOOST_LITTLE_ENDIAN */ - if (! header_num_faces_read || num_facets != header_num_facets) { - fprintf(stderr, - "Warning: File size doesn't match number of facets in the header\n"); - } - } - /* Otherwise, if the .STL file is ASCII, then do the following */ - else { - /* Reopen the file in text mode (for getting correct newlines on Windows) */ - // fix to silence a warning about unused return value. - // obviously if it fails we have problems.... - fp = boost::nowide::freopen(file, "r", fp); + if (! header_num_faces_read || num_facets != header_num_facets) + BOOST_LOG_TRIVIAL(info) << "stl_open_count_facets: Warning: File size doesn't match number of facets in the header: " << file; + } + // Otherwise, if the .STL file is ASCII, then do the following: + else + { + // Reopen the file in text mode (for getting correct newlines on Windows) + // fix to silence a warning about unused return value. + // obviously if it fails we have problems.... + fp = boost::nowide::freopen(file, "r", fp); - // do another null check to be safe - if(fp == nullptr) { - error_msg = (char*) - malloc(81 + strlen(file)); /* Allow 80 chars+file size for message */ - sprintf(error_msg, "stl_initialize: Couldn't open %s for reading", - file); - perror(error_msg); - free(error_msg); - fclose(fp); - return nullptr; - } + // do another null check to be safe + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "stl_open_count_facets: Couldn't open " << file << " for reading"; + fclose(fp); + return nullptr; + } - /* Find the number of facets */ - char linebuf[100]; - while (fgets(linebuf, 100, fp) != nullptr) { - /* don't count short lines */ - if (strlen(linebuf) <= 4) continue; - - /* skip solid/endsolid lines as broken STL file generators may put several of them */ - if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) continue; - - ++num_lines; - } - - rewind(fp); - - /* Get the header */ - for(i = 0; - (i < 80) && (stl->stats.header[i] = getc(fp)) != '\n'; i++); - stl->stats.header[i] = '\0'; /* Lose the '\n' */ - stl->stats.header[80] = '\0'; + // Find the number of facets. + char linebuf[100]; + int num_lines = 1; + while (fgets(linebuf, 100, fp) != nullptr) { + // Don't count short lines. + if (strlen(linebuf) <= 4) + continue; + // Skip solid/endsolid lines as broken STL file generators may put several of them. + if (strncmp(linebuf, "solid", 5) == 0 || strncmp(linebuf, "endsolid", 8) == 0) + continue; + ++ num_lines; + } - num_facets = num_lines / ASCII_LINES_PER_FACET; - } - stl->stats.number_of_facets += num_facets; - stl->stats.original_num_facets = stl->stats.number_of_facets; - return fp; + rewind(fp); + + // Get the header. + int i = 0; + for (; i < 80 && (stl->stats.header[i] = getc(fp)) != '\n'; ++ i) ; + stl->stats.header[i] = '\0'; // Lose the '\n' + stl->stats.header[80] = '\0'; + + num_facets = num_lines / ASCII_LINES_PER_FACET; + } + + stl->stats.number_of_facets += num_facets; + stl->stats.original_num_facets = stl->stats.number_of_facets; + return fp; } /* Reads the contents of the file pointed to by fp into the stl structure, @@ -156,85 +141,82 @@ static FILE* stl_open_count_facets(stl_file *stl, const char *file) time running this for the stl and therefore we should reset our max and min stats. */ static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first) { - stl_facet facet; + if (stl->stats.type == binary) + fseek(fp, HEADER_SIZE, SEEK_SET); + else + rewind(fp); - if(stl->stats.type == binary) { - fseek(fp, HEADER_SIZE, SEEK_SET); - } else { - rewind(fp); - } + char normal_buf[3][32]; + for (uint32_t i = first_facet; i < stl->stats.number_of_facets; ++i) { + stl_facet facet; - char normal_buf[3][32]; - for(uint32_t i = first_facet; i < stl->stats.number_of_facets; i++) { - if(stl->stats.type == binary) - /* Read a single facet from a binary .STL file */ - { - /* we assume little-endian architecture! */ - if (fread(&facet, 1, SIZEOF_STL_FACET, fp) != SIZEOF_STL_FACET) - return false; + if (stl->stats.type == binary) { + // Read a single facet from a binary .STL file. We assume little-endian architecture! + if (fread(&facet, 1, SIZEOF_STL_FACET, fp) != SIZEOF_STL_FACET) + return false; #ifndef BOOST_LITTLE_ENDIAN - // Convert the loaded little endian data to big endian. - stl_internal_reverse_quads((char*)&facet, 48); + // Convert the loaded little endian data to big endian. + stl_internal_reverse_quads((char*)&facet, 48); #endif /* BOOST_LITTLE_ENDIAN */ - } else - /* Read a single facet from an ASCII .STL file */ - { - // skip solid/endsolid - // (in this order, otherwise it won't work when they are paired in the middle of a file) - fscanf(fp, "endsolid%*[^\n]\n"); - fscanf(fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid") - // Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs. - int res_normal = fscanf(fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); - assert(res_normal == 3); - int res_outer_loop = fscanf(fp, " outer loop"); - assert(res_outer_loop == 0); - int res_vertex1 = fscanf(fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); - assert(res_vertex1 == 3); - int res_vertex2 = fscanf(fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); - assert(res_vertex2 == 3); - int res_vertex3 = fscanf(fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); - assert(res_vertex3 == 3); - int res_endloop = fscanf(fp, " endloop"); - assert(res_endloop == 0); - // There is a leading and trailing white space around endfacet to eat up all leading and trailing white spaces including numerous tabs and new lines. - int res_endfacet = fscanf(fp, " endfacet "); - if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { - perror("Something is syntactically very wrong with this ASCII STL!"); - return false; - } + } else { + // Read a single facet from an ASCII .STL file + // skip solid/endsolid + // (in this order, otherwise it won't work when they are paired in the middle of a file) + fscanf(fp, "endsolid%*[^\n]\n"); + fscanf(fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid") + // Leading space in the fscanf format skips all leading white spaces including numerous new lines and tabs. + int res_normal = fscanf(fp, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); + assert(res_normal == 3); + int res_outer_loop = fscanf(fp, " outer loop"); + assert(res_outer_loop == 0); + int res_vertex1 = fscanf(fp, " vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); + assert(res_vertex1 == 3); + int res_vertex2 = fscanf(fp, " vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); + assert(res_vertex2 == 3); + int res_vertex3 = fscanf(fp, " vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); + assert(res_vertex3 == 3); + int res_endloop = fscanf(fp, " endloop"); + assert(res_endloop == 0); + // There is a leading and trailing white space around endfacet to eat up all leading and trailing white spaces including numerous tabs and new lines. + int res_endfacet = fscanf(fp, " endfacet "); + if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { + BOOST_LOG_TRIVIAL(error) << "Something is syntactically very wrong with this ASCII STL! "; + return false; + } - // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. - if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 || - sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 || - sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { - // Normal was mangled. Maybe denormals or "not a number" were stored? - // Just reset the normal and silently ignore it. - memset(&facet.normal, 0, sizeof(facet.normal)); - } - } + // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. + if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 || + sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 || + sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { + // Normal was mangled. Maybe denormals or "not a number" were stored? + // Just reset the normal and silently ignore it. + memset(&facet.normal, 0, sizeof(facet.normal)); + } + } #if 0 - // Report close to zero vertex coordinates. Due to the nature of the floating point numbers, - // close to zero values may be represented with singificantly higher precision than the rest of the vertices. - // It may be worth to round these numbers to zero during loading to reduce the number of errors reported - // during the STL import. - for (size_t j = 0; j < 3; ++ j) { - if (facet.vertex[j](0) > -1e-12f && facet.vertex[j](0) < 1e-12f) - printf("stl_read: facet %d(0) = %e\r\n", j, facet.vertex[j](0)); - if (facet.vertex[j](1) > -1e-12f && facet.vertex[j](1) < 1e-12f) - printf("stl_read: facet %d(1) = %e\r\n", j, facet.vertex[j](1)); - if (facet.vertex[j](2) > -1e-12f && facet.vertex[j](2) < 1e-12f) - printf("stl_read: facet %d(2) = %e\r\n", j, facet.vertex[j](2)); - } + // Report close to zero vertex coordinates. Due to the nature of the floating point numbers, + // close to zero values may be represented with singificantly higher precision than the rest of the vertices. + // It may be worth to round these numbers to zero during loading to reduce the number of errors reported + // during the STL import. + for (size_t j = 0; j < 3; ++ j) { + if (facet.vertex[j](0) > -1e-12f && facet.vertex[j](0) < 1e-12f) + printf("stl_read: facet %d(0) = %e\r\n", j, facet.vertex[j](0)); + if (facet.vertex[j](1) > -1e-12f && facet.vertex[j](1) < 1e-12f) + printf("stl_read: facet %d(1) = %e\r\n", j, facet.vertex[j](1)); + if (facet.vertex[j](2) > -1e-12f && facet.vertex[j](2) < 1e-12f) + printf("stl_read: facet %d(2) = %e\r\n", j, facet.vertex[j](2)); + } #endif - /* Write the facet into memory. */ - stl->facet_start[i] = facet; - stl_facet_stats(stl, facet, first); - } - stl->stats.size = stl->stats.max - stl->stats.min; - stl->stats.bounding_diameter = stl->stats.size.norm(); - return true; + // Write the facet into memory. + stl->facet_start[i] = facet; + stl_facet_stats(stl, facet, first); + } + + stl->stats.size = stl->stats.max - stl->stats.min; + stl->stats.bounding_diameter = stl->stats.size.norm(); + return true; } bool stl_open(stl_file *stl, const char *file) @@ -277,21 +259,21 @@ void stl_reallocate(stl_file *stl) void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first) { - // While we are going through all of the facets, let's find the - // maximum and minimum values for x, y, and z + // While we are going through all of the facets, let's find the + // maximum and minimum values for x, y, and z - if (first) { - // Initialize the max and min values the first time through - stl->stats.min = facet.vertex[0]; - stl->stats.max = facet.vertex[0]; - stl_vertex diff = (facet.vertex[1] - facet.vertex[0]).cwiseAbs(); - stl->stats.shortest_edge = std::max(diff(0), std::max(diff(1), diff(2))); - first = false; - } + if (first) { + // Initialize the max and min values the first time through + stl->stats.min = facet.vertex[0]; + stl->stats.max = facet.vertex[0]; + stl_vertex diff = (facet.vertex[1] - facet.vertex[0]).cwiseAbs(); + stl->stats.shortest_edge = std::max(diff(0), std::max(diff(1), diff(2))); + first = false; + } - // Now find the max and min values. - for (size_t i = 0; i < 3; ++ i) { - stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]); - stl->stats.max = stl->stats.max.cwiseMax(facet.vertex[i]); - } + // Now find the max and min values. + for (size_t i = 0; i < 3; ++ i) { + stl->stats.min = stl->stats.min.cwiseMin(facet.vertex[i]); + stl->stats.max = stl->stats.max.cwiseMax(facet.vertex[i]); + } } diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index f4e4dbf0a7..bb135db95b 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -25,13 +25,14 @@ #include #include +#include + #include "stl.h" static void stl_rotate(float *x, float *y, const double c, const double s); static float get_area(stl_facet *facet); static float get_volume(stl_file *stl); - void stl_verify_neighbors(stl_file *stl) { stl->stats.backwards_edges = 0; @@ -56,7 +57,7 @@ void stl_verify_neighbors(stl_file *stl) } if (edge_a.p1 != edge_b.p1 || edge_a.p2 != edge_b.p2) { // These edges should match but they don't. Print results. - printf("edge %d of facet %d doesn't match edge %d of facet %d\n", j, i, vnot + 1, neighbor); + BOOST_LOG_TRIVIAL(info) << "edge " << j << " of facet " << i << " doesn't match edge " << (vnot + 1) << " of facet " << neighbor; stl_write_facet(stl, (char*)"first facet", i); stl_write_facet(stl, (char*)"second facet", neighbor); } @@ -291,123 +292,104 @@ static float get_area(stl_facet *facet) return 0.5f * n.dot(sum); } -void stl_repair(stl_file *stl, - int fixall_flag, - int exact_flag, - int tolerance_flag, - float tolerance, - int increment_flag, - float increment, - int nearby_flag, - int iterations, - int remove_unconnected_flag, - int fill_holes_flag, - int normal_directions_flag, - int normal_values_flag, - int reverse_all_flag, - int verbose_flag) { - - int i; - int last_edges_fixed = 0; +void stl_repair( + stl_file *stl, + bool fixall_flag, + bool exact_flag, + bool tolerance_flag, + float tolerance, + bool increment_flag, + float increment, + bool nearby_flag, + int iterations, + bool remove_unconnected_flag, + bool fill_holes_flag, + bool normal_directions_flag, + bool normal_values_flag, + bool reverse_all_flag, + bool verbose_flag) +{ + if (exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag || fill_holes_flag || normal_directions_flag) { + if (verbose_flag) + printf("Checking exact...\n"); + exact_flag = true; + stl_check_facets_exact(stl); + stl->stats.facets_w_1_bad_edge = (stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge); + stl->stats.facets_w_2_bad_edge = (stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge); + stl->stats.facets_w_3_bad_edge = (stl->stats.number_of_facets - stl->stats.connected_facets_1_edge); + } - if(exact_flag || fixall_flag || nearby_flag || remove_unconnected_flag - || fill_holes_flag || normal_directions_flag) { - if (verbose_flag) - printf("Checking exact...\n"); - exact_flag = 1; - stl_check_facets_exact(stl); - stl->stats.facets_w_1_bad_edge = - (stl->stats.connected_facets_2_edge - - stl->stats.connected_facets_3_edge); - stl->stats.facets_w_2_bad_edge = - (stl->stats.connected_facets_1_edge - - stl->stats.connected_facets_2_edge); - stl->stats.facets_w_3_bad_edge = - (stl->stats.number_of_facets - - stl->stats.connected_facets_1_edge); - } - - if(nearby_flag || fixall_flag) { - if(!tolerance_flag) { - tolerance = stl->stats.shortest_edge; - } - if(!increment_flag) { - increment = stl->stats.bounding_diameter / 10000.0; + if (nearby_flag || fixall_flag) { + if (! tolerance_flag) + tolerance = stl->stats.shortest_edge; + if (! increment_flag) + increment = stl->stats.bounding_diameter / 10000.0; } - if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { - for(i = 0; i < iterations; i++) { - if(stl->stats.connected_facets_3_edge < - stl->stats.number_of_facets) { - if (verbose_flag) - printf("\ -Checking nearby. Tolerance= %f Iteration=%d of %d...", - tolerance, i + 1, iterations); - stl_check_facets_nearby(stl, tolerance); - if (verbose_flag) - printf(" Fixed %d edges.\n", - stl->stats.edges_fixed - last_edges_fixed); - last_edges_fixed = stl->stats.edges_fixed; - tolerance += increment; - } else { - if (verbose_flag) - printf("\ -All facets connected. No further nearby check necessary.\n"); - break; - } - } - } else { - if (verbose_flag) - printf("All facets connected. No nearby check necessary.\n"); - } - } + if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { + int last_edges_fixed = 0; + for (int i = 0; i < iterations; ++ i) { + if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { + if (verbose_flag) + printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); + stl_check_facets_nearby(stl, tolerance); + if (verbose_flag) + printf(" Fixed %d edges.\n", stl->stats.edges_fixed - last_edges_fixed); + last_edges_fixed = stl->stats.edges_fixed; + tolerance += increment; + } else { + if (verbose_flag) + printf("All facets connected. No further nearby check necessary.\n"); + break; + } + } + } else if (verbose_flag) + printf("All facets connected. No nearby check necessary.\n"); - if(remove_unconnected_flag || fixall_flag || fill_holes_flag) { - if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { - if (verbose_flag) - printf("Removing unconnected facets...\n"); - stl_remove_unconnected_facets(stl); - } else - if (verbose_flag) - printf("No unconnected need to be removed.\n"); - } + if (remove_unconnected_flag || fixall_flag || fill_holes_flag) { + if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { + if (verbose_flag) + printf("Removing unconnected facets...\n"); + stl_remove_unconnected_facets(stl); + } else if (verbose_flag) + printf("No unconnected need to be removed.\n"); + } - if(fill_holes_flag || fixall_flag) { - if(stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { - if (verbose_flag) - printf("Filling holes...\n"); - stl_fill_holes(stl); - } else - if (verbose_flag) - printf("No holes need to be filled.\n"); - } + if (fill_holes_flag || fixall_flag) { + if (stl->stats.connected_facets_3_edge < stl->stats.number_of_facets) { + if (verbose_flag) + printf("Filling holes...\n"); + stl_fill_holes(stl); + } else if (verbose_flag) + printf("No holes need to be filled.\n"); + } - if(reverse_all_flag) { - if (verbose_flag) - printf("Reversing all facets...\n"); - stl_reverse_all_facets(stl); - } + if (reverse_all_flag) { + if (verbose_flag) + printf("Reversing all facets...\n"); + stl_reverse_all_facets(stl); + } - if(normal_directions_flag || fixall_flag) { - if (verbose_flag) - printf("Checking normal directions...\n"); - stl_fix_normal_directions(stl); - } + if (normal_directions_flag || fixall_flag) { + if (verbose_flag) + printf("Checking normal directions...\n"); + stl_fix_normal_directions(stl); + } - if(normal_values_flag || fixall_flag) { - if (verbose_flag) - printf("Checking normal values...\n"); - stl_fix_normal_values(stl); - } + if (normal_values_flag || fixall_flag) { + if (verbose_flag) + printf("Checking normal values...\n"); + stl_fix_normal_values(stl); + } - /* Always calculate the volume. It shouldn't take too long */ - if (verbose_flag) - printf("Calculating volume...\n"); - stl_calculate_volume(stl); + // Always calculate the volume. It shouldn't take too long. + if (verbose_flag) + printf("Calculating volume...\n"); + stl_calculate_volume(stl); - if(exact_flag) { - if (verbose_flag) - printf("Verifying neighbors...\n"); - stl_verify_neighbors(stl); - } + if (exact_flag) { + if (verbose_flag) + printf("Verifying neighbors...\n"); + stl_verify_neighbors(stl); + } } diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 45092f257a..b28cb2eda7 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -837,7 +837,7 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const return preset_name_dst; // Try to generate another name. char buf[64]; - sprintf(buf, " (%d)", i); + sprintf(buf, " (%d)", (int)i); preset_name_dst = preset_name_src + buf + bundle_name; } } From af5017c46c42305553e42f95c4f62c2cbe27d607 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 10 Jun 2019 21:14:58 +0200 Subject: [PATCH 072/627] admesh refactoring: Use Eigen vec3i for indexed triangles. --- src/admesh/connect.cpp | 10 +- src/admesh/normals.cpp | 308 +++++++++++++++---------------- src/admesh/shared.cpp | 20 +- src/admesh/stl.h | 17 +- src/admesh/stl_io.cpp | 170 ++++++----------- src/admesh/util.cpp | 321 +++++++++++++++------------------ src/libslic3r/Format/3mf.cpp | 2 +- src/libslic3r/Format/AMF.cpp | 2 +- src/libslic3r/TriangleMesh.cpp | 6 +- xs/xsp/TriangleMesh.xsp | 6 +- 10 files changed, 369 insertions(+), 493 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index 5ae03597ee..cbd4f1d337 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -196,13 +196,13 @@ struct HashTableEdges { // Hash table on edges std::vector heads; - HashEdge* tail; - int M; + HashEdge* tail; + int M; #ifndef NDEBUG - size_t malloced = 0; - size_t freed = 0; - size_t collisions = 0; + size_t malloced = 0; + size_t freed = 0; + size_t collisions = 0; #endif /* NDEBUG */ private: diff --git a/src/admesh/normals.cpp b/src/admesh/normals.cpp index f20d9e68e4..464f9e6d8e 100644 --- a/src/admesh/normals.cpp +++ b/src/admesh/normals.cpp @@ -27,19 +27,16 @@ #include "stl.h" -static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); - static void reverse_facet(stl_file *stl, int facet_num) { - stl->stats.facets_reversed += 1; + ++ stl->stats.facets_reversed; int neighbor[3] = { stl->neighbors_start[facet_num].neighbor[0], stl->neighbors_start[facet_num].neighbor[1], stl->neighbors_start[facet_num].neighbor[2] }; int vnot[3] = { stl->neighbors_start[facet_num].which_vertex_not[0], stl->neighbors_start[facet_num].which_vertex_not[1], stl->neighbors_start[facet_num].which_vertex_not[2] }; // reverse the facet stl_vertex tmp_vertex = stl->facet_start[facet_num].vertex[0]; - stl->facet_start[facet_num].vertex[0] = - stl->facet_start[facet_num].vertex[1]; + stl->facet_start[facet_num].vertex[0] = stl->facet_start[facet_num].vertex[1]; stl->facet_start[facet_num].vertex[1] = tmp_vertex; // fix the vnots of the neighboring facets @@ -64,187 +61,164 @@ static void reverse_facet(stl_file *stl, int facet_num) stl->neighbors_start[facet_num].which_vertex_not[2] = (stl->neighbors_start[facet_num].which_vertex_not[2] + 3) % 6; } -void stl_fix_normal_directions(stl_file *stl) +// Returns true if the normal was flipped. +static bool check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag) { - /* int edge_num;*/ - /* int vnot;*/ - int checked = 0; - int facet_num; - /* int next_facet;*/ - int i; - int j; - struct stl_normal { - int facet_num; - struct stl_normal *next; - }; - struct stl_normal *head; - struct stl_normal *tail; - struct stl_normal *newn; - struct stl_normal *temp; + stl_facet *facet = &stl->facet_start[facet_num]; - int reversed_count = 0; - int id; - int force_exit = 0; + stl_normal normal; + stl_calculate_normal(normal, facet); + stl_normalize_vector(normal); + stl_normal normal_dif = (normal - facet->normal).cwiseAbs(); - // this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (stl->stats.number_of_facets == 0) return; + const float eps = 0.001f; + if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { + // Normal is within tolerance. It is not really necessary to change the values here, but just for consistency, I will. + facet->normal = normal; + return false; + } - /* Initialize linked list. */ - head = new stl_normal; - tail = new stl_normal; - head->next = tail; - tail->next = tail; + stl_normal test_norm = facet->normal; + stl_normalize_vector(test_norm); + normal_dif = (normal - test_norm).cwiseAbs(); + if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { + // The normal is not within tolerance, but direction is OK. + if (normal_fix_flag) { + facet->normal = normal; + ++ stl->stats.normals_fixed; + } + return false; + } - /* Initialize list that keeps track of already fixed facets. */ - std::vector norm_sw(stl->stats.number_of_facets, 0); - /* Initialize list that keeps track of reversed facets. */ - std::vector reversed_ids(stl->stats.number_of_facets, 0); - - facet_num = 0; - /* If normal vector is not within tolerance and backwards: - Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances - of it being wrong randomly are low if most of the triangles are right: */ - if (stl_check_normal_vector(stl, 0, 0) == 2) { - reverse_facet(stl, 0); - reversed_ids[reversed_count++] = 0; - } - - /* Say that we've fixed this facet: */ - norm_sw[facet_num] = 1; - checked++; - - for(;;) { - /* Add neighbors_to_list. - Add unconnected neighbors to the list:a */ - for(j = 0; j < 3; j++) { - /* Reverse the neighboring facets if necessary. */ - if(stl->neighbors_start[facet_num].which_vertex_not[j] > 2) { - /* If the facet has a neighbor that is -1, it means that edge isn't shared by another facet */ - if(stl->neighbors_start[facet_num].neighbor[j] != -1) { - if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] == 1) { - /* trying to modify a facet already marked as fixed, revert all changes made until now and exit (fixes: #716, #574, #413, #269, #262, #259, #230, #228, #206) */ - for (id = reversed_count - 1; id >= 0; --id) { - reverse_facet(stl, reversed_ids[id]); - } - force_exit = 1; - break; - } else { - reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]); - reversed_ids[reversed_count++] = stl->neighbors_start[facet_num].neighbor[j]; - } - } - } - /* If this edge of the facet is connected: */ - if(stl->neighbors_start[facet_num].neighbor[j] != -1) { - /* If we haven't fixed this facet yet, add it to the list: */ - if(norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) { - /* Add node to beginning of list. */ - newn = new stl_normal; - newn->facet_num = stl->neighbors_start[facet_num].neighbor[j]; - newn->next = head->next; - head->next = newn; - } - } - } - - /* an error occourred, quit the for loop and exit */ - if (force_exit) break; - - /* Get next facet to fix from top of list. */ - if(head->next != tail) { - facet_num = head->next->facet_num; - if(norm_sw[facet_num] != 1) { /* If facet is in list mutiple times */ - norm_sw[facet_num] = 1; /* Record this one as being fixed. */ - checked++; - } - temp = head->next; /* Delete this facet from the list. */ - head->next = head->next->next; - delete temp; - } else { /* if we ran out of facets to fix: */ - /* All of the facets in this part have been fixed. */ - stl->stats.number_of_parts += 1; - if(checked >= stl->stats.number_of_facets) { - /* All of the facets have been checked. Bail out. */ - break; - } else { - /* There is another part here. Find it and continue. */ - for(i = 0; i < stl->stats.number_of_facets; i++) { - if(norm_sw[i] == 0) { - /* This is the first facet of the next part. */ - facet_num = i; - if(stl_check_normal_vector(stl, i, 0) == 2) { - reverse_facet(stl, i); - reversed_ids[reversed_count++] = i; - } - - norm_sw[facet_num] = 1; - checked++; - break; - } - } - } - } - } - delete head; - delete tail; + test_norm *= -1.f; + normal_dif = (normal - test_norm).cwiseAbs(); + if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { + // The normal is not within tolerance and backwards. + if (normal_fix_flag) { + facet->normal = normal; + ++ stl->stats.normals_fixed; + } + return true; + } + if (normal_fix_flag) { + facet->normal = normal; + ++ stl->stats.normals_fixed; + } + // Status is unknown. + return false; } -static int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag) +void stl_fix_normal_directions(stl_file *stl) { - /* Returns 0 if the normal is within tolerance */ - /* Returns 1 if the normal is not within tolerance, but direction is OK */ - /* Returns 2 if the normal is not within tolerance and backwards */ - /* Returns 4 if the status is unknown. */ + // This may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209 + if (stl->stats.number_of_facets == 0) + return; - stl_facet *facet; + struct stl_normal { + int facet_num; + stl_normal *next; + }; - facet = &stl->facet_start[facet_num]; + // Initialize linked list. + stl_normal *head = new stl_normal; + stl_normal *tail = new stl_normal; + head->next = tail; + tail->next = tail; - stl_normal normal; - stl_calculate_normal(normal, facet); - stl_normalize_vector(normal); - stl_normal normal_dif = (normal - facet->normal).cwiseAbs(); + // Initialize list that keeps track of already fixed facets. + std::vector norm_sw(stl->stats.number_of_facets, 0); + // Initialize list that keeps track of reversed facets. + std::vector reversed_ids(stl->stats.number_of_facets, 0); - const float eps = 0.001f; - if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { - /* It is not really necessary to change the values here */ - /* but just for consistency, I will. */ - facet->normal = normal; - return 0; - } + int facet_num = 0; + int reversed_count = 0; + // If normal vector is not within tolerance and backwards: + // Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances + // of it being wrong randomly are low if most of the triangles are right: + if (check_normal_vector(stl, 0, 0)) { + reverse_facet(stl, 0); + reversed_ids[reversed_count ++] = 0; + } - stl_normal test_norm = facet->normal; - stl_normalize_vector(test_norm); - normal_dif = (normal - test_norm).cwiseAbs(); - if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { - if(normal_fix_flag) { - facet->normal = normal; - stl->stats.normals_fixed += 1; - } - return 1; - } + // Say that we've fixed this facet: + norm_sw[facet_num] = 1; + int checked = 1; - test_norm *= -1.f; - normal_dif = (normal - test_norm).cwiseAbs(); - if (normal_dif(0) < eps && normal_dif(1) < eps && normal_dif(2) < eps) { - // Facet is backwards. - if(normal_fix_flag) { - facet->normal = normal; - stl->stats.normals_fixed += 1; - } - return 2; - } - if(normal_fix_flag) { - facet->normal = normal; - stl->stats.normals_fixed += 1; - } - return 4; + for (;;) { + // Add neighbors_to_list. Add unconnected neighbors to the list. + bool force_exit = false; + for (int j = 0; j < 3; ++ j) { + // Reverse the neighboring facets if necessary. + if (stl->neighbors_start[facet_num].which_vertex_not[j] > 2) { + // If the facet has a neighbor that is -1, it means that edge isn't shared by another facet + if (stl->neighbors_start[facet_num].neighbor[j] != -1) { + if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] == 1) { + // trying to modify a facet already marked as fixed, revert all changes made until now and exit (fixes: #716, #574, #413, #269, #262, #259, #230, #228, #206) + for (int id = reversed_count - 1; id >= 0; -- id) + reverse_facet(stl, reversed_ids[id]); + force_exit = true; + break; + } + reverse_facet(stl, stl->neighbors_start[facet_num].neighbor[j]); + reversed_ids[reversed_count ++] = stl->neighbors_start[facet_num].neighbor[j]; + } + } + // If this edge of the facet is connected: + if (stl->neighbors_start[facet_num].neighbor[j] != -1) { + // If we haven't fixed this facet yet, add it to the list: + if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) { + // Add node to beginning of list. + stl_normal *newn = new stl_normal; + newn->facet_num = stl->neighbors_start[facet_num].neighbor[j]; + newn->next = head->next; + head->next = newn; + } + } + } + + // an error occourred, quit the for loop and exit + if (force_exit) + break; + + // Get next facet to fix from top of list. + if (head->next != tail) { + facet_num = head->next->facet_num; + if (norm_sw[facet_num] != 1) { // If facet is in list mutiple times + norm_sw[facet_num] = 1; // Record this one as being fixed. + ++ checked; + } + stl_normal *temp = head->next; // Delete this facet from the list. + head->next = head->next->next; + delete temp; + } else { // If we ran out of facets to fix: All of the facets in this part have been fixed. + ++ stl->stats.number_of_parts; + if (checked >= stl->stats.number_of_facets) + // All of the facets have been checked. Bail out. + break; + // There is another part here. Find it and continue. + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + if (norm_sw[i] == 0) { + // This is the first facet of the next part. + facet_num = i; + if (check_normal_vector(stl, i, 0)) { + reverse_facet(stl, i); + reversed_ids[reversed_count++] = i; + } + norm_sw[facet_num] = 1; + ++ checked; + break; + } + } + } + + delete head; + delete tail; } void stl_fix_normal_values(stl_file *stl) { for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) - stl_check_normal_vector(stl, i, 1); + check_normal_vector(stl, i, 1); } void stl_reverse_all_facets(stl_file *stl) diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index fe6d5e6564..78179e863a 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -33,7 +33,7 @@ void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its) { // 3 indices to vertex per face - its.indices.assign(stl->stats.number_of_facets, v_indices_struct()); + its.indices.assign(stl->stats.number_of_facets, stl_triangle_vertex_indices(-1, -1, -1)); // Shared vertices (3D coordinates) its.vertices.clear(); its.vertices.reserve(stl->stats.number_of_facets / 2); @@ -46,7 +46,7 @@ void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its) for (uint32_t facet_idx = 0; facet_idx < stl->stats.number_of_facets; ++ facet_idx) { for (int j = 0; j < 3; ++ j) { - if (its.indices[facet_idx].vertex[j] != -1) + if (its.indices[facet_idx][j] != -1) // Shared vertex was already assigned. continue; // Create a new shared vertex. @@ -84,7 +84,7 @@ void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its) next_edge = pivot_vertex; } } - its.indices[facet_in_fan_idx].vertex[pivot_vertex] = its.vertices.size() - 1; + its.indices[facet_in_fan_idx][pivot_vertex] = its.vertices.size() - 1; fan_traversal_facet_visited[facet_in_fan_idx] = fan_traversal_stamp; // next_edge is an index of the starting vertex of the edge, not an index of the opposite vertex to the edge! @@ -139,7 +139,7 @@ bool its_write_off(const indexed_triangle_set &its, const char *file) for (int i = 0; i < its.vertices.size(); ++ i) fprintf(fp, "\t%f %f %f\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); for (uint32_t i = 0; i < its.indices.size(); ++ i) - fprintf(fp, "\t3 %d %d %d\n", its.indices[i].vertex[0], its.indices[i].vertex[1], its.indices[i].vertex[2]); + fprintf(fp, "\t3 %d %d %d\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]); fclose(fp); return true; } @@ -177,8 +177,8 @@ bool its_write_vrml(const indexed_triangle_set &its, const char *file) fprintf(fp, "\t\t\tcoordIndex [\n"); for (size_t i = 0; i + 1 < its.indices.size(); ++ i) - fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", its.indices[i].vertex[0], its.indices[i].vertex[1], its.indices[i].vertex[2]); - fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", its.indices[i].vertex[0], its.indices[i].vertex[1], its.indices[i].vertex[2]); + fprintf(fp, "\t\t\t\t%d, %d, %d, -1,\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]); + fprintf(fp, "\t\t\t\t%d, %d, %d, -1]\n", its.indices[i][0], its.indices[i][1], its.indices[i][2]); fprintf(fp, "\t\t}\n"); fprintf(fp, "\t}\n"); fprintf(fp, "}\n"); @@ -198,7 +198,7 @@ bool its_write_obj(const indexed_triangle_set &its, const char *file) for (size_t i = 0; i < its.vertices.size(); ++ i) fprintf(fp, "v %f %f %f\n", its.vertices[i](0), its.vertices[i](1), its.vertices[i](2)); for (size_t i = 0; i < its.indices.size(); ++ i) - fprintf(fp, "f %d %d %d\n", its.indices[i].vertex[0]+1, its.indices[i].vertex[1]+1, its.indices[i].vertex[2]+1); + fprintf(fp, "f %d %d %d\n", its.indices[i][0]+1, its.indices[i][1]+1, its.indices[i][2]+1); fclose(fp); return true; } @@ -220,7 +220,7 @@ bool stl_validate(const stl_file *stl, const indexed_triangle_set &its) // Verify validity of neighborship data. for (int facet_idx = 0; facet_idx < (int)stl->stats.number_of_facets; ++ facet_idx) { const stl_neighbors &nbr = stl->neighbors_start[facet_idx]; - const int *vertices = (its.indices.empty()) ? nullptr : its.indices[facet_idx].vertex; + const int *vertices = (its.indices.empty()) ? nullptr : its.indices[facet_idx]; for (int nbr_idx = 0; nbr_idx < 3; ++ nbr_idx) { int nbr_face = stl->neighbors_start[facet_idx].neighbor[nbr_idx]; assert(nbr_face < (int)stl->stats.number_of_facets); @@ -237,10 +237,10 @@ bool stl_validate(const stl_file *stl, const indexed_triangle_set &its) // Has shared vertices. if (nbr_vnot < 3) { // Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are correctly oriented. - assert((its.indices[nbr_face].vertex[(nbr_vnot + 1) % 3] == vertices[(nbr_idx + 1) % 3] && its.indices[nbr_face].vertex[(nbr_vnot + 2) % 3] == vertices[nbr_idx])); + assert((its.indices[nbr_face][(nbr_vnot + 1) % 3] == vertices[(nbr_idx + 1) % 3] && its.indices[nbr_face][(nbr_vnot + 2) % 3] == vertices[nbr_idx])); } else { // Faces facet_idx and nbr_face share two vertices accross the common edge. Faces are incorrectly oriented, one of them is flipped. - assert((its.indices[nbr_face].vertex[(nbr_vnot + 2) % 3] == vertices[(nbr_idx + 1) % 3] && its.indices[nbr_face].vertex[(nbr_vnot + 1) % 3] == vertices[nbr_idx])); + assert((its.indices[nbr_face][(nbr_vnot + 2) % 3] == vertices[(nbr_idx + 1) % 3] && its.indices[nbr_face][(nbr_vnot + 1) % 3] == vertices[nbr_idx])); } } } diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 500d6bfdb1..fce23eb3f9 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -41,6 +41,7 @@ typedef Eigen::Matrix stl_vertex; typedef Eigen::Matrix stl_normal; +typedef Eigen::Matrix stl_triangle_vertex_indices; static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect"); static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect"); @@ -68,12 +69,6 @@ static_assert(sizeof(stl_facet) >= SIZEOF_STL_FACET, "size of stl_facet incorrec typedef enum {binary, ascii, inmemory} stl_type; -struct stl_edge { - stl_vertex p1; - stl_vertex p2; - int facet_number; -}; - struct stl_neighbors { stl_neighbors() { reset(); } void reset() { @@ -93,12 +88,6 @@ struct stl_neighbors { char which_vertex_not[3]; }; -struct v_indices_struct { - // -1 means no vertex index has been assigned yet - v_indices_struct() { vertex[0] = -1; vertex[1] = -1; vertex[2] = -1; } - int vertex[3]; -}; - struct stl_stats { char header[81]; stl_type type; @@ -137,8 +126,8 @@ struct stl_file { struct indexed_triangle_set { void clear() { indices.clear(); vertices.clear(); } - std::vector indices; - std::vector vertices; + std::vector indices; + std::vector vertices; }; extern bool stl_open(stl_file *stl, const char *file); diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp index dc4e4a7db3..7e181716c6 100644 --- a/src/admesh/stl_io.cpp +++ b/src/admesh/stl_io.cpp @@ -22,94 +22,55 @@ #include #include -#include "stl.h" #include #include #include -#if !defined(SEEK_SET) -#define SEEK_SET 0 -#define SEEK_CUR 1 -#define SEEK_END 2 -#endif +#include "stl.h" void stl_stats_out(stl_file *stl, FILE *file, char *input_file) { - /* this is here for Slic3r, without our config.h - it won't use this part of the code anyway */ + // This is here for Slic3r, without our config.h it won't use this part of the code anyway. #ifndef VERSION #define VERSION "unknown" #endif - fprintf(file, "\n\ -================= Results produced by ADMesh version " VERSION " ================\n"); - fprintf(file, "\ -Input file : %s\n", input_file); - if(stl->stats.type == binary) { - fprintf(file, "\ -File type : Binary STL file\n"); - } else { - fprintf(file, "\ -File type : ASCII STL file\n"); - } - fprintf(file, "\ -Header : %s\n", stl->stats.header); - fprintf(file, "============== Size ==============\n"); - fprintf(file, "Min X = % f, Max X = % f\n", - stl->stats.min(0), stl->stats.max(0)); - fprintf(file, "Min Y = % f, Max Y = % f\n", - stl->stats.min(1), stl->stats.max(1)); - fprintf(file, "Min Z = % f, Max Z = % f\n", - stl->stats.min(2), stl->stats.max(2)); - - fprintf(file, "\ -========= Facet Status ========== Original ============ Final ====\n"); - fprintf(file, "\ -Number of facets : %5d %5d\n", - stl->stats.original_num_facets, stl->stats.number_of_facets); - fprintf(file, "\ -Facets with 1 disconnected edge : %5d %5d\n", - stl->stats.facets_w_1_bad_edge, stl->stats.connected_facets_2_edge - - stl->stats.connected_facets_3_edge); - fprintf(file, "\ -Facets with 2 disconnected edges : %5d %5d\n", - stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge - - stl->stats.connected_facets_2_edge); - fprintf(file, "\ -Facets with 3 disconnected edges : %5d %5d\n", - stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - - stl->stats.connected_facets_1_edge); - fprintf(file, "\ -Total disconnected facets : %5d %5d\n", - stl->stats.facets_w_1_bad_edge + stl->stats.facets_w_2_bad_edge + - stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - - stl->stats.connected_facets_3_edge); - - fprintf(file, - "=== Processing Statistics === ===== Other Statistics =====\n"); - fprintf(file, "\ -Number of parts : %5d Volume : % f\n", - stl->stats.number_of_parts, stl->stats.volume); - fprintf(file, "\ -Degenerate facets : %5d\n", stl->stats.degenerate_facets); - fprintf(file, "\ -Edges fixed : %5d\n", stl->stats.edges_fixed); - fprintf(file, "\ -Facets removed : %5d\n", stl->stats.facets_removed); - fprintf(file, "\ -Facets added : %5d\n", stl->stats.facets_added); - fprintf(file, "\ -Facets reversed : %5d\n", stl->stats.facets_reversed); - fprintf(file, "\ -Backwards edges : %5d\n", stl->stats.backwards_edges); - fprintf(file, "\ -Normals fixed : %5d\n", stl->stats.normals_fixed); + fprintf(file, "\n================= Results produced by ADMesh version " VERSION " ================\n"); + fprintf(file, "Input file : %s\n", input_file); + if (stl->stats.type == binary) + fprintf(file, "File type : Binary STL file\n"); + else + fprintf(file, "File type : ASCII STL file\n"); + fprintf(file, "Header : %s\n", stl->stats.header); + fprintf(file, "============== Size ==============\n"); + fprintf(file, "Min X = % f, Max X = % f\n", stl->stats.min(0), stl->stats.max(0)); + fprintf(file, "Min Y = % f, Max Y = % f\n", stl->stats.min(1), stl->stats.max(1)); + fprintf(file, "Min Z = % f, Max Z = % f\n", stl->stats.min(2), stl->stats.max(2)); + fprintf(file, "========= Facet Status ========== Original ============ Final ====\n"); + fprintf(file, "Number of facets : %5d %5d\n", stl->stats.original_num_facets, stl->stats.number_of_facets); + fprintf(file, "Facets with 1 disconnected edge : %5d %5d\n", + stl->stats.facets_w_1_bad_edge, stl->stats.connected_facets_2_edge - stl->stats.connected_facets_3_edge); + fprintf(file, "Facets with 2 disconnected edges : %5d %5d\n", + stl->stats.facets_w_2_bad_edge, stl->stats.connected_facets_1_edge - stl->stats.connected_facets_2_edge); + fprintf(file, "Facets with 3 disconnected edges : %5d %5d\n", + stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_1_edge); + fprintf(file, "Total disconnected facets : %5d %5d\n", + stl->stats.facets_w_1_bad_edge + stl->stats.facets_w_2_bad_edge + stl->stats.facets_w_3_bad_edge, stl->stats.number_of_facets - stl->stats.connected_facets_3_edge); + fprintf(file, "=== Processing Statistics === ===== Other Statistics =====\n"); + fprintf(file, "Number of parts : %5d Volume : %f\n", stl->stats.number_of_parts, stl->stats.volume); + fprintf(file, "Degenerate facets : %5d\n", stl->stats.degenerate_facets); + fprintf(file, "Edges fixed : %5d\n", stl->stats.edges_fixed); + fprintf(file, "Facets removed : %5d\n", stl->stats.facets_removed); + fprintf(file, "Facets added : %5d\n", stl->stats.facets_added); + fprintf(file, "Facets reversed : %5d\n", stl->stats.facets_reversed); + fprintf(file, "Backwards edges : %5d\n", stl->stats.backwards_edges); + fprintf(file, "Normals fixed : %5d\n", stl->stats.normals_fixed); } bool stl_write_ascii(stl_file *stl, const char *file, const char *label) { FILE *fp = boost::nowide::fopen(file, "w"); - if (fp == NULL) { + if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "stl_write_ascii: Couldn't open " << file << " for writing"; return false; } @@ -117,19 +78,11 @@ bool stl_write_ascii(stl_file *stl, const char *file, const char *label) fprintf(fp, "solid %s\n", label); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { - fprintf(fp, " facet normal % .8E % .8E % .8E\n", - stl->facet_start[i].normal(0), stl->facet_start[i].normal(1), - stl->facet_start[i].normal(2)); + fprintf(fp, " facet normal % .8E % .8E % .8E\n", stl->facet_start[i].normal(0), stl->facet_start[i].normal(1), stl->facet_start[i].normal(2)); fprintf(fp, " outer loop\n"); - fprintf(fp, " vertex % .8E % .8E % .8E\n", - stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), - stl->facet_start[i].vertex[0](2)); - fprintf(fp, " vertex % .8E % .8E % .8E\n", - stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), - stl->facet_start[i].vertex[1](2)); - fprintf(fp, " vertex % .8E % .8E % .8E\n", - stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2)); + fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2)); + fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2)); + fprintf(fp, " vertex % .8E % .8E % .8E\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2)); fprintf(fp, " endloop\n"); fprintf(fp, " endfacet\n"); } @@ -142,7 +95,7 @@ bool stl_write_ascii(stl_file *stl, const char *file, const char *label) bool stl_print_neighbors(stl_file *stl, char *file) { FILE *fp = boost::nowide::fopen(file, "w"); - if (fp == NULL) { + if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "stl_print_neighbors: Couldn't open " << file << " for writing"; return false; } @@ -175,7 +128,7 @@ void stl_internal_reverse_quads(char *buf, size_t cnt) bool stl_write_binary(stl_file *stl, const char *file, const char *label) { FILE *fp = boost::nowide::fopen(file, "wb"); - if (fp == NULL) { + if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "stl_write_binary: Couldn't open " << file << " for writing"; return false; } @@ -184,6 +137,9 @@ bool stl_write_binary(stl_file *stl, const char *file, const char *label) for (size_t i = strlen(label); i < LABEL_SIZE; ++ i) putc(0, fp); +#if !defined(SEEK_SET) + #define SEEK_SET 0 +#endif fseek(fp, LABEL_SIZE, SEEK_SET); #ifdef BOOST_LITTLE_ENDIAN fwrite(&stl->stats.number_of_facets, 4, 1, fp); @@ -242,7 +198,7 @@ bool stl_write_quad_object(stl_file *stl, char *file) stl_vertex color; FILE *fp = boost::nowide::fopen(file, "w"); - if (fp == NULL) { + if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing"; return false; } @@ -255,22 +211,10 @@ bool stl_write_quad_object(stl_file *stl, char *file) case 2: color = uncon_2_color; break; default: color = uncon_3_color; } - fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", - stl->facet_start[i].vertex[0](0), - stl->facet_start[i].vertex[0](1), - stl->facet_start[i].vertex[0](2), color(0), color(1), color(2)); - fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", - stl->facet_start[i].vertex[1](0), - stl->facet_start[i].vertex[1](1), - stl->facet_start[i].vertex[1](2), color(0), color(1), color(2)); - fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", - stl->facet_start[i].vertex[2](0), - stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); - fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", - stl->facet_start[i].vertex[2](0), - stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); + fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2), color(0), color(1), color(2)); + fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2), color(0), color(1), color(2)); + fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); + fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2), color(0), color(1), color(2)); } fclose(fp); return true; @@ -279,7 +223,7 @@ bool stl_write_quad_object(stl_file *stl, char *file) bool stl_write_dxf(stl_file *stl, const char *file, char *label) { FILE *fp = boost::nowide::fopen(file, "w"); - if (fp == NULL) { + if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "stl_write_quad_object: Couldn't open " << file << " for writing"; return false; } @@ -292,20 +236,12 @@ bool stl_write_dxf(stl_file *stl, const char *file, char *label) fprintf(fp, "0\nSECTION\n2\nENTITIES\n"); - for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { fprintf(fp, "0\n3DFACE\n8\n0\n"); - fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", - stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), - stl->facet_start[i].vertex[0](2)); - fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", - stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), - stl->facet_start[i].vertex[1](2)); - fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", - stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2)); - fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", - stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), - stl->facet_start[i].vertex[2](2)); + fprintf(fp, "10\n%f\n20\n%f\n30\n%f\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2)); + fprintf(fp, "11\n%f\n21\n%f\n31\n%f\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2)); + fprintf(fp, "12\n%f\n22\n%f\n32\n%f\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2)); + fprintf(fp, "13\n%f\n23\n%f\n33\n%f\n", stl->facet_start[i].vertex[2](0), stl->facet_start[i].vertex[2](1), stl->facet_start[i].vertex[2](2)); } fprintf(fp, "0\nENDSEC\n0\nEOF\n"); diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index bb135db95b..6fff8a8ed5 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -29,16 +29,17 @@ #include "stl.h" -static void stl_rotate(float *x, float *y, const double c, const double s); -static float get_area(stl_facet *facet); -static float get_volume(stl_file *stl); - void stl_verify_neighbors(stl_file *stl) { stl->stats.backwards_edges = 0; for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { for (int j = 0; j < 3; ++ j) { + struct stl_edge { + stl_vertex p1; + stl_vertex p2; + int facet_number; + }; stl_edge edge_a; edge_a.p1 = stl->facet_start[i].vertex[j]; edge_a.p2 = stl->facet_start[i].vertex[(j + 1) % 3]; @@ -67,164 +68,140 @@ void stl_verify_neighbors(stl_file *stl) void stl_translate(stl_file *stl, float x, float y, float z) { - stl_vertex new_min(x, y, z); - stl_vertex shift = new_min - stl->stats.min; - for (int i = 0; i < stl->stats.number_of_facets; ++ i) - for (int j = 0; j < 3; ++ j) - stl->facet_start[i].vertex[j] += shift; - stl->stats.min = new_min; - stl->stats.max += shift; + stl_vertex new_min(x, y, z); + stl_vertex shift = new_min - stl->stats.min; + for (int i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j] += shift; + stl->stats.min = new_min; + stl->stats.max += shift; } /* Translates the stl by x,y,z, relatively from wherever it is currently */ void stl_translate_relative(stl_file *stl, float x, float y, float z) { - stl_vertex shift(x, y, z); - for (int i = 0; i < stl->stats.number_of_facets; ++ i) - for (int j = 0; j < 3; ++ j) - stl->facet_start[i].vertex[j] += shift; - stl->stats.min += shift; - stl->stats.max += shift; + stl_vertex shift(x, y, z); + for (int i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j] += shift; + stl->stats.min += shift; + stl->stats.max += shift; } void stl_scale_versor(stl_file *stl, const stl_vertex &versor) { - // Scale extents. - auto s = versor.array(); - stl->stats.min.array() *= s; - stl->stats.max.array() *= s; - // Scale size. - stl->stats.size.array() *= s; - // Scale volume. - if (stl->stats.volume > 0.0) - stl->stats.volume *= versor(0) * versor(1) * versor(2); - // Scale the mesh. - for (int i = 0; i < stl->stats.number_of_facets; ++ i) - for (int j = 0; j < 3; ++ j) - stl->facet_start[i].vertex[j].array() *= s; + // Scale extents. + auto s = versor.array(); + stl->stats.min.array() *= s; + stl->stats.max.array() *= s; + // Scale size. + stl->stats.size.array() *= s; + // Scale volume. + if (stl->stats.volume > 0.0) + stl->stats.volume *= versor(0) * versor(1) * versor(2); + // Scale the mesh. + for (int i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j].array() *= s; } static void calculate_normals(stl_file *stl) { - stl_normal normal; - for(uint32_t i = 0; i < stl->stats.number_of_facets; i++) { - stl_calculate_normal(normal, &stl->facet_start[i]); - stl_normalize_vector(normal); - stl->facet_start[i].normal = normal; - } + stl_normal normal; + for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { + stl_calculate_normal(normal, &stl->facet_start[i]); + stl_normalize_vector(normal); + stl->facet_start[i].normal = normal; + } } -void -stl_rotate_x(stl_file *stl, float angle) { - int i; - int j; - double radian_angle = (angle / 180.0) * M_PI; - double c = cos(radian_angle); - double s = sin(radian_angle); - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl_rotate(&stl->facet_start[i].vertex[j](1), - &stl->facet_start[i].vertex[j](2), c, s); - } - } - stl_get_size(stl); - calculate_normals(stl); +static void rotate_point_2d(float *x, float *y, const double c, const double s) +{ + double xold = *x; + double yold = *y; + *x = float(c * xold - s * yold); + *y = float(s * xold + c * yold); } -void -stl_rotate_y(stl_file *stl, float angle) { - int i; - int j; - double radian_angle = (angle / 180.0) * M_PI; - double c = cos(radian_angle); - double s = sin(radian_angle); - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl_rotate(&stl->facet_start[i].vertex[j](2), - &stl->facet_start[i].vertex[j](0), c, s); - } - } - stl_get_size(stl); - calculate_normals(stl); +void stl_rotate_x(stl_file *stl, float angle) +{ + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + rotate_point_2d(&stl->facet_start[i].vertex[j](1), &stl->facet_start[i].vertex[j](2), c, s); + stl_get_size(stl); + calculate_normals(stl); } -void -stl_rotate_z(stl_file *stl, float angle) { - int i; - int j; - double radian_angle = (angle / 180.0) * M_PI; - double c = cos(radian_angle); - double s = sin(radian_angle); - - for(i = 0; i < stl->stats.number_of_facets; i++) { - for(j = 0; j < 3; j++) { - stl_rotate(&stl->facet_start[i].vertex[j](0), - &stl->facet_start[i].vertex[j](1), c, s); - } - } - stl_get_size(stl); - calculate_normals(stl); +void stl_rotate_y(stl_file *stl, float angle) +{ + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + rotate_point_2d(&stl->facet_start[i].vertex[j](2), &stl->facet_start[i].vertex[j](0), c, s); + stl_get_size(stl); + calculate_normals(stl); } - - -static void -stl_rotate(float *x, float *y, const double c, const double s) { - double xold = *x; - double yold = *y; - *x = float(c * xold - s * yold); - *y = float(s * xold + c * yold); +void stl_rotate_z(stl_file *stl, float angle) +{ + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + rotate_point_2d(&stl->facet_start[i].vertex[j](0), &stl->facet_start[i].vertex[j](1), c, s); + stl_get_size(stl); + calculate_normals(stl); } void stl_get_size(stl_file *stl) { - if (stl->stats.number_of_facets == 0) - return; - stl->stats.min = stl->facet_start[0].vertex[0]; - stl->stats.max = stl->stats.min; - for (int i = 0; i < stl->stats.number_of_facets; ++ i) { - const stl_facet &face = stl->facet_start[i]; - for (int j = 0; j < 3; ++ j) { - stl->stats.min = stl->stats.min.cwiseMin(face.vertex[j]); - stl->stats.max = stl->stats.max.cwiseMax(face.vertex[j]); - } - } - stl->stats.size = stl->stats.max - stl->stats.min; - stl->stats.bounding_diameter = stl->stats.size.norm(); + if (stl->stats.number_of_facets == 0) + return; + stl->stats.min = stl->facet_start[0].vertex[0]; + stl->stats.max = stl->stats.min; + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + const stl_facet &face = stl->facet_start[i]; + for (int j = 0; j < 3; ++ j) { + stl->stats.min = stl->stats.min.cwiseMin(face.vertex[j]); + stl->stats.max = stl->stats.max.cwiseMax(face.vertex[j]); + } + } + stl->stats.size = stl->stats.max - stl->stats.min; + stl->stats.bounding_diameter = stl->stats.size.norm(); } void stl_mirror_xy(stl_file *stl) { - for(int i = 0; i < stl->stats.number_of_facets; i++) { - for(int j = 0; j < 3; j++) { - stl->facet_start[i].vertex[j](2) *= -1.0; - } - } - float temp_size = stl->stats.min(2); - stl->stats.min(2) = stl->stats.max(2); - stl->stats.max(2) = temp_size; - stl->stats.min(2) *= -1.0; - stl->stats.max(2) *= -1.0; - stl_reverse_all_facets(stl); - stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; ++ j) + stl->facet_start[i].vertex[j](2) *= -1.0; + float temp_size = stl->stats.min(2); + stl->stats.min(2) = stl->stats.max(2); + stl->stats.max(2) = temp_size; + stl->stats.min(2) *= -1.0; + stl->stats.max(2) *= -1.0; + stl_reverse_all_facets(stl); + stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } void stl_mirror_yz(stl_file *stl) { - for (int i = 0; i < stl->stats.number_of_facets; i++) { - for (int j = 0; j < 3; j++) { - stl->facet_start[i].vertex[j](0) *= -1.0; - } - } - float temp_size = stl->stats.min(0); - stl->stats.min(0) = stl->stats.max(0); - stl->stats.max(0) = temp_size; - stl->stats.min(0) *= -1.0; - stl->stats.max(0) *= -1.0; - stl_reverse_all_facets(stl); - stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) + for (int j = 0; j < 3; j++) + stl->facet_start[i].vertex[j](0) *= -1.0; + float temp_size = stl->stats.min(0); + stl->stats.min(0) = stl->stats.max(0); + stl->stats.max(0) = temp_size; + stl->stats.min(0) *= -1.0; + stl->stats.max(0) *= -1.0; + stl_reverse_all_facets(stl); + stl->stats.facets_reversed -= stl->stats.number_of_facets; /* for not altering stats */ } void stl_mirror_xz(stl_file *stl) @@ -241,55 +218,55 @@ void stl_mirror_xz(stl_file *stl) stl->stats.facets_reversed -= stl->stats.number_of_facets; // for not altering stats } +static float get_area(stl_facet *facet) +{ + /* cast to double before calculating cross product because large coordinates + can result in overflowing product + (bad area is responsible for bad volume and bad facets reversal) */ + double cross[3][3]; + for (int i = 0; i < 3; i++) { + cross[i][0]=(((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](2)) - + ((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](1))); + cross[i][1]=(((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](0)) - + ((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](2))); + cross[i][2]=(((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](1)) - + ((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](0))); + } + + stl_normal sum; + sum(0) = cross[0][0] + cross[1][0] + cross[2][0]; + sum(1) = cross[0][1] + cross[1][1] + cross[2][1]; + sum(2) = cross[0][2] + cross[1][2] + cross[2][2]; + + // This should already be done. But just in case, let's do it again. + //FIXME this is questionable. the "sum" normal should be accurate, while the normal "n" may be calculated with a low accuracy. + stl_normal n; + stl_calculate_normal(n, facet); + stl_normalize_vector(n); + return 0.5f * n.dot(sum); +} + static float get_volume(stl_file *stl) { - // Choose a point, any point as the reference. - stl_vertex p0 = stl->facet_start[0].vertex[0]; - float volume = 0.f; - for(uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { - // Do dot product to get distance from point to plane. - float height = stl->facet_start[i].normal.dot(stl->facet_start[i].vertex[0] - p0); - float area = get_area(&stl->facet_start[i]); - volume += (area * height) / 3.0f; - } - return volume; + // Choose a point, any point as the reference. + stl_vertex p0 = stl->facet_start[0].vertex[0]; + float volume = 0.f; + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { + // Do dot product to get distance from point to plane. + float height = stl->facet_start[i].normal.dot(stl->facet_start[i].vertex[0] - p0); + float area = get_area(&stl->facet_start[i]); + volume += (area * height) / 3.0f; + } + return volume; } void stl_calculate_volume(stl_file *stl) { - stl->stats.volume = get_volume(stl); - if(stl->stats.volume < 0.0) { - stl_reverse_all_facets(stl); - stl->stats.volume = -stl->stats.volume; - } -} - -static float get_area(stl_facet *facet) -{ - /* cast to double before calculating cross product because large coordinates - can result in overflowing product - (bad area is responsible for bad volume and bad facets reversal) */ - double cross[3][3]; - for (int i = 0; i < 3; i++) { - cross[i][0]=(((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](2)) - - ((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](1))); - cross[i][1]=(((double)facet->vertex[i](2) * (double)facet->vertex[(i + 1) % 3](0)) - - ((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](2))); - cross[i][2]=(((double)facet->vertex[i](0) * (double)facet->vertex[(i + 1) % 3](1)) - - ((double)facet->vertex[i](1) * (double)facet->vertex[(i + 1) % 3](0))); - } - - stl_normal sum; - sum(0) = cross[0][0] + cross[1][0] + cross[2][0]; - sum(1) = cross[0][1] + cross[1][1] + cross[2][1]; - sum(2) = cross[0][2] + cross[1][2] + cross[2][2]; - - // This should already be done. But just in case, let's do it again. - //FIXME this is questionable. the "sum" normal should be accurate, while the normal "n" may be calculated with a low accuracy. - stl_normal n; - stl_calculate_normal(n, facet); - stl_normalize_vector(n); - return 0.5f * n.dot(sum); + stl->stats.volume = get_volume(stl); + if (stl->stats.volume < 0.0) { + stl_reverse_all_facets(stl); + stl->stats.volume = -stl->stats.volume; + } } void stl_repair( diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 8298fe2220..6544265281 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1928,7 +1928,7 @@ namespace Slic3r { stream << " <" << TRIANGLE_TAG << " "; for (int j = 0; j < 3; ++j) { - stream << "v" << j + 1 << "=\"" << its.indices[i].vertex[j] + volume_it->second.first_vertex_id << "\" "; + stream << "v" << j + 1 << "=\"" << its.indices[i][j] + volume_it->second.first_vertex_id << "\" "; } stream << "/>\n"; } diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index dcd9138641..48887bc789 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -958,7 +958,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) for (size_t i = 0; i < (int)volume->mesh.its.indices.size(); ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) - stream << " " << volume->mesh.its.indices[i].vertex[j] + vertices_offset << "\n"; + stream << " " << volume->mesh.its.indices[i][j] + vertices_offset << "\n"; stream << " \n"; } stream << " \n"; diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index ae35c8a5bc..5b0ecbd007 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -614,8 +614,8 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) for (int i = 0; i < 3; ++ i) { EdgeToFace &e2f = edges_map[facet_idx*3+i]; - e2f.vertex_low = this->mesh->its.indices[facet_idx].vertex[i]; - e2f.vertex_high = this->mesh->its.indices[facet_idx].vertex[(i + 1) % 3]; + e2f.vertex_low = this->mesh->its.indices[facet_idx][i]; + e2f.vertex_high = this->mesh->its.indices[facet_idx][(i + 1) % 3]; e2f.face = facet_idx; // 1 based indexing, to be always strictly positive. e2f.face_edge = i + 1; @@ -852,7 +852,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( // Reorder vertices so that the first one is the one with lowest Z. // This is needed to get all intersection lines in a consistent order // (external on the right of the line) - const int *vertices = this->mesh->its.indices[facet_idx].vertex; + const stl_triangle_vertex_indices &vertices = this->mesh->its.indices[facet_idx]; int i = (facet.vertex[1].z() == min_z) ? 1 : ((facet.vertex[2].z() == min_z) ? 2 : 0); // These are used only if the cut plane is tilted: diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index 3e90bfefdf..f3153665cb 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -129,9 +129,9 @@ TriangleMesh::facets() AV* facet = newAV(); av_store(facets, i, newRV_noinc((SV*)facet)); av_extend(facet, 2); - av_store(facet, 0, newSVnv(THIS->its.indices[i].vertex[0])); - av_store(facet, 1, newSVnv(THIS->its.indices[i].vertex[1])); - av_store(facet, 2, newSVnv(THIS->its.indices[i].vertex[2])); + av_store(facet, 0, newSVnv(THIS->its.indices[i][0])); + av_store(facet, 1, newSVnv(THIS->its.indices[i][1])); + av_store(facet, 2, newSVnv(THIS->its.indices[i][2])); } RETVAL = newRV_noinc((SV*)facets); From 0cb5b57c5cf6661f20ab0b3fca3a85ce3b9c790d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 10 Jun 2019 22:43:21 +0200 Subject: [PATCH 073/627] SLA gimzmo: Sharing the Mesh's indexed triangle set with IGL AABB structure directly, without having to make a copy. --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 61 ++++++++++---------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 7 ++- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index ae017f7d12..89475a0582 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -27,6 +27,7 @@ GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_i : GLGizmoBase(parent, sprite_id) #endif // ENABLE_SVG_ICONS , m_quadric(nullptr) + , m_its(nullptr) { m_quadric = ::gluNewQuadric(); if (m_quadric != nullptr) @@ -379,36 +380,24 @@ bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const bool GLGizmoSlaSupports::is_mesh_update_necessary() const { return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) - && ((m_model_object->id() != m_current_mesh_model_id) || m_V.size()==0); + && ((m_model_object->id() != m_current_mesh_model_id) || m_its == nullptr); } void GLGizmoSlaSupports::update_mesh() { wxBusyCursor wait; - Eigen::MatrixXf& V = m_V; - Eigen::MatrixXi& F = m_F; - // We rely on SLA model object having a single volume, // this way we can use that mesh directly. // This mesh does not account for the possible Z up SLA offset. m_mesh = &m_model_object->volumes.front()->mesh; const_cast(m_mesh)->require_shared_vertices(); // TriangleMeshSlicer needs this - const stl_file& stl = m_mesh->stl; - V.resize(3 * stl.stats.number_of_facets, 3); - F.resize(stl.stats.number_of_facets, 3); - for (unsigned int i=0; i(3 * i + 0, 0) = facet.vertex[0]; - V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1]; - V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2]; - F(i, 0) = 3*i+0; - F(i, 1) = 3*i+1; - F(i, 2) = 3*i+2; - } + m_its = &m_mesh->its; m_current_mesh_model_id = m_model_object->id(); m_editing_mode = false; - m_AABB = igl::AABB(); - m_AABB.init(m_V, m_F); + m_AABB.deinit(); + m_AABB.init( + MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), + MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3)); } // Unprojects the mouse position on the mesh and return the hit point and normal of the facet. @@ -416,7 +405,7 @@ void GLGizmoSlaSupports::update_mesh() std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) { // if the gizmo doesn't have the V, F structures for igl, calculate them first: - if (m_V.size() == 0) + if (m_its == nullptr) update_mesh(); const Camera& camera = m_parent.get_camera(); @@ -442,7 +431,10 @@ std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse point1 = inv * point1; point2 = inv * point2; - if (!m_AABB.intersect_ray(m_V, m_F, point1.cast(), (point2-point1).cast(), hits)) + if (!m_AABB.intersect_ray( + MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), + MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), + point1.cast(), (point2-point1).cast(), hits)) throw std::invalid_argument("unproject_on_mesh(): No intersection found."); std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); @@ -457,9 +449,9 @@ std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse igl::Hit& hit = hits[i]; int fid = hit.id; // facet id bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit - a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); - b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); - result = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); + a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]); + b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]); + result = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)]; if (m_clipping_plane_distance == 0.f || !is_point_clipped(result.cast())) break; } @@ -564,15 +556,18 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // Cast a ray in the direction of the camera and look for intersection with the mesh: std::vector hits; // Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. - if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) { + if (m_AABB.intersect_ray( + MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), + MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), + support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) { std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; }); if (m_clipping_plane_distance != 0.f) { // If the closest hit facet normal points in the same direction as the ray, // we are looking through the mesh and should therefore discard the point: int fid = hits.front().id; // facet id - Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); - Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); + Vec3f a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]); + Vec3f b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]); if ((a.cross(b)).dot(direction_to_camera_mesh) > 0.f) is_obscured = true; @@ -582,7 +577,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous int fid = hit.id; // facet id Vec3f bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit - Vec3f hit_pos = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); + Vec3f hit_pos = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)]; if (is_point_clipped(hit_pos.cast())) { hits.erase(hits.begin()+j); --j; @@ -759,9 +754,12 @@ void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const int idx = 0; Eigen::Matrix pp = m_editing_mode_cache[i].support_point.pos; Eigen::Matrix cc; - m_AABB.squared_distance(m_V, m_F, pp, idx, cc); - Vec3f a = (m_V.row(m_F(idx, 1)) - m_V.row(m_F(idx, 0))); - Vec3f b = (m_V.row(m_F(idx, 2)) - m_V.row(m_F(idx, 0))); + m_AABB.squared_distance( + MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), + MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), + pp, idx, cc); + Vec3f a = (m_its->vertices[m_its->indices[idx](1)] - m_its->vertices[m_its->indices[idx](0)]); + Vec3f b = (m_its->vertices[m_its->indices[idx](2)] - m_its->vertices[m_its->indices[idx](0)]); m_editing_mode_cache[i].normal = a.cross(b); } @@ -1067,8 +1065,7 @@ void GLGizmoSlaSupports::on_set_state() m_clipping_plane_distance = 0.f; // Release triangle mesh slicer and the AABB spatial search structure. m_AABB.deinit(); - m_V = Eigen::MatrixXf(); - m_F = Eigen::MatrixXi(); + m_its = nullptr; m_tms.reset(); m_supports_tms.reset(); }); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index fb758d2404..4946db12dd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -35,10 +35,11 @@ private: const float RenderPointScale = 1.f; GLUquadricObj* m_quadric; - Eigen::MatrixXf m_V; // vertices - Eigen::MatrixXi m_F; // facets indices - igl::AABB m_AABB; + typedef Eigen::Map> MapMatrixXfUnaligned; + typedef Eigen::Map> MapMatrixXiUnaligned; + igl::AABB m_AABB; const TriangleMesh* m_mesh; + const indexed_triangle_set* m_its; mutable const TriangleMesh* m_supports_mesh; mutable std::vector m_triangles; mutable std::vector m_supports_triangles; From 590c290ede9ed9da7e608614fbe170d489d1aa06 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 10 Jun 2019 22:43:42 +0200 Subject: [PATCH 074/627] Fix of a typo. --- src/admesh/shared.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admesh/shared.cpp b/src/admesh/shared.cpp index 78179e863a..902bbfc9b8 100644 --- a/src/admesh/shared.cpp +++ b/src/admesh/shared.cpp @@ -220,7 +220,7 @@ bool stl_validate(const stl_file *stl, const indexed_triangle_set &its) // Verify validity of neighborship data. for (int facet_idx = 0; facet_idx < (int)stl->stats.number_of_facets; ++ facet_idx) { const stl_neighbors &nbr = stl->neighbors_start[facet_idx]; - const int *vertices = (its.indices.empty()) ? nullptr : its.indices[facet_idx]; + const int *vertices = its.indices.empty() ? nullptr : its.indices[facet_idx].data(); for (int nbr_idx = 0; nbr_idx < 3; ++ nbr_idx) { int nbr_face = stl->neighbors_start[facet_idx].neighbor[nbr_idx]; assert(nbr_face < (int)stl->stats.number_of_facets); From 5fc465b7e8e5398771e4b6db6043bc84770e5923 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 11 Jun 2019 09:29:32 +0200 Subject: [PATCH 075/627] admesh refactoring: Using boost::object_pool for linked list memory allocation. --- src/admesh/connect.cpp | 525 +++++++++++++++++++++-------------------- src/admesh/normals.cpp | 15 +- src/admesh/stl_io.cpp | 10 +- 3 files changed, 284 insertions(+), 266 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index cbd4f1d337..be782b1b7b 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -28,8 +28,9 @@ #include #include +#include #include -#include +#include #include "stl.h" @@ -42,11 +43,11 @@ struct HashEdge { int hash(int M) const { return ((key[0] / 11 + key[1] / 7 + key[2] / 3) ^ (key[3] / 11 + key[4] / 7 + key[5] / 3)) % M; } // Index of a facet owning this edge. - int facet_number; + int facet_number; // Index of this edge inside the facet with an index of facet_number. // If this edge is stored backwards, which_edge is increased by 3. - int which_edge; - struct HashEdge *next; + int which_edge; + HashEdge *next; void load_exact(stl_file *stl, const stl_vertex *a, const stl_vertex *b) { @@ -69,15 +70,15 @@ struct HashEdge { // Switch negative zeros to positive zeros, so memcmp will consider them to be equal. for (size_t i = 0; i < 6; ++ i) { unsigned char *p = (unsigned char*)(this->key + i); - #ifdef BOOST_LITTLE_ENDIAN + #if BOOST_ENDIAN_LITTLE_BYTE if (p[0] == 0 && p[1] == 0 && p[2] == 0 && p[3] == 0x80) // Negative zero, switch to positive zero. p[3] = 0; - #else /* BOOST_LITTLE_ENDIAN */ + #else /* BOOST_ENDIAN_LITTLE_BYTE */ if (p[0] == 0x80 && p[1] == 0 && p[2] == 0 && p[3] == 0) // Negative zero, switch to positive zero. p[0] = 0; - #endif /* BOOST_LITTLE_ENDIAN */ + #endif /* BOOST_ENDIAN_LITTLE_BYTE */ } } @@ -115,7 +116,7 @@ struct HashTableEdges { HashTableEdges(size_t number_of_faces) { this->M = (int)hash_size_from_nr_faces(number_of_faces); this->heads.assign(this->M, nullptr); - this->tail = new HashEdge; + this->tail = pool.construct(); this->tail->next = this->tail; for (int i = 0; i < this->M; ++ i) this->heads[i] = this->tail; @@ -124,80 +125,32 @@ struct HashTableEdges { for (int i = 0; i < this->M; ++ i) { for (HashEdge *temp = this->heads[i]; this->heads[i] != this->tail; temp = this->heads[i]) { this->heads[i] = this->heads[i]->next; - delete temp; + pool.destroy(temp); #ifndef NDEBUG ++ this->freed; #endif /* NDEBUG */ } } this->heads.clear(); - delete this->tail; + pool.destroy(this->tail); this->tail = nullptr; } - void insert_edge(stl_file *stl, const HashEdge &edge, void (*match_neighbors)(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b)) + void insert_edge_exact(stl_file *stl, const HashEdge &edge) { - int chain_number = edge.hash(this->M); - HashEdge *link = this->heads[chain_number]; - if (link == this->tail) { - // This list doesn't have any edges currently in it. Add this one. - HashEdge *new_edge = new HashEdge(edge); -#ifndef NDEBUG - ++ this->malloced; -#endif /* NDEBUG */ - new_edge->next = this->tail; - this->heads[chain_number] = new_edge; - } else if (edges_equal(edge, *link)) { - // This is a match. Record result in neighbors list. - match_neighbors(stl, edge, *link); - // Delete the matched edge from the list. - this->heads[chain_number] = link->next; - delete link; -#ifndef NDEBUG - ++ this->freed; -#endif /* NDEBUG */ - } else { - // Continue through the rest of the list. - for (;;) { - if (link->next == this->tail) { - // This is the last item in the list. Insert a new edge. - HashEdge *new_edge = new HashEdge; -#ifndef NDEBUG - ++ this->malloced; -#endif /* NDEBUG */ - *new_edge = edge; - new_edge->next = this->tail; - link->next = new_edge; -#ifndef NDEBUG - ++ this->collisions; -#endif /* NDEBUG */ - break; - } - if (edges_equal(edge, *link->next)) { - // This is a match. Record result in neighbors list. - match_neighbors(stl, edge, *link->next); - // Delete the matched edge from the list. - HashEdge *temp = link->next; - link->next = link->next->next; - delete temp; -#ifndef NDEBUG - ++ this->freed; -#endif /* NDEBUG */ - break; - } - // This is not a match. Go to the next link. - link = link->next; -#ifndef NDEBUG - ++ this->collisions; -#endif /* NDEBUG */ - } - } + this->insert_edge(stl, edge, [stl](const HashEdge& edge1, const HashEdge& edge2) { record_neighbors(stl, edge1, edge2); }); + } + + void insert_edge_nearby(stl_file *stl, const HashEdge &edge) + { + this->insert_edge(stl, edge, [stl](const HashEdge& edge1, const HashEdge& edge2) { match_neighbors_nearby(stl, edge1, edge2); }); } // Hash table on edges std::vector heads; HashEdge* tail; int M; + boost::object_pool pool; #ifndef NDEBUG size_t malloced = 0; @@ -216,198 +169,260 @@ private: return (it == primes.end()) ? primes.back() : *it; } + + // MatchNeighbors(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) + template + void insert_edge(stl_file *stl, const HashEdge &edge, MatchNeighbors match_neighbors) + { + int chain_number = edge.hash(this->M); + HashEdge *link = this->heads[chain_number]; + if (link == this->tail) { + // This list doesn't have any edges currently in it. Add this one. + HashEdge *new_edge = pool.construct(edge); +#ifndef NDEBUG + ++ this->malloced; +#endif /* NDEBUG */ + new_edge->next = this->tail; + this->heads[chain_number] = new_edge; + } else if (edges_equal(edge, *link)) { + // This is a match. Record result in neighbors list. + match_neighbors(edge, *link); + // Delete the matched edge from the list. + this->heads[chain_number] = link->next; + pool.destroy(link); +#ifndef NDEBUG + ++ this->freed; +#endif /* NDEBUG */ + } else { + // Continue through the rest of the list. + for (;;) { + if (link->next == this->tail) { + // This is the last item in the list. Insert a new edge. + HashEdge *new_edge = pool.construct(); +#ifndef NDEBUG + ++ this->malloced; +#endif /* NDEBUG */ + *new_edge = edge; + new_edge->next = this->tail; + link->next = new_edge; +#ifndef NDEBUG + ++ this->collisions; +#endif /* NDEBUG */ + break; + } + if (edges_equal(edge, *link->next)) { + // This is a match. Record result in neighbors list. + match_neighbors(edge, *link->next); + // Delete the matched edge from the list. + HashEdge *temp = link->next; + link->next = link->next->next; + pool.destroy(temp); +#ifndef NDEBUG + ++ this->freed; +#endif /* NDEBUG */ + break; + } + // This is not a match. Go to the next link. + link = link->next; +#ifndef NDEBUG + ++ this->collisions; +#endif /* NDEBUG */ + } + } + } + // Edges equal for hashing. Edgesof different facet are allowed to be matched. static inline bool edges_equal(const HashEdge &edge_a, const HashEdge &edge_b) { return edge_a.facet_number != edge_b.facet_number && edge_a == edge_b; } + + static void record_neighbors(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) + { + // Facet a's neighbor is facet b + stl->neighbors_start[edge_a.facet_number].neighbor[edge_a.which_edge % 3] = edge_b.facet_number; /* sets the .neighbor part */ + stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] = (edge_b.which_edge + 2) % 3; /* sets the .which_vertex_not part */ + + // Facet b's neighbor is facet a + stl->neighbors_start[edge_b.facet_number].neighbor[edge_b.which_edge % 3] = edge_a.facet_number; /* sets the .neighbor part */ + stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] = (edge_a.which_edge + 2) % 3; /* sets the .which_vertex_not part */ + + if (((edge_a.which_edge < 3) && (edge_b.which_edge < 3)) || ((edge_a.which_edge > 2) && (edge_b.which_edge > 2))) { + // These facets are oriented in opposite directions, their normals are probably messed up. + stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] += 3; + stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] += 3; + } + + // Count successful connects: + // Total connects: + stl->stats.connected_edges += 2; + // Count individual connects: + switch (stl->neighbors_start[edge_a.facet_number].num_neighbors()) { + case 1: ++ stl->stats.connected_facets_1_edge; break; + case 2: ++ stl->stats.connected_facets_2_edge; break; + case 3: ++ stl->stats.connected_facets_3_edge; break; + default: assert(false); + } + switch (stl->neighbors_start[edge_b.facet_number].num_neighbors()) { + case 1: ++ stl->stats.connected_facets_1_edge; break; + case 2: ++ stl->stats.connected_facets_2_edge; break; + case 3: ++ stl->stats.connected_facets_3_edge; break; + default: assert(false); + } + } + + static void match_neighbors_nearby(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) + { + record_neighbors(stl, edge_a, edge_b); + + // Which vertices to change + int facet1 = -1; + int facet2 = -1; + int vertex1, vertex2; + stl_vertex new_vertex1, new_vertex2; + { + int v1a; // pair 1, facet a + int v1b; // pair 1, facet b + int v2a; // pair 2, facet a + int v2b; // pair 2, facet b + // Find first pair. + if (edge_a.which_edge < 3) { + v1a = edge_a.which_edge; + v2a = (edge_a.which_edge + 1) % 3; + } else { + v2a = edge_a.which_edge % 3; + v1a = (edge_a.which_edge + 1) % 3; + } + if (edge_b.which_edge < 3) { + v1b = edge_b.which_edge; + v2b = (edge_b.which_edge + 1) % 3; + } else { + v2b = edge_b.which_edge % 3; + v1b = (edge_b.which_edge + 1) % 3; + } + + // Of the first pair, which vertex, if any, should be changed + if (stl->facet_start[edge_a.facet_number].vertex[v1a] != stl->facet_start[edge_b.facet_number].vertex[v1b]) { + // These facets are different. + if ( (stl->neighbors_start[edge_a.facet_number].neighbor[v1a] == -1) + && (stl->neighbors_start[edge_a.facet_number].neighbor[(v1a + 2) % 3] == -1)) { + // This vertex has no neighbors. This is a good one to change. + facet1 = edge_a.facet_number; + vertex1 = v1a; + new_vertex1 = stl->facet_start[edge_b.facet_number].vertex[v1b]; + } else { + facet1 = edge_b.facet_number; + vertex1 = v1b; + new_vertex1 = stl->facet_start[edge_a.facet_number].vertex[v1a]; + } + } + + // Of the second pair, which vertex, if any, should be changed. + if (stl->facet_start[edge_a.facet_number].vertex[v2a] == stl->facet_start[edge_b.facet_number].vertex[v2b]) { + // These facets are different. + if ( (stl->neighbors_start[edge_a.facet_number].neighbor[v2a] == -1) + && (stl->neighbors_start[edge_a.facet_number].neighbor[(v2a + 2) % 3] == -1)) { + // This vertex has no neighbors. This is a good one to change. + facet2 = edge_a.facet_number; + vertex2 = v2a; + new_vertex2 = stl->facet_start[edge_b.facet_number].vertex[v2b]; + } else { + facet2 = edge_b.facet_number; + vertex2 = v2b; + new_vertex2 = stl->facet_start[edge_a.facet_number].vertex[v2a]; + } + } + } + + auto change_vertices = [stl](int facet_num, int vnot, stl_vertex new_vertex) + { + int first_facet = facet_num; + bool direction = false; + + for (;;) { + int pivot_vertex; + int next_edge; + if (vnot > 2) { + if (direction) { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot % 3; + } + else { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } + direction = !direction; + } + else { + if (direction) { + pivot_vertex = (vnot + 2) % 3; + next_edge = pivot_vertex; + } + else { + pivot_vertex = (vnot + 1) % 3; + next_edge = vnot; + } + } + #if 0 + if (stl->facet_start[facet_num].vertex[pivot_vertex](0) == new_vertex(0) && + stl->facet_start[facet_num].vertex[pivot_vertex](1) == new_vertex(1) && + stl->facet_start[facet_num].vertex[pivot_vertex](2) == new_vertex(2)) + printf("Changing vertex %f,%f,%f: Same !!!\r\n", new_vertex(0), new_vertex(1), new_vertex(2)); + else { + if (stl->facet_start[facet_num].vertex[pivot_vertex](0) != new_vertex(0)) + printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", + stl->facet_start[facet_num].vertex[pivot_vertex](0), + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](0)), + new_vertex(0), + *reinterpret_cast(&new_vertex(0))); + if (stl->facet_start[facet_num].vertex[pivot_vertex](1) != new_vertex(1)) + printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", + stl->facet_start[facet_num].vertex[pivot_vertex](1), + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](1)), + new_vertex(1), + *reinterpret_cast(&new_vertex(1))); + if (stl->facet_start[facet_num].vertex[pivot_vertex](2) != new_vertex(2)) + printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", + stl->facet_start[facet_num].vertex[pivot_vertex](2), + *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](2)), + new_vertex(2), + *reinterpret_cast(&new_vertex(2))); + } + #endif + stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex; + vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; + facet_num = stl->neighbors_start[facet_num].neighbor[next_edge]; + if (facet_num == -1) + break; + + if (facet_num == first_facet) { + // back to the beginning + BOOST_LOG_TRIVIAL(info) << "Back to the first facet changing vertices: probably a mobius part. Try using a smaller tolerance or don't do a nearby check."; + return; + } + } + }; + + if (facet1 != -1) { + int vnot1 = (facet1 == edge_a.facet_number) ? + (edge_a.which_edge + 2) % 3 : + (edge_b.which_edge + 2) % 3; + if (((vnot1 + 2) % 3) == vertex1) + vnot1 += 3; + change_vertices(facet1, vnot1, new_vertex1); + } + if (facet2 != -1) { + int vnot2 = (facet2 == edge_a.facet_number) ? + (edge_a.which_edge + 2) % 3 : + (edge_b.which_edge + 2) % 3; + if (((vnot2 + 2) % 3) == vertex2) + vnot2 += 3; + change_vertices(facet2, vnot2, new_vertex2); + } + stl->stats.edges_fixed += 2; + } }; -static void record_neighbors(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) -{ - // Facet a's neighbor is facet b - stl->neighbors_start[edge_a.facet_number].neighbor[edge_a.which_edge % 3] = edge_b.facet_number; /* sets the .neighbor part */ - stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] = (edge_b.which_edge + 2) % 3; /* sets the .which_vertex_not part */ - - // Facet b's neighbor is facet a - stl->neighbors_start[edge_b.facet_number].neighbor[edge_b.which_edge % 3] = edge_a.facet_number; /* sets the .neighbor part */ - stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] = (edge_a.which_edge + 2) % 3; /* sets the .which_vertex_not part */ - - if (((edge_a.which_edge < 3) && (edge_b.which_edge < 3)) || ((edge_a.which_edge > 2) && (edge_b.which_edge > 2))) { - // These facets are oriented in opposite directions, their normals are probably messed up. - stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] += 3; - stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] += 3; - } - - // Count successful connects: - // Total connects: - stl->stats.connected_edges += 2; - // Count individual connects: - switch (stl->neighbors_start[edge_a.facet_number].num_neighbors()) { - case 1: ++ stl->stats.connected_facets_1_edge; break; - case 2: ++ stl->stats.connected_facets_2_edge; break; - case 3: ++ stl->stats.connected_facets_3_edge; break; - default: assert(false); - } - switch (stl->neighbors_start[edge_b.facet_number].num_neighbors()) { - case 1: ++ stl->stats.connected_facets_1_edge; break; - case 2: ++ stl->stats.connected_facets_2_edge; break; - case 3: ++ stl->stats.connected_facets_3_edge; break; - default: assert(false); - } -} - -static void match_neighbors_nearby(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) -{ - record_neighbors(stl, edge_a, edge_b); - - // Which vertices to change - int facet1 = -1; - int facet2 = -1; - int vertex1, vertex2; - stl_vertex new_vertex1, new_vertex2; - { - int v1a; // pair 1, facet a - int v1b; // pair 1, facet b - int v2a; // pair 2, facet a - int v2b; // pair 2, facet b - // Find first pair. - if (edge_a.which_edge < 3) { - v1a = edge_a.which_edge; - v2a = (edge_a.which_edge + 1) % 3; - } else { - v2a = edge_a.which_edge % 3; - v1a = (edge_a.which_edge + 1) % 3; - } - if (edge_b.which_edge < 3) { - v1b = edge_b.which_edge; - v2b = (edge_b.which_edge + 1) % 3; - } else { - v2b = edge_b.which_edge % 3; - v1b = (edge_b.which_edge + 1) % 3; - } - - // Of the first pair, which vertex, if any, should be changed - if (stl->facet_start[edge_a.facet_number].vertex[v1a] != stl->facet_start[edge_b.facet_number].vertex[v1b]) { - // These facets are different. - if ( (stl->neighbors_start[edge_a.facet_number].neighbor[v1a] == -1) - && (stl->neighbors_start[edge_a.facet_number].neighbor[(v1a + 2) % 3] == -1)) { - // This vertex has no neighbors. This is a good one to change. - facet1 = edge_a.facet_number; - vertex1 = v1a; - new_vertex1 = stl->facet_start[edge_b.facet_number].vertex[v1b]; - } else { - facet1 = edge_b.facet_number; - vertex1 = v1b; - new_vertex1 = stl->facet_start[edge_a.facet_number].vertex[v1a]; - } - } - - // Of the second pair, which vertex, if any, should be changed. - if (stl->facet_start[edge_a.facet_number].vertex[v2a] == stl->facet_start[edge_b.facet_number].vertex[v2b]) { - // These facets are different. - if ( (stl->neighbors_start[edge_a.facet_number].neighbor[v2a] == -1) - && (stl->neighbors_start[edge_a.facet_number].neighbor[(v2a + 2) % 3] == -1)) { - // This vertex has no neighbors. This is a good one to change. - facet2 = edge_a.facet_number; - vertex2 = v2a; - new_vertex2 = stl->facet_start[edge_b.facet_number].vertex[v2b]; - } else { - facet2 = edge_b.facet_number; - vertex2 = v2b; - new_vertex2 = stl->facet_start[edge_a.facet_number].vertex[v2a]; - } - } - } - - auto change_vertices = [stl](int facet_num, int vnot, stl_vertex new_vertex) - { - int first_facet = facet_num; - bool direction = false; - - for (;;) { - int pivot_vertex; - int next_edge; - if (vnot > 2) { - if (direction) { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot % 3; - } - else { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - } - direction = !direction; - } - else { - if (direction) { - pivot_vertex = (vnot + 2) % 3; - next_edge = pivot_vertex; - } - else { - pivot_vertex = (vnot + 1) % 3; - next_edge = vnot; - } - } -#if 0 - if (stl->facet_start[facet_num].vertex[pivot_vertex](0) == new_vertex(0) && - stl->facet_start[facet_num].vertex[pivot_vertex](1) == new_vertex(1) && - stl->facet_start[facet_num].vertex[pivot_vertex](2) == new_vertex(2)) - printf("Changing vertex %f,%f,%f: Same !!!\r\n", new_vertex(0), new_vertex(1), new_vertex(2)); - else { - if (stl->facet_start[facet_num].vertex[pivot_vertex](0) != new_vertex(0)) - printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", - stl->facet_start[facet_num].vertex[pivot_vertex](0), - *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](0)), - new_vertex(0), - *reinterpret_cast(&new_vertex(0))); - if (stl->facet_start[facet_num].vertex[pivot_vertex](1) != new_vertex(1)) - printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", - stl->facet_start[facet_num].vertex[pivot_vertex](1), - *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](1)), - new_vertex(1), - *reinterpret_cast(&new_vertex(1))); - if (stl->facet_start[facet_num].vertex[pivot_vertex](2) != new_vertex(2)) - printf("Changing coordinate x, vertex %e (0x%08x) to %e(0x%08x)\r\n", - stl->facet_start[facet_num].vertex[pivot_vertex](2), - *reinterpret_cast(&stl->facet_start[facet_num].vertex[pivot_vertex](2)), - new_vertex(2), - *reinterpret_cast(&new_vertex(2))); - } -#endif - stl->facet_start[facet_num].vertex[pivot_vertex] = new_vertex; - vnot = stl->neighbors_start[facet_num].which_vertex_not[next_edge]; - facet_num = stl->neighbors_start[facet_num].neighbor[next_edge]; - if (facet_num == -1) - break; - - if (facet_num == first_facet) { - // back to the beginning - BOOST_LOG_TRIVIAL(info) << "Back to the first facet changing vertices: probably a mobius part. Try using a smaller tolerance or don't do a nearby check."; - return; - } - } - }; - - if (facet1 != -1) { - int vnot1 = (facet1 == edge_a.facet_number) ? - (edge_a.which_edge + 2) % 3 : - (edge_b.which_edge + 2) % 3; - if (((vnot1 + 2) % 3) == vertex1) - vnot1 += 3; - change_vertices(facet1, vnot1, new_vertex1); - } - if (facet2 != -1) { - int vnot2 = (facet2 == edge_a.facet_number) ? - (edge_a.which_edge + 2) % 3 : - (edge_b.which_edge + 2) % 3; - if (((vnot2 + 2) % 3) == vertex2) - vnot2 += 3; - change_vertices(facet2, vnot2, new_vertex2); - } - stl->stats.edges_fixed += 2; -} - // This function builds the neighbors list. No modifications are made // to any of the facets. The edges are said to match only if all six // floats of the first edge matches all six floats of the second edge. @@ -445,7 +460,7 @@ void stl_check_facets_exact(stl_file *stl) edge.facet_number = i; edge.which_edge = j; edge.load_exact(stl, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); - hash_table.insert_edge(stl, edge, record_neighbors); + hash_table.insert_edge_exact(stl, edge); } } @@ -476,7 +491,7 @@ void stl_check_facets_nearby(stl_file *stl, float tolerance) edge.which_edge = j; if (edge.load_nearby(stl, facet.vertex[j], facet.vertex[(j + 1) % 3], tolerance)) // Only insert edges that have different keys. - hash_table.insert_edge(stl, edge, match_neighbors_nearby); + hash_table.insert_edge_nearby(stl, edge); } } } @@ -654,7 +669,7 @@ void stl_fill_holes(stl_file *stl) edge.facet_number = i; edge.which_edge = j; edge.load_exact(stl, &facet.vertex[j], &facet.vertex[(j + 1) % 3]); - hash_table.insert_edge(stl, edge, record_neighbors); + hash_table.insert_edge_exact(stl, edge); } } @@ -704,7 +719,7 @@ void stl_fill_holes(stl_file *stl) edge.facet_number = stl->stats.number_of_facets - 1; edge.which_edge = k; edge.load_exact(stl, &new_facet.vertex[k], &new_facet.vertex[(k + 1) % 3]); - hash_table.insert_edge(stl, edge, record_neighbors); + hash_table.insert_edge_exact(stl, edge); } break; } diff --git a/src/admesh/normals.cpp b/src/admesh/normals.cpp index 464f9e6d8e..4d47573f6c 100644 --- a/src/admesh/normals.cpp +++ b/src/admesh/normals.cpp @@ -25,6 +25,8 @@ #include #include +#include + #include "stl.h" static void reverse_facet(stl_file *stl, int facet_num) @@ -120,8 +122,9 @@ void stl_fix_normal_directions(stl_file *stl) }; // Initialize linked list. - stl_normal *head = new stl_normal; - stl_normal *tail = new stl_normal; + boost::object_pool pool; + stl_normal *head = pool.construct(); + stl_normal *tail = pool.construct(); head->next = tail; tail->next = tail; @@ -168,7 +171,7 @@ void stl_fix_normal_directions(stl_file *stl) // If we haven't fixed this facet yet, add it to the list: if (norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) { // Add node to beginning of list. - stl_normal *newn = new stl_normal; + stl_normal *newn = pool.construct(); newn->facet_num = stl->neighbors_start[facet_num].neighbor[j]; newn->next = head->next; head->next = newn; @@ -189,7 +192,7 @@ void stl_fix_normal_directions(stl_file *stl) } stl_normal *temp = head->next; // Delete this facet from the list. head->next = head->next->next; - delete temp; + pool.destroy(temp); } else { // If we ran out of facets to fix: All of the facets in this part have been fixed. ++ stl->stats.number_of_parts; if (checked >= stl->stats.number_of_facets) @@ -211,8 +214,8 @@ void stl_fix_normal_directions(stl_file *stl) } } - delete head; - delete tail; + pool.destroy(head); + pool.destroy(tail); } void stl_fix_normal_values(stl_file *stl) diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp index 7e181716c6..464c98907a 100644 --- a/src/admesh/stl_io.cpp +++ b/src/admesh/stl_io.cpp @@ -25,7 +25,7 @@ #include #include -#include +#include #include "stl.h" @@ -114,7 +114,7 @@ bool stl_print_neighbors(stl_file *stl, char *file) return true; } -#ifndef BOOST_LITTLE_ENDIAN +#if BOOST_ENDIAN_BIG_BYTE // Swap a buffer of 32bit data from little endian to big endian and vice versa. void stl_internal_reverse_quads(char *buf, size_t cnt) { @@ -141,11 +141,11 @@ bool stl_write_binary(stl_file *stl, const char *file, const char *label) #define SEEK_SET 0 #endif fseek(fp, LABEL_SIZE, SEEK_SET); -#ifdef BOOST_LITTLE_ENDIAN +#if BOOST_ENDIAN_LITTLE_BYTE fwrite(&stl->stats.number_of_facets, 4, 1, fp); for (const stl_facet &facet : stl->facet_start) fwrite(&facet, SIZEOF_STL_FACET, 1, fp); -#else /* BOOST_LITTLE_ENDIAN */ +#else /* BOOST_ENDIAN_LITTLE_BYTE */ char buffer[50]; // Convert the number of facets to little endian. memcpy(buffer, &stl->stats.number_of_facets, 4); @@ -157,7 +157,7 @@ bool stl_write_binary(stl_file *stl, const char *file, const char *label) stl_internal_reverse_quads(buffer, 48); fwrite(buffer, SIZEOF_STL_FACET, 1, fp); } -#endif /* BOOST_LITTLE_ENDIAN */ +#endif /* BOOST_ENDIAN_LITTLE_BYTE */ fclose(fp); return true; } From 16c5a87997e6ce1cea09cda8e2ca4aaf06676092 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Jun 2019 09:50:58 +0200 Subject: [PATCH 076/627] Import/export of the Layers information to/from AMF --- src/libslic3r/Format/AMF.cpp | 57 ++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index d26b5f3ed9..ed9c8f4456 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -106,6 +106,9 @@ struct AMFParserContext // amf/material/metadata NODE_TYPE_OBJECT, // amf/object // amf/object/metadata + NODE_TYPE_LAYER_CONFIG, // amf/object/layer_config_ranges + NODE_TYPE_RANGE, // amf/object/layer_config_ranges/range + // amf/object/layer_config_ranges/range/metadata NODE_TYPE_MESH, // amf/object/mesh NODE_TYPE_VERTICES, // amf/object/mesh/vertices NODE_TYPE_VERTEX, // amf/object/mesh/vertices/vertex @@ -260,7 +263,9 @@ void AMFParserContext::startElement(const char *name, const char **atts) m_value[0] = get_attribute(atts, "type"); node_type_new = NODE_TYPE_METADATA; } - } else if (strcmp(name, "mesh") == 0) { + } else if (strcmp(name, "layer_config_ranges") == 0 && m_path[1] == NODE_TYPE_OBJECT) + node_type_new = NODE_TYPE_LAYER_CONFIG; + else if (strcmp(name, "mesh") == 0) { if (m_path[1] == NODE_TYPE_OBJECT) node_type_new = NODE_TYPE_MESH; } else if (strcmp(name, "instance") == 0) { @@ -317,6 +322,10 @@ void AMFParserContext::startElement(const char *name, const char **atts) else if (strcmp(name, "mirrorz") == 0) node_type_new = NODE_TYPE_MIRRORZ; } + else if (m_path[2] == NODE_TYPE_LAYER_CONFIG && strcmp(name, "range") == 0) { + assert(m_object); + node_type_new = NODE_TYPE_RANGE; + } break; case 4: if (m_path[3] == NODE_TYPE_VERTICES) { @@ -334,6 +343,10 @@ void AMFParserContext::startElement(const char *name, const char **atts) } else if (strcmp(name, "triangle") == 0) node_type_new = NODE_TYPE_TRIANGLE; } + else if (m_path[3] == NODE_TYPE_RANGE && strcmp(name, "metadata") == 0) { + m_value[0] = get_attribute(atts, "type"); + node_type_new = NODE_TYPE_METADATA; + } break; case 5: if (strcmp(name, "coordinates") == 0) { @@ -569,8 +582,13 @@ void AMFParserContext::endElement(const char * /* name */) config = &m_material->config; else if (m_path[1] == NODE_TYPE_OBJECT && m_object) config = &m_object->config; - } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) + } + else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) config = &m_volume->config; + else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_RANGE && m_object && !m_object->layer_config_ranges.empty()) { + auto it = --m_object->layer_config_ranges.end(); + config = &it->second; + } if (config) config->set_deserialize(opt_key, m_value[1]); } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) { @@ -607,6 +625,16 @@ void AMFParserContext::endElement(const char * /* name */) } m_object->sla_points_status = sla::PointsStatus::UserModified; } + else if (m_path.size() == 5 && m_path[1] == NODE_TYPE_OBJECT && m_path[3] == NODE_TYPE_RANGE && + m_object && strcmp(opt_key, "layer_height_ranges") == 0) { + // Parse object's layer_height_ranges, a semicolon separated doubles. + char* p = const_cast(m_value[1].c_str()); + char* end = strchr(p, ';'); + *end = 0; + + const t_layer_height_range range = {double(atof(p)), double(atof(end + 1))}; + m_object->layer_config_ranges[range]; + } else if (m_path.size() == 5 && m_path[3] == NODE_TYPE_VOLUME && m_volume) { if (strcmp(opt_key, "modifier") == 0) { // Is this volume a modifier volume? @@ -905,6 +933,31 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) } //FIXME Store the layer height ranges (ModelObject::layer_height_ranges) + + // #ys_FIXME_experiment : Try to export layer config range + const t_layer_config_ranges& config_ranges = object->layer_config_ranges; + if (!config_ranges.empty()) + { + // Store the layer config range as a single semicolon separated list. + stream << " \n"; + size_t layer_counter = 0; + for (auto range : config_ranges) { + stream << " \n"; + + stream << " "; + stream << range.first.first << ";" << range.first.second << "\n"; + + for (const std::string& key : range.second.keys()) + stream << " " << range.second.serialize(key) << "\n"; + + stream << " \n"; + layer_counter++; + } + + stream << " \n"; + } + + const std::vector& sla_support_points = object->sla_support_points; if (!sla_support_points.empty()) { // Store the SLA supports as a single semicolon separated list. From f0f608f247832b9f5ca3b1cd0a3bb03d148a7fad Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Jun 2019 10:11:42 +0200 Subject: [PATCH 077/627] Copy/paste Layers for OSX --- src/slic3r/GUI/GUI_ObjectList.cpp | 40 ++++++++++++++++++------------- src/slic3r/GUI/GUI_ObjectList.hpp | 3 +++ 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index f6dd6c5915..23eb67c508 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -148,10 +148,10 @@ ObjectList::ObjectList(wxWindow* parent) : wxAcceleratorTable accel(6, entries); SetAcceleratorTable(accel); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); }, wxID_COPY); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); }, wxID_PASTE); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children(); }, wxID_SELECTALL); - this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy(); }, wxID_COPY); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->paste(); }, wxID_PASTE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children(); }, wxID_SELECTALL); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE); } #else __WXOSX__ Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX @@ -773,6 +773,22 @@ void ObjectList::show_context_menu() } } +void ObjectList::copy() +{ + if (m_selection_mode & smLayer) + fill_layer_config_ranges_cache(); + else + wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); +} + +void ObjectList::paste() +{ + if (!m_layer_config_ranges_cache.empty()) + paste_layers_into_list(); + else + wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); +} + #ifndef __WXOSX__ void ObjectList::key_event(wxKeyEvent& event) { @@ -787,18 +803,10 @@ void ObjectList::key_event(wxKeyEvent& event) } else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL/*WXK_SHIFT*/)) select_item_all_children(); - else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) { - if (m_selection_mode & smLayer) - fill_layer_config_ranges_cache(); - else - wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); - } - else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL)) { - if (!m_layer_config_ranges_cache.empty()) - paste_layers_into_list(); - else - wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); - } + else if (wxGetKeyState(wxKeyCode('C')) && wxGetKeyState(WXK_CONTROL)) + copy(); + else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL)) + paste(); else event.Skip(); } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 7c7046626d..ed055a3a68 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -202,6 +202,9 @@ public: void key_event(wxKeyEvent& event); #endif /* __WXOSX__ */ + void copy(); + void paste(); + void get_settings_choice(const wxString& category_name); void get_freq_settings_choice(const wxString& bundle_name); void update_settings_item(); From ddd0a9abb69ca7715641efaaa34dbbf030b552ef Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 11 Jun 2019 12:40:07 +0200 Subject: [PATCH 078/627] SPE-742: Builtin pad feature in zero elevation mode. --- .clang-format | 2 +- sandboxes/slabasebed/slabasebed.cpp | 32 +-- src/libslic3r/SLA/SLABasePool.cpp | 321 ++++++++++++++++++---- src/libslic3r/SLA/SLABasePool.hpp | 17 +- src/libslic3r/SLA/SLACommon.hpp | 7 + src/libslic3r/SLA/SLASupportTree.cpp | 391 ++++++++++++++++++++------- src/libslic3r/SLA/SLASupportTree.hpp | 21 +- src/libslic3r/SLAPrint.cpp | 126 ++++++--- 8 files changed, 685 insertions(+), 232 deletions(-) diff --git a/.clang-format b/.clang-format index d5740f6894..9a2c3ce1dd 100644 --- a/.clang-format +++ b/.clang-format @@ -46,7 +46,7 @@ BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true -ColumnLimit: 75 +ColumnLimit: 78 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true diff --git a/sandboxes/slabasebed/slabasebed.cpp b/sandboxes/slabasebed/slabasebed.cpp index 0c34eb9f52..5393f61fd7 100644 --- a/sandboxes/slabasebed/slabasebed.cpp +++ b/sandboxes/slabasebed/slabasebed.cpp @@ -15,7 +15,8 @@ const std::string USAGE_STR = { namespace Slic3r { namespace sla { -Contour3D create_base_pool(const ExPolygons &ground_layer, +Contour3D create_base_pool(const Polygons &ground_layer, + const Polygons &holes = {}, const PoolConfig& cfg = PoolConfig()); Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling, @@ -42,37 +43,28 @@ int main(const int argc, const char *argv[]) { model.ReadSTLFile(argv[1]); model.align_to_origin(); - ExPolygons ground_slice; - sla::Contour3D mesh; -// TriangleMesh basepool; - + Polygons ground_slice; sla::base_plate(model, ground_slice, 0.1f); - if(ground_slice.empty()) return EXIT_FAILURE; -// ExPolygon bottom_plate = ground_slice.front(); -// ExPolygon top_plate = bottom_plate; -// sla::offset(top_plate, coord_t(3.0/SCALING_FACTOR)); -// sla::offset(bottom_plate, coord_t(1.0/SCALING_FACTOR)); + Polygon gndfirst; gndfirst = ground_slice.front(); + sla::offset_with_breakstick_holes(gndfirst, 0.5, 10, 0.3); + + sla::Contour3D mesh; + bench.start(); -// TriangleMesh pool; sla::PoolConfig cfg; cfg.min_wall_height_mm = 0; - cfg.edge_radius_mm = 0.2; - mesh = sla::create_base_pool(ground_slice, cfg); - -// mesh.merge(triangulate_expolygon_3d(top_plate, 3.0, false)); -// mesh.merge(triangulate_expolygon_3d(bottom_plate, 0.0, true)); -// mesh = sla::walls(bottom_plate.contour, top_plate.contour, 0, 3, 2.0, [](){}); - + cfg.edge_radius_mm = 0; + mesh = sla::create_base_pool(ground_slice, {}, cfg); + bench.stop(); cout << "Base pool creation time: " << std::setprecision(10) << bench.getElapsedSec() << " seconds." << endl; - -// auto point = []() + for(auto& trind : mesh.indices) { Vec3d p0 = mesh.points[size_t(trind[0])]; Vec3d p1 = mesh.points[size_t(trind[1])]; diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 171d2b8d03..9b3f80f1a1 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -7,9 +7,9 @@ #include "Tesselate.hpp" // For debugging: -//#include -//#include -//#include "SVG.hpp" +// #include +// #include +#include "SVG.hpp" namespace Slic3r { namespace sla { @@ -180,9 +180,10 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, } /// Offsetting with clipper and smoothing the edges into a curvature. -void offset(ExPolygon& sh, coord_t distance) { +void offset(ExPolygon& sh, coord_t distance, bool edgerounding = true) { using ClipperLib::ClipperOffset; using ClipperLib::jtRound; + using ClipperLib::jtMiter; using ClipperLib::etClosedPolygon; using ClipperLib::Paths; using ClipperLib::Path; @@ -199,11 +200,13 @@ void offset(ExPolygon& sh, coord_t distance) { return; } + auto jointype = edgerounding? jtRound : jtMiter; + ClipperOffset offs; offs.ArcTolerance = 0.01*mm(1); Paths result; - offs.AddPath(ctour, jtRound, etClosedPolygon); - offs.AddPaths(holes, jtRound, etClosedPolygon); + offs.AddPath(ctour, jointype, etClosedPolygon); + offs.AddPaths(holes, jointype, etClosedPolygon); offs.Execute(result, static_cast(distance)); // Offsetting reverts the orientation and also removes the last vertex @@ -233,6 +236,49 @@ void offset(ExPolygon& sh, coord_t distance) { } } +void offset(Polygon& sh, coord_t distance, bool edgerounding = true) { + using ClipperLib::ClipperOffset; + using ClipperLib::jtRound; + using ClipperLib::jtMiter; + using ClipperLib::etClosedPolygon; + using ClipperLib::Paths; + using ClipperLib::Path; + + auto&& ctour = Slic3rMultiPoint_to_ClipperPath(sh); + + // If the input is not at least a triangle, we can not do this algorithm + if(ctour.size() < 3) { + BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; + return; + } + + ClipperOffset offs; + offs.ArcTolerance = 0.01*mm(1); + Paths result; + offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon); + offs.Execute(result, static_cast(distance)); + + // Offsetting reverts the orientation and also removes the last vertex + // so boost will not have a closed polygon. + + bool found_the_contour = false; + for(auto& r : result) { + if(ClipperLib::Orientation(r)) { + // We don't like if the offsetting generates more than one contour + // but throwing would be an overkill. Instead, we should warn the + // caller about the inability to create correct geometries + if(!found_the_contour) { + auto rr = ClipperPath_to_Slic3rPolygon(r); + sh.points.swap(rr.points); + found_the_contour = true; + } else { + BOOST_LOG_TRIVIAL(warning) + << "Warning: offsetting result is invalid!"; + } + } + } +} + /// Unification of polygons (with clipper) preserving holes as well. ExPolygons unify(const ExPolygons& shapes) { using ClipperLib::ptSubject; @@ -303,6 +349,118 @@ ExPolygons unify(const ExPolygons& shapes) { return retv; } +Polygons unify(const Polygons& shapes) { + using ClipperLib::ptSubject; + + bool closed = true; + bool valid = true; + + ClipperLib::Clipper clipper; + + for(auto& path : shapes) { + auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path); + + if(!clipperpath.empty()) + valid &= clipper.AddPath(clipperpath, ptSubject, closed); + } + + if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!"; + + ClipperLib::Paths result; + clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); + + Polygons ret; + for (ClipperLib::Path &p : result) { + Polygon pp = ClipperPath_to_Slic3rPolygon(p); + if (!pp.is_clockwise()) ret.emplace_back(std::move(pp)); + } + + return ret; +} + +// Function to cut tiny connector cavities for a given polygon. The input poly +// will be offsetted by "padding" and small rectangle shaped cavities will be +// inserted along the perimeter in every "stride" distance. The stick rectangles +// will have a with about "stick_width". The input dimensions are in world +// measure, not the scaled clipper units. +void offset_with_breakstick_holes(ExPolygon& poly, + double padding, + double stride, + double stick_width, + double penetration) +{ + // We do the basic offsetting first + const bool dont_round_edges = false; + offset(poly, coord_t(padding / SCALING_FACTOR), dont_round_edges); + + SVG svg("bridgestick_plate.svg"); + svg.draw(poly); + + auto transf = [stick_width, penetration, padding, stride](Points &pts) { + // The connector stick will be a small rectangle with dimensions + // stick_width x (penetration + padding) to have some penetration + // into the input polygon. + + Points out; + out.reserve(2 * pts.size()); // output polygon points + + // stick bottom and right edge dimensions + double sbottom = stick_width / SCALING_FACTOR; + double sright = (penetration + padding) / SCALING_FACTOR; + + // scaled stride distance + double sstride = stride / SCALING_FACTOR; + double t = 0; + + // process pairs of vertices as an edge, start with the last and + // first point + for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) { + // Get vertices and the direction vectors + const Point &a = pts[i], &b = pts[j]; + Vec2d dir = b.cast() - a.cast(); + double nrm = dir.norm(); + dir /= nrm; + Vec2d dirp(-dir(Y), dir(X)); + + // Insert start point + out.emplace_back(a); + + // dodge the start point, do not make sticks on the joins + while (t < sright) t += sright; + double tend = nrm - sright; + + while (t < tend) { // insert the stick on the polygon perimeter + + // calculate the stick rectangle vertices and insert them + // into the output. + Point p1 = a + (t * dir).cast(); + Point p2 = p1 + (sright * dirp).cast(); + Point p3 = p2 + (sbottom * dir).cast(); + Point p4 = p3 + (sright * -dirp).cast(); + out.insert(out.end(), {p1, p2, p3, p4}); + + // continue along the perimeter + t += sstride; + } + + t = t - nrm; + + // Insert edge endpoint + out.emplace_back(b); + } + + // move the new points + out.shrink_to_fit(); + pts.swap(out); + }; + + transf(poly.contour.points); + for (auto &h : poly.holes) transf(h.points); + + svg.draw(poly); + svg.Close(); +} + /// Only a debug function to generate top and bottom plates from a 2D shape. /// It is not used in the algorithm directly. inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) { @@ -467,41 +625,38 @@ inline Point centroid(Points& pp) { return c; } -inline Point centroid(const ExPolygon& poly) { - return poly.contour.centroid(); +inline Point centroid(const Polygon& poly) { + return poly.centroid(); } /// A fake concave hull that is constructed by connecting separate shapes /// with explicit bridges. Bridges are generated from each shape's centroid /// to the center of the "scene" which is the centroid calculated from the shape /// centroids (a star is created...) -ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, - ThrowOnCancel throw_on_cancel = [](){}) +Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, + ThrowOnCancel throw_on_cancel = [](){}) { namespace bgi = boost::geometry::index; - using SpatElement = std::pair; + using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; - if(polys.empty()) return ExPolygons(); + if(polys.empty()) return Polygons(); + + const double max_dist = mm(max_dist_mm); - ExPolygons punion = unify(polys); // could be redundant + Polygons punion = unify(polys); // could be redundant if(punion.size() == 1) return punion; // We get the centroids of all the islands in the 2D slice Points centroids; centroids.reserve(punion.size()); std::transform(punion.begin(), punion.end(), std::back_inserter(centroids), - [](const ExPolygon& poly) { return centroid(poly); }); - - - SpatIndex boxindex; unsigned idx = 0; - std::for_each(punion.begin(), punion.end(), - [&boxindex, &idx](const ExPolygon& expo) { - BoundingBox bb(expo); - boxindex.insert(std::make_pair(bb, idx++)); - }); - + [](const Polygon& poly) { return centroid(poly); }); + SpatIndex ctrindex; + unsigned idx = 0; + for(const Point &ct : centroids) ctrindex.insert(std::make_pair(ct, idx++)); + // Centroid of the centroids of islands. This is where the additional // connector sticks are routed. Point cc = centroid(centroids); @@ -511,25 +666,32 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, idx = 0; std::transform(centroids.begin(), centroids.end(), std::back_inserter(punion), - [&punion, &boxindex, cc, max_dist_mm, &idx, throw_on_cancel] + [¢roids, &ctrindex, cc, max_dist, &idx, throw_on_cancel] (const Point& c) { throw_on_cancel(); double dx = x(c) - x(cc), dy = y(c) - y(cc); double l = std::sqrt(dx * dx + dy * dy); double nx = dx / l, ny = dy / l; - double max_dist = mm(max_dist_mm); - - ExPolygon& expo = punion[idx++]; - BoundingBox querybb(expo); - - querybb.offset(max_dist); + + Point& ct = centroids[idx]; + std::vector result; - boxindex.query(bgi::intersects(querybb), std::back_inserter(result)); - if(result.size() <= 1) return ExPolygon(); + ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result)); - ExPolygon r; - auto& ctour = r.contour.points; + double dist = max_dist; + for (const SpatElement &el : result) + if (el.second != idx) { + dist = Line(el.first, ct).length(); + break; + } + + idx++; + + if (dist >= max_dist) return Polygon(); + + Polygon r; + auto& ctour = r.points; ctour.reserve(3); ctour.emplace_back(cc); @@ -538,7 +700,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, ctour.emplace_back(c + Point( -y(d), x(d) )); ctour.emplace_back(c + Point( y(d), -x(d) )); offset(r, mm(1)); - + return r; }); @@ -576,13 +738,14 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, ExPolygons utmp = unify(tmp); - for(auto& o : utmp) { - auto&& smp = o.simplify(0.1/SCALING_FACTOR); + for(ExPolygon& o : utmp) { + auto&& smp = o.simplify(0.1/SCALING_FACTOR); // TODO: is this important? output.insert(output.end(), smp.begin(), smp.end()); } } -Contour3D create_base_pool(const ExPolygons &ground_layer, +Contour3D create_base_pool(const Polygons &ground_layer, + const ExPolygons &obj_self_pad = {}, const PoolConfig& cfg = PoolConfig()) { // for debugging: @@ -597,7 +760,7 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, // serve as the bottom plate of the pad. We will offset this concave hull // and then offset back the result with clipper with rounding edges ON. This // trick will create a nice rounded pad shape. - ExPolygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); + Polygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); const double thickness = cfg.min_wall_thickness_mm; const double wingheight = cfg.min_wall_height_mm; @@ -617,42 +780,37 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, Contour3D pool; - for(ExPolygon& concaveh : concavehs) { - if(concaveh.contour.points.empty()) return pool; - - // Get rid of any holes in the concave hull output. - concaveh.holes.clear(); + for(Polygon& concaveh : concavehs) { + if(concaveh.points.empty()) return pool; // Here lies the trick that does the smoothing only with clipper offset // calls. The offset is configured to round edges. Inner edges will // be rounded because we offset twice: ones to get the outer (top) plate // and again to get the inner (bottom) plate auto outer_base = concaveh; - outer_base.holes.clear(); offset(outer_base, s_safety_dist + s_wingdist + s_thickness); - ExPolygon bottom_poly = outer_base; - bottom_poly.holes.clear(); + ExPolygon bottom_poly; bottom_poly.contour = outer_base; offset(bottom_poly, -s_bottom_offs); // Punching a hole in the top plate for the cavity ExPolygon top_poly; ExPolygon middle_base; ExPolygon inner_base; - top_poly.contour = outer_base.contour; + top_poly.contour = outer_base; if(wingheight > 0) { - inner_base = outer_base; + inner_base.contour = outer_base; offset(inner_base, -(s_thickness + s_wingdist + s_eradius)); - middle_base = outer_base; + middle_base.contour = outer_base; offset(middle_base, -s_thickness); top_poly.holes.emplace_back(middle_base.contour); auto& tph = top_poly.holes.back().points; std::reverse(tph.begin(), tph.end()); } - ExPolygon ob = outer_base; double wh = 0; + ExPolygon ob; ob.contour = outer_base; double wh = 0; // now we will calculate the angle or portion of the circle from // pi/2 that will connect perfectly with the bottom plate. @@ -713,11 +871,56 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, wh, -wingdist, thrcl)); } - // Now we need to triangulate the top and bottom plates as well as the - // cavity bottom plate which is the same as the bottom plate but it is - // elevated by the thickness. - pool.merge(triangulate_expolygon_3d(top_poly)); - pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); + if (cfg.embed_object) { + ExPolygons pp = diff_ex(to_polygons(bottom_poly), + to_polygons(obj_self_pad)); + + // Generate outer walls + auto fp = [](const Point &p, Point::coord_type z) { + return unscale(x(p), y(p), z); + }; + + auto straight_walls = [&pool, s_thickness, fp](const Polygon &cntr) + { + auto lines = cntr.lines(); + bool cclk = cntr.is_counter_clockwise(); + + for (auto &l : lines) { + auto s = coord_t(pool.points.size()); + pool.points.emplace_back(fp(l.a, -s_thickness)); + pool.points.emplace_back(fp(l.b, -s_thickness)); + pool.points.emplace_back(fp(l.a, 0)); + pool.points.emplace_back(fp(l.b, 0)); + + if(cclk) { + pool.indices.emplace_back(s + 3, s + 1, s); + pool.indices.emplace_back(s + 2, s + 3, s); + } else { + pool.indices.emplace_back(s, s + 1, s + 3); + pool.indices.emplace_back(s, s + 3, s + 2); + } + } + }; + + for (ExPolygon &ep : pp) { + pool.merge(triangulate_expolygon_3d(ep)); + pool.merge(triangulate_expolygon_3d(ep, -fullheight, true)); + + for (auto &h : ep.holes) straight_walls(h); + } + + // Skip the outer contour. TODO: make sure the first in the list + // IS the outer contour. + for (auto it = std::next(pp.begin()); it != pp.end(); ++it) + straight_walls(it->contour); + + } else { + // Now we need to triangulate the top and bottom plates as well as + // the cavity bottom plate which is the same as the bottom plate + // but it is elevated by the thickness. + pool.merge(triangulate_expolygon_3d(top_poly)); + pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); + } if(wingheight > 0) pool.merge(triangulate_expolygon_3d(inner_base, -wingheight)); @@ -727,8 +930,8 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, return pool; } -void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, - const PoolConfig& cfg) +void create_base_pool(const Polygons &ground_layer, TriangleMesh& out, + const ExPolygons &holes, const PoolConfig& cfg) { @@ -738,7 +941,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, // std::fstream fout("pad_debug.obj", std::fstream::out); // if(fout.good()) pool.to_obj(fout); - out.merge(mesh(create_base_pool(ground_layer, cfg))); + out.merge(mesh(create_base_pool(ground_layer, holes, cfg))); } } diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 3c88e58c85..0ed26b6e7d 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -8,7 +8,9 @@ namespace Slic3r { class ExPolygon; +class Polygon; using ExPolygons = std::vector; +using Polygons = std::vector; class TriangleMesh; @@ -23,12 +25,24 @@ void base_plate(const TriangleMesh& mesh, // input mesh float layerheight = 0.05f, // The sampling height ThrowOnCancel thrfn = [](){}); // Will be called frequently +// Function to cut tiny connector cavities for a given polygon. The input poly +// will be offsetted by "padding" and small rectangle shaped cavities will be +// inserted along the perimeter in every "stride" distance. The stick rectangles +// will have a with about "stick_width". The input dimensions are in world +// measure, not the scaled clipper units. +void offset_with_breakstick_holes(ExPolygon& poly, + double padding, + double stride, + double stick_width, + double penetration = 0.0); + struct PoolConfig { double min_wall_thickness_mm = 2; double min_wall_height_mm = 5; double max_merge_distance_mm = 50; double edge_radius_mm = 1; double wall_slope = std::atan(1.0); // Universal constant for Pi/4 + bool embed_object = false; ThrowOnCancel throw_on_cancel = [](){}; @@ -42,8 +56,9 @@ struct PoolConfig { }; /// Calculate the pool for the mesh for SLA printing -void create_base_pool(const ExPolygons& base_plate, +void create_base_pool(const Polygons& base_plate, TriangleMesh& output_mesh, + const ExPolygons& holes, const PoolConfig& = PoolConfig()); /// TODO: Currently the base plate of the pool will have half the height of the diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index 855802759e..2b72aa92c3 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -72,6 +72,7 @@ public: ~EigenMesh3D(); inline double ground_level() const { return m_ground_level; } + inline double& ground_level() { return m_ground_level; } inline const Eigen::MatrixXd& V() const { return m_V; } inline const Eigen::MatrixXi& F() const { return m_F; } @@ -149,6 +150,12 @@ public: #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ double squared_distance(const Vec3d& p, int& i, Vec3d& c) const; + inline double squared_distance(const Vec3d &p) const + { + int i; + Vec3d c; + return squared_distance(p, i, c); + } }; diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index cb2001024d..43ffaed869 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -71,6 +72,8 @@ const double SupportConfig::normal_cutoff_angle = 150.0 * M_PI / 180.0; // The shortest distance of any support structure from the model surface const double SupportConfig::safety_distance_mm = 0.5; +const double SupportConfig::pillar_base_safety_distance_mm = 0.5; + const double SupportConfig::max_solo_pillar_height_mm = 15.0; const double SupportConfig::max_dual_pillar_height_mm = 35.0; const double SupportConfig::optimizer_rel_score_diff = 1e-6; @@ -413,7 +416,7 @@ struct Pillar { assert(steps > 0); height = jp(Z) - endp(Z); - if(height > 0) { // Endpoint is below the starting point + if(height > EPSILON) { // Endpoint is below the starting point // We just create a bridge geometry with the pillar parameters and // move the data. @@ -556,28 +559,47 @@ struct Pad { PoolConfig cfg; double zlevel = 0; - Pad() {} + Pad() = default; Pad(const TriangleMesh& object_support_mesh, - const ExPolygons& baseplate, + const ExPolygons& modelbase, double ground_level, const PoolConfig& pcfg) : cfg(pcfg), - zlevel(ground_level + - (sla::get_pad_fullheight(pcfg) - sla::get_pad_elevation(pcfg)) ) + zlevel(ground_level + + sla::get_pad_fullheight(pcfg) - + sla::get_pad_elevation(pcfg)) { - ExPolygons basep; + Polygons basep; cfg.throw_on_cancel(); - + // The 0.1f is the layer height with which the mesh is sampled and then // the layers are unified into one vector of polygons. - base_plate(object_support_mesh, basep, + ExPolygons platetmp; + base_plate(object_support_mesh, platetmp, float(cfg.min_wall_height_mm + cfg.min_wall_thickness_mm), 0.1f, pcfg.throw_on_cancel); + + // We don't need the holes for the base plate from the supports + for (const ExPolygon &bp : platetmp) basep.emplace_back(bp.contour); + for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); + + if(pcfg.embed_object) { + + auto modelbase_sticks = modelbase; + for(auto& poly : modelbase_sticks) + sla::offset_with_breakstick_holes( + poly, + SupportConfig::pillar_base_safety_distance_mm, // padding + 10, // stride (mm) + 0.3, // stick_width (mm) + 0.1); // penetration (mm) - for(auto& bp : baseplate) basep.emplace_back(bp); + create_base_pool(basep, tmesh, modelbase_sticks, cfg); + } else { + create_base_pool(basep, tmesh, {}, cfg); + } - create_base_pool(basep, tmesh, cfg); tmesh.translate(0, 0, float(zlevel)); } @@ -763,9 +785,9 @@ public: } const Pad& create_pad(const TriangleMesh& object_supports, - const ExPolygons& baseplate, + const ExPolygons& modelbase, const PoolConfig& cfg) { - m_pad = Pad(object_supports, baseplate, ground_level, cfg); + m_pad = Pad(object_supports, modelbase, ground_level, cfg); return m_pad; } @@ -1149,7 +1171,7 @@ class SLASupportTree::Algorithm { auto hr = m.query_ray_hit(p + sd*dir, dir); if(ins_check && hr.is_inside()) { - if(hr.distance() > r + sd) hits[i] = HitResult(0.0); + if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0); else { // re-cast the ray from the outside of the object auto hr2 = @@ -1264,9 +1286,12 @@ class SLASupportTree::Algorithm { // For connecting a head to a nearby pillar. bool connect_to_nearpillar(const Head& head, long nearpillar_id) { - - auto nearpillar = [this, nearpillar_id]() { return m_result.pillar(nearpillar_id); }; - if(nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; + + auto nearpillar = [this, nearpillar_id]() { + return m_result.pillar(nearpillar_id); + }; + + if (nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; Vec3d headjp = head.junction_point(); Vec3d nearjp_u = nearpillar().startpoint(); @@ -1369,6 +1394,108 @@ class SLASupportTree::Algorithm { return nearest_id >= 0; } + + // This is a proxy function for pillar creation which will mind the gap + // between the pad and the model bottom in zero elevation mode. + void create_ground_pillar(const Vec3d &jp, + const Vec3d &sourcedir, + double radius, + int head_id = -1) + { + // People were killed for this number (seriously) + static const double SQR2 = std::sqrt(2.0); + + double gndlvl = m_result.ground_level; + Vec3d endp = {jp(X), jp(Y), gndlvl}; + double sd = SupportConfig::pillar_base_safety_distance_mm; + int pillar_id = -1; + double min_dist = sd + m_cfg.base_radius_mm + EPSILON; + double dist = 0; + bool can_add_base = true; + bool normal_mode = true; + + if (m_cfg.object_elevation_mm < EPSILON + && (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) { + // Get the distance from the mesh. This can be later optimized + // to get the distance in 2D plane because we are dealing with + // the ground level only. + + normal_mode = false; + double mv = min_dist - dist; + double azimuth = std::atan2(sourcedir(Y), sourcedir(X)); + double sinpolar = std::sin(PI - m_cfg.bridge_slope); + double cospolar = std::cos(PI - m_cfg.bridge_slope); + double cosazm = std::cos(azimuth); + double sinazm = std::sin(azimuth); + + auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar) + .normalized(); + + using namespace libnest2d::opt; + StopCriteria scr; + scr.stop_score = min_dist; + SubplexOptimizer solver(scr); + + auto result = solver.optimize_max( + [this, dir, jp, gndlvl](double mv) { + Vec3d endp = jp + SQR2 * mv * dir; + endp(Z) = gndlvl; + return std::sqrt(m_mesh.squared_distance(endp)); + }, + initvals(mv), bound(0.0, 2 * min_dist)); + + mv = std::get<0>(result.optimum); + endp = jp + std::sqrt(2) * mv * dir; + Vec3d pgnd = {endp(X), endp(Y), gndlvl}; + can_add_base = result.score > min_dist; + + // We have to check if the bridge is feasible. + if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) { + normal_mode = true; + endp = {jp(X), jp(Y), gndlvl}; + } + else { + // If the new endpoint is below ground, do not make a pillar + if (endp(Z) < gndlvl) + endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off + else { + Pillar &plr = m_result.add_pillar(endp, pgnd, radius); + + if (can_add_base) + plr.add_base(m_cfg.base_height_mm, + m_cfg.base_radius_mm); + + pillar_id = plr.id; + } + + m_result.add_bridge(jp, endp, radius); + m_result.add_junction(endp, radius); + + // Add a degenerated pillar and the bridge. + // The degenerate pillar will have zero length and it will + // prevent from queries of head_pillar() to have non-existing + // pillar when the head should have one. + if (head_id >= 0) + m_result.add_pillar(unsigned(head_id), jp, radius); + } + } + + if (normal_mode) { + Pillar &plr = head_id >= 0 + ? m_result.add_pillar(unsigned(head_id), + endp, + radius) + : m_result.add_pillar(jp, endp, radius); + + if (can_add_base) + plr.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + + pillar_id = plr.id; + } + + if(pillar_id >= 0) // Save the pillar endpoint in the spatial index + m_pillar_index.insert(endp, pillar_id); + } public: @@ -1447,9 +1574,9 @@ public: // (Quaternion::FromTwoVectors) and apply the rotation to the // arrow head. - double z = n(2); - double r = 1.0; // for normalized vector - double polar = std::acos(z / r); + double z = n(2); + double r = 1.0; // for normalized vector + double polar = std::acos(z / r); double azimuth = std::atan2(n(1), n(0)); // skip if the tilt is not sane @@ -1473,14 +1600,14 @@ public: std::cos(polar)).normalized(); // check available distance - double t = pinhead_mesh_intersect( - hp, // touching point - nn, // normal - pin_r, - m_cfg.head_back_radius_mm, - w); + EigenMesh3D::hit_result t + = pinhead_mesh_intersect(hp, // touching point + nn, // normal + pin_r, + m_cfg.head_back_radius_mm, + w); - if(t <= w) { + if(t.distance() <= w) { // Let's try to optimize this angle, there might be a // viable normal that doesn't collide with the model @@ -1523,12 +1650,17 @@ public: // save the verified and corrected normal m_support_nmls.row(fidx) = nn; - if(t > w) { - // mark the point for needing a head. - m_iheads.emplace_back(fidx); - } else if( polar >= 3*PI/4 ) { - // Headless supports do not tilt like the headed ones so - // the normal should point almost to the ground. + if (t.distance() > w) { + // Check distance from ground, we might have zero elevation. + if (hp(Z) + w * nn(Z) < m_result.ground_level) { + m_iheadless.emplace_back(fidx); + } else { + // mark the point for needing a head. + m_iheads.emplace_back(fidx); + } + } else if (polar >= 3 * PI / 4) { + // Headless supports do not tilt like the headed ones + // so the normal should point almost to the ground. m_iheadless.emplace_back(fidx); } } @@ -1594,16 +1726,22 @@ public: // from each other in the XY plane to not cross their pillar bases // These clusters of support points will join in one pillar, // possibly in their centroid support point. + auto pointfn = [this](unsigned i) { return m_result.head(i).junction_point(); }; - auto predicate = [this](const SpatElement& e1, const SpatElement& e2) { + + auto predicate = [this](const SpatElement &e1, + const SpatElement &e2) { double d2d = distance(to_2d(e1.first), to_2d(e2.first)); double d3d = distance(e1.first, e2.first); - return d2d < 2 * m_cfg.base_radius_mm && - d3d < m_cfg.max_bridge_length_mm; + return d2d < 2 * m_cfg.base_radius_mm + && d3d < m_cfg.max_bridge_length_mm; }; - m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate, + + m_pillar_clusters = cluster(ground_head_indices, + pointfn, + predicate, m_cfg.max_bridges_on_pillar); } @@ -1615,7 +1753,7 @@ public: void routing_to_ground() { const double pradius = m_cfg.head_back_radius_mm; - const double gndlvl = m_result.ground_level; + // const double gndlvl = m_result.ground_level; ClusterEl cl_centroids; cl_centroids.reserve(m_pillar_clusters.size()); @@ -1648,13 +1786,8 @@ public: Head& h = m_result.head(hid); h.transform(); - Vec3d p = h.junction_point(); p(Z) = gndlvl; - auto& plr = m_result.add_pillar(hid, p, h.r_back_mm) - .add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - // Save the pillar endpoint and the pillar id in the spatial index - m_pillar_index.insert(plr.endpoint(), unsigned(plr.id)); + create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id); } // now we will go through the clusters ones again and connect the @@ -1681,15 +1814,12 @@ public: !search_pillar_and_connect(sidehead)) { Vec3d pstart = sidehead.junction_point(); - Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; + //Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; // Could not find a pillar, create one - auto& pillar = m_result.add_pillar(unsigned(sidehead.id), - pend, pradius) - .add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - - // connects to ground, eligible for bridging - m_pillar_index.insert(pend, unsigned(pillar.id)); + create_ground_pillar(pstart, + sidehead.dir, + pradius, + sidehead.id); } } } @@ -1718,12 +1848,7 @@ public: m_result.add_bridge(hjp, endp, head.r_back_mm); m_result.add_junction(endp, head.r_back_mm); - auto groundp = endp; - groundp(Z) = m_result.ground_level; - auto& newpillar = m_result.add_pillar(endp, groundp, head.r_back_mm) - .add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - m_pillar_index.insert(groundp, unsigned(newpillar.id)); + this->create_ground_pillar(endp, dir, head.r_back_mm); }; std::vector modelpillars; @@ -1883,6 +2008,28 @@ public: m_pillar_index.insert(pillar.endpoint(), pillid); } } + + // Helper function for interconnect_pillars where pairs of already connected + // pillars should be checked for not to be processed again. This can be done + // in O(log) or even constant time with a set or an unordered set of hash + // values uniquely representing a pair of integers. The order of numbers + // within the pair should not matter, it has the same unique hash. + template static I pairhash(I a, I b) + { + using std::ceil; using std::log2; using std::max; using std::min; + + static_assert(std::is_integral::value, + "This function works only for integral types."); + + I g = min(a, b), l = max(a, b); + + auto bits_g = g ? int(ceil(log2(g))) : 0; + + // Assume the hash will fit into the output variable + assert((l ? (ceil(log2(l))) : 0) + bits_g < int(sizeof(I) * CHAR_BIT)); + + return (l << bits_g) + g; + } void interconnect_pillars() { // Now comes the algorithm that connects pillars with each other. @@ -1900,17 +2047,23 @@ public: double min_height_ratio = 0.5; std::set pairs; - + + // A function to connect one pillar with its neighbors. THe number of + // neighbors is given in the configuration. This function if called + // for every pillar in the pillar index. A pair of pillar will not + // be connected multiple times this is ensured by the 'pairs' set which + // remembers the processed pillar pairs auto cascadefn = [this, d, &pairs, min_height_ratio, H1] (const SpatElement& el) { - Vec3d qp = el.first; - - const Pillar& pillar = m_result.pillar(el.second); + Vec3d qp = el.first; // endpoint of the pillar + const Pillar& pillar = m_result.pillar(el.second); // actual pillar + + // Get the max number of neighbors a pillar should connect to unsigned neighbors = m_cfg.pillar_cascade_neighbors; - // connections are enough for one pillar + // connections are already enough for the pillar if(pillar.links >= neighbors) return; // Query all remaining points within reach @@ -1924,21 +2077,21 @@ public: return distance(e1.first, qp) < distance(e2.first, qp); }); - for(auto& re : qres) { + for(auto& re : qres) { // process the queried neighbors - if(re.second == el.second) continue; + if(re.second == el.second) continue; // Skip self auto a = el.second, b = re.second; - // I hope that the area of a square is never equal to its - // circumference - auto hashval = 2 * (a + b) + a * b; - + // Get unique hash for the given pair (order doesn't matter) + auto hashval = pairhash(a, b); + + // Search for the pair amongst the remembered pairs if(pairs.find(hashval) != pairs.end()) continue; const Pillar& neighborpillar = m_result.pillars()[re.second]; - // this neighbor is occupied + // this neighbor is occupied, skip if(neighborpillar.links >= neighbors) continue; if(interconnect(pillar, neighborpillar)) { @@ -1960,47 +2113,75 @@ public: if(pillar.links >= neighbors) break; } }; - + + // Run the cascade for the pillars in the index m_pillar_index.foreach(cascadefn); - + + // We would be done here if we could allow some pillars to not be + // connected with any neighbors. But this might leave the support tree + // unprintable. + // + // The current solution is to insert additional pillars next to these + // lonely pillars. One or even two additional pillar might get inserted + // depending on the length of the lonely pillar. + size_t pillarcount = m_result.pillars().size(); - + + // Again, go through all pillars, this time in the whole support tree + // not just the index. for(size_t pid = 0; pid < pillarcount; pid++) { auto pillar = [this, pid]() { return m_result.pillar(pid); }; - + + // Decide how many additional pillars will be needed: + unsigned needpillars = 0; - if(pillar().bridges > m_cfg.max_bridges_on_pillar) needpillars = 3; - else if(pillar().links < 2 && pillar().height > H2) { + if (pillar().bridges > m_cfg.max_bridges_on_pillar) + needpillars = 3; + else if (pillar().links < 2 && pillar().height > H2) { // Not enough neighbors to support this pillar needpillars = 2 - pillar().links; - } - else if(pillar().links < 1 && pillar().height > H1) { + } else if (pillar().links < 1 && pillar().height > H1) { // No neighbors could be found and the pillar is too long. needpillars = 1; } - // Search for new pillar locations - bool found = false; - double alpha = 0; // goes to 2Pi - double r = 2 * m_cfg.base_radius_mm; - Vec3d pillarsp = pillar().startpoint(); + // Search for new pillar locations: + + bool found = false; + double alpha = 0; // goes to 2Pi + double r = 2 * m_cfg.base_radius_mm; + Vec3d pillarsp = pillar().startpoint(); + + // temp value for starting point detection Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r); - std::vector tv(needpillars, false); - std::vector spts(needpillars); + // A vector of bool for placement feasbility + std::vector canplace(needpillars, false); + std::vector spts(needpillars); // vector of starting points + + double gnd = m_result.ground_level; + double min_dist = SupportConfig::pillar_base_safety_distance_mm + + m_cfg.base_radius_mm + EPSILON; + while(!found && alpha < 2*PI) { - - for(unsigned n = 0; n < needpillars; n++) { - double a = alpha + n * PI/3; - Vec3d s = sp; + for (unsigned n = 0; n < needpillars; n++) { + double a = alpha + n * PI / 3; + Vec3d s = sp; s(X) += std::cos(a) * r; s(Y) += std::sin(a) * r; spts[n] = s; + + // Check the path vertically down auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); - tv[n] = std::isinf(hr.distance()); + + // If the path is clear, check for pillar base collisions + canplace[n] = std::isinf(hr.distance()) + && m_mesh.squared_distance({s(X), s(Y), gnd}) + > min_dist; } - found = std::all_of(tv.begin(), tv.end(), [](bool v){return v;}); + found = std::all_of(canplace.begin(), canplace.end(), + [](bool v) { return v; }); // 20 angles will be tried... alpha += 0.1 * PI; @@ -2010,7 +2191,7 @@ public: newpills.reserve(needpillars); if(found) for(unsigned n = 0; n < needpillars; n++) { - Vec3d s = spts[n]; double gnd = m_result.ground_level; + Vec3d s = spts[n]; Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); @@ -2075,9 +2256,12 @@ public: // This is only for checking double idist = bridge_mesh_intersect(sph, dir, R, true); double dist = ray_mesh_intersect(sj, dir); + if (std::isinf(dist)) + dist = sph(Z) - m_result.ground_level - HWIDTH_MM; - if(std::isinf(idist) || std::isnan(idist) || idist < 2*R || - std::isinf(dist) || std::isnan(dist) || dist < 2*R) { + if(std::isnan(idist) || idist < 2*R || + std::isnan(dist) || dist < 2*R) + { BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" << " support stick at: " << sj.transpose(); @@ -2214,7 +2398,9 @@ bool SLASupportTree::generate(const std::vector &support_points, return pc == ABORT; } -SLASupportTree::SLASupportTree(): m_impl(new Impl()) {} +SLASupportTree::SLASupportTree(double gnd_lvl): m_impl(new Impl()) { + m_impl->ground_level = gnd_lvl; +} const TriangleMesh &SLASupportTree::merged_mesh() const { @@ -2226,7 +2412,7 @@ void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { outmesh.merge(get_pad()); } -SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const +std::vector SLASupportTree::slice(float layerh, float init_layerh) const { if(init_layerh < 0) init_layerh = layerh; auto& stree = get(); @@ -2247,34 +2433,29 @@ SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const fullmesh.merge(get_pad()); fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this TriangleMeshSlicer slicer(&fullmesh); - SlicedSupports ret; + std::vector ret; slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn); return ret; } -SlicedSupports SLASupportTree::slice(const std::vector &heights, +std::vector SLASupportTree::slice(const std::vector &heights, float cr) const { TriangleMesh fullmesh = m_impl->merged_mesh(); fullmesh.merge(get_pad()); fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this TriangleMeshSlicer slicer(&fullmesh); - SlicedSupports ret; + std::vector ret; slicer.slice(heights, cr, &ret, get().ctl().cancelfn); return ret; } -const TriangleMesh &SLASupportTree::add_pad(const SliceLayer& baseplate, +const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase, const PoolConfig& pcfg) const { -// PoolConfig pcfg; -// pcfg.min_wall_thickness_mm = min_wall_thickness_mm; -// pcfg.min_wall_height_mm = min_wall_height_mm; -// pcfg.max_merge_distance_mm = max_merge_distance_mm; -// pcfg.edge_radius_mm = edge_radius_mm; - return m_impl->create_pad(merged_mesh(), baseplate, pcfg).tmesh; + return m_impl->create_pad(merged_mesh(), modelbase, pcfg).tmesh; } const TriangleMesh &SLASupportTree::get_pad() const diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 66677e4d7a..1602316e8c 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -24,10 +24,11 @@ class TriangleMesh; class Model; class ModelInstance; class ModelObject; +class Polygon; class ExPolygon; -using SliceLayer = std::vector; -using SlicedSupports = std::vector; +using Polygons = std::vector; +using ExPolygons = std::vector; namespace sla { @@ -90,6 +91,10 @@ struct SupportConfig { // The shortest distance of any support structure from the model surface static const double safety_distance_mm; + + // The shortest distance between a pillar base perimeter from the model + // body. This is only useful when elevation is set to zero. + static const double pillar_base_safety_distance_mm; static const double max_solo_pillar_height_mm; static const double max_dual_pillar_height_mm; @@ -160,7 +165,7 @@ class SLASupportTree { public: - SLASupportTree(); + SLASupportTree(double ground_level = 0.0); SLASupportTree(const std::vector& pts, const EigenMesh3D& em, @@ -179,12 +184,16 @@ public: void merged_mesh_with_pad(TriangleMesh&) const; /// Get the sliced 2d layers of the support geometry. - SlicedSupports slice(float layerh, float init_layerh = -1.0) const; + std::vector slice(float layerh, float init_layerh = -1.0) const; - SlicedSupports slice(const std::vector&, float closing_radius) const; + std::vector slice(const std::vector&, float closing_radius) const; /// Adding the "pad" (base pool) under the supports - const TriangleMesh& add_pad(const SliceLayer& baseplate, + /// modelbase will be used according to the embed_object flag in PoolConfig. + /// If set, the plate will interpreted as the model's intrinsic pad. + /// Otherwise, the modelbase will be unified with the base plate calculated + /// from the supports. + const TriangleMesh& add_pad(const ExPolygons& modelbase, const PoolConfig& pcfg) const; /// Get the pad geometry diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index f6a1c429e5..f4599a2661 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -32,8 +32,8 @@ class SLAPrintObject::SupportData { public: sla::EigenMesh3D emesh; // index-triangle representation std::vector support_points; // all the support points (manual/auto) - SupportTreePtr support_tree_ptr; // the supports - SlicedSupports support_slices; // sliced supports + SupportTreePtr support_tree_ptr; // the supports + std::vector support_slices; // sliced supports inline SupportData(const TriangleMesh& trmesh): emesh(trmesh) {} }; @@ -471,7 +471,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) int n_object_steps = int(params.to_object_step) + 1; if (n_object_steps == 0) - n_object_steps = (int)slaposCount; + n_object_steps = int(slaposCount); if (params.single_model_object.valid()) { // Find the print object to be processed with priority. @@ -486,7 +486,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) // Find out whether the priority print object is being currently processed. bool running = false; for (int istep = 0; istep < n_object_steps; ++ istep) { - if (! print_object->m_stepmask[istep]) + if (! print_object->m_stepmask[size_t(istep)]) // Step was skipped, cancel. break; if (print_object->is_step_started_unguarded(SLAPrintObjectStep(istep))) { @@ -502,7 +502,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) if (params.single_model_instance_only) { // Suppress all the steps of other instances. for (SLAPrintObject *po : m_objects) - for (int istep = 0; istep < (int)slaposCount; ++ istep) + for (size_t istep = 0; istep < slaposCount; ++ istep) po->m_stepmask[istep] = false; } else if (! running) { // Swap the print objects, so that the selected print_object is first in the row. @@ -512,15 +512,15 @@ void SLAPrint::set_task(const TaskParams ¶ms) } // and set the steps for the current object. for (int istep = 0; istep < n_object_steps; ++ istep) - print_object->m_stepmask[istep] = true; - for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) - print_object->m_stepmask[istep] = false; + print_object->m_stepmask[size_t(istep)] = true; + for (int istep = n_object_steps; istep < int(slaposCount); ++ istep) + print_object->m_stepmask[size_t(istep)] = false; } else { // Slicing all objects. bool running = false; for (SLAPrintObject *print_object : m_objects) for (int istep = 0; istep < n_object_steps; ++ istep) { - if (! print_object->m_stepmask[istep]) { + if (! print_object->m_stepmask[size_t(istep)]) { // Step may have been skipped. Restart. goto loop_end; } @@ -536,8 +536,8 @@ void SLAPrint::set_task(const TaskParams ¶ms) this->call_cancel_callback(); for (SLAPrintObject *po : m_objects) { for (int istep = 0; istep < n_object_steps; ++ istep) - po->m_stepmask[istep] = true; - for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) + po->m_stepmask[size_t(istep)] = true; + for (auto istep = size_t(n_object_steps); istep < slaposCount; ++ istep) po->m_stepmask[istep] = false; } } @@ -555,9 +555,9 @@ void SLAPrint::set_task(const TaskParams ¶ms) void SLAPrint::finalize() { for (SLAPrintObject *po : m_objects) - for (int istep = 0; istep < (int)slaposCount; ++ istep) + for (size_t istep = 0; istep < slaposCount; ++ istep) po->m_stepmask[istep] = true; - for (int istep = 0; istep < (int)slapsCount; ++ istep) + for (size_t istep = 0; istep < slapsCount; ++ istep) m_stepmask[istep] = true; } @@ -599,17 +599,29 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { return scfg; } +bool use_builtin_pad(const SLAPrintObjectConfig& c) { + return c.support_object_elevation.getFloat() <= EPSILON && + c.pad_enable.getBool(); +} + sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { sla::PoolConfig pcfg; pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat(); - pcfg.wall_slope = c.pad_wall_slope.getFloat(); - pcfg.edge_radius_mm = c.pad_edge_radius.getFloat(); + pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0; + + // We do not support radius for now + pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat(); + pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat(); pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); + // set builtin pad implicitly ON + pcfg.embed_object = use_builtin_pad(c); + return pcfg; } + } std::string SLAPrint::validate() const @@ -632,8 +644,10 @@ std::string SLAPrint::validate() const cfg.head_width_mm + 2 * cfg.head_back_radius_mm - cfg.head_penetration_mm; + + double elv = cfg.object_elevation_mm; - if(supports_en && pinhead_width > cfg.object_elevation_mm) + if(supports_en && elv > EPSILON && elv < pinhead_width ) return L("Elevation is too low for object."); } @@ -818,23 +832,55 @@ void SLAPrint::process() BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " << po.m_supportdata->support_points.size(); - // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports - m_report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); + // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass + // the update status to GLGizmoSlaSupports + m_report_status(*this, + -1, + L("Generating support points"), + SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); } else { - // There are either some points on the front-end, or the user removed them on purpose. No calculation will be done. + // There are either some points on the front-end, or the user + // removed them on purpose. No calculation will be done. po.m_supportdata->support_points = po.transformed_support_points(); } + + // If the builtin pad mode is engaged, we have to filter out all the + // points that are on the bottom of the object + if(use_builtin_pad(po.m_config)) { + double gnd = po.m_supportdata->emesh.ground_level(); + auto & pts = po.m_supportdata->support_points; + + // get iterator to the reorganized vector end + auto endit = std::remove_if( + pts.begin(), + pts.end(), + [&po, gnd](const sla::SupportPoint &sp) { + double diff = std::abs(gnd - double(sp.pos(Z))); + return diff <= po.m_config.pad_wall_thickness.getFloat(); + }); + + // erase all elements after the new end + pts.erase(endit, pts.end()); + } }; // In this step we create the supports auto support_tree = [this, ostepd](SLAPrintObject& po) { if(!po.m_supportdata) return; + + sla::PoolConfig pcfg = make_pool_config(po.m_config); + + if(pcfg.embed_object) + po.m_supportdata->emesh.ground_level() += pcfg.min_wall_thickness_mm; if(!po.m_config.supports_enable.getBool()) { + // Generate empty support tree. It can still host a pad - po.m_supportdata->support_tree_ptr.reset(new SLASupportTree()); + po.m_supportdata->support_tree_ptr.reset( + new SLASupportTree(po.m_supportdata->emesh.ground_level())); + return; } @@ -856,7 +902,7 @@ void SLAPrint::process() ctl.stopcondition = [this](){ return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; - + po.m_supportdata->support_tree_ptr.reset( new SLASupportTree(po.m_supportdata->support_points, po.m_supportdata->emesh, scfg, ctl)); @@ -894,27 +940,26 @@ void SLAPrint::process() if(po.m_config.pad_enable.getBool()) { - double wt = po.m_config.pad_wall_thickness.getFloat(); - double h = po.m_config.pad_wall_height.getFloat(); - double md = po.m_config.pad_max_merge_distance.getFloat(); - // Radius is disabled for now... - double er = 0; // po.m_config.pad_edge_radius.getFloat(); - double tilt = po.m_config.pad_wall_slope.getFloat() * PI / 180.0; - double lh = po.m_config.layer_height.getFloat(); - double elevation = po.m_config.support_object_elevation.getFloat(); - if(!po.m_config.supports_enable.getBool()) elevation = 0; - sla::PoolConfig pcfg(wt, h, md, er, tilt); + // Get the distilled pad configuration from the config + sla::PoolConfig pcfg = make_pool_config(po.m_config); - ExPolygons bp; - double pad_h = sla::get_pad_fullheight(pcfg); - auto&& trmesh = po.transformed_mesh(); + ExPolygons bp; // This will store the base plate of the pad. + double pad_h = sla::get_pad_fullheight(pcfg); + const TriangleMesh &trmesh = po.transformed_mesh(); // This call can get pretty time consuming auto thrfn = [this](){ throw_if_canceled(); }; - if(elevation < pad_h) { - // we have to count with the model geometry for the base plate - sla::base_plate(trmesh, bp, float(pad_h), float(lh), thrfn); + if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { + // No support (thus no elevation) or zero elevation mode + // we sometimes call it "builtin pad" is enabled so we will + // get a sample from the bottom of the mesh and use it for pad + // creation. + sla::base_plate(trmesh, + bp, + float(pad_h), + float(po.m_config.layer_height.getFloat()), + thrfn); } pcfg.throw_on_cancel = thrfn; @@ -1647,7 +1692,7 @@ double SLAPrintObject::get_elevation() const { // will be in the future, we provide the config to the get_pad_elevation // method and we will have the correct value sla::PoolConfig pcfg = make_pool_config(m_config); - ret += sla::get_pad_elevation(pcfg); + if(!pcfg.embed_object) ret += sla::get_pad_elevation(pcfg); } return ret; @@ -1661,8 +1706,9 @@ double SLAPrintObject::get_current_elevation() const if(!has_supports && !has_pad) return 0; - else if(has_supports && !has_pad) + else if(has_supports && !has_pad) { return se ? m_config.support_object_elevation.getFloat() : 0; + } return get_elevation(); } @@ -1786,7 +1832,7 @@ std::vector SLAPrintObject::transformed_support_points() cons ret.reserve(spts.size()); for(sla::SupportPoint& sp : spts) { - Vec3d transformed_pos = trafo() * Vec3d(sp.pos(0), sp.pos(1), sp.pos(2)); + Vec3d transformed_pos = trafo() * Vec3d(double(sp.pos(0)), double(sp.pos(1)), double(sp.pos(2))); ret.emplace_back(transformed_pos(0), transformed_pos(1), transformed_pos(2), sp.head_front_radius, sp.is_new_island); } From 4e0eb12ef6518e0a9e964612da626c86890d5e02 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Jun 2019 14:39:41 +0200 Subject: [PATCH 079/627] Import/export of the Layers information to/from 3MF --- src/libslic3r/Format/3mf.cpp | 181 ++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 38b34c4620..11202e7b32 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -33,6 +33,7 @@ const std::string RELATIONSHIPS_FILE = "_rels/.rels"; const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; +const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Slic3r_PE_layer_config_ranges.txt"; const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const char* MODEL_TAG = "model"; @@ -331,6 +332,7 @@ namespace Slic3r { typedef std::map IdToMetadataMap; typedef std::map IdToGeometryMap; typedef std::map> IdToLayerHeightsProfileMap; + typedef std::map IdToLayerConfigRangesMap; typedef std::map> IdToSlaSupportPointsMap; // Version of the 3mf file @@ -347,6 +349,7 @@ namespace Slic3r { CurrentConfig m_curr_config; IdToMetadataMap m_objects_metadata; IdToLayerHeightsProfileMap m_layer_heights_profiles; + IdToLayerConfigRangesMap m_layer_config_ranges; IdToSlaSupportPointsMap m_sla_support_points; std::string m_curr_metadata_name; std::string m_curr_characters; @@ -365,6 +368,7 @@ namespace Slic3r { bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename); @@ -476,6 +480,7 @@ namespace Slic3r { m_curr_config.volume_id = -1; m_objects_metadata.clear(); m_layer_heights_profiles.clear(); + m_layer_config_ranges.clear(); m_sla_support_points.clear(); m_curr_metadata_name.clear(); m_curr_characters.clear(); @@ -546,9 +551,14 @@ namespace Slic3r { if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) { - // extract slic3r lazer heights profile file + // extract slic3r layer heights profile file _extract_layer_heights_profile_config_from_archive(archive, stat); } + if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) + { + // extract slic3r layer config ranges file + _extract_layer_config_ranges_from_archive(archive, stat); + } else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) { // extract sla support points file @@ -592,6 +602,11 @@ namespace Slic3r { if (obj_layer_heights_profile != m_layer_heights_profiles.end()) model_object->layer_height_profile = obj_layer_heights_profile->second; + // m_layer_config_ranges are indexed by a 1 based model object index. + IdToLayerConfigRangesMap::iterator obj_layer_config_ranges = m_layer_config_ranges.find(object.second + 1); + if (obj_layer_config_ranges != m_layer_config_ranges.end()) + model_object->layer_config_ranges = obj_layer_config_ranges->second; + // m_sla_support_points are indexed by a 1 based model object index. IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1); if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { @@ -769,6 +784,115 @@ namespace Slic3r { } } + void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size > 0) + { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading layer config ranges data to buffer"); + return; + } + + if (buffer.back() == '|') + buffer.pop_back(); + + std::vector objects; + boost::split(objects, buffer, boost::is_any_of("|"), boost::token_compress_off); + + for (std::string& object : objects) + { + // delete all spaces + boost::replace_all(object, " ", ""); + + std::vector object_data; + boost::split(object_data, object, boost::is_any_of("*"), boost::token_compress_off); + /* there should be at least one layer config range in the object + * object_data[0] => object information + * object_data[i>=1] => range information + */ + if (object_data.size() < 2) { + add_error("Error while reading object data"); + continue; + } + + std::vector object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) { + add_error("Error while reading object id"); + continue; + } + + // get object information + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) { + add_error("Found invalid object id"); + continue; + } + + IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(object_id); + if (object_item != m_layer_config_ranges.end()) { + add_error("Found duplicated layer config range"); + continue; + } + + t_layer_config_ranges config_ranges; + + // get ranges information + for (size_t i = 1; i < object_data.size(); ++i) + { + if (object_data[i].back() == '\n') + object_data[i].pop_back(); + + std::vector range_data; + boost::split(range_data, object_data[i], boost::is_any_of("\n"), boost::token_compress_off); + /* There should be at least two options for layer config range + * range_data[0] => Z range information + * range_data[i>=1] => configuration for the range + */ + if (range_data.size() < 3) { + add_error("Found invalid layer config range"); + continue; + } + + std::vector z_range_str; + boost::split(z_range_str, range_data[0], boost::is_any_of("="), boost::token_compress_off); + if (z_range_str.size() != 2) { + add_error("Error while reading layer config range"); + continue; + } + + std::vector z_values; + boost::split(z_values, z_range_str[1], boost::is_any_of(";"), boost::token_compress_off); + if (z_values.size() != 2) { + add_error("Found invalid layer config range"); + continue; + } + + // get Z range information + t_layer_height_range z_range = { (coordf_t)std::atof(z_values[0].c_str()) , (coordf_t)std::atof(z_values[1].c_str()) }; + DynamicPrintConfig& config = config_ranges[z_range]; + + // get configuration options for the range + for (size_t j = 1; j < range_data.size(); ++j) + { + std::vector key_val; + boost::split(key_val, range_data[j], boost::is_any_of("="), boost::token_compress_off); + if (key_val.size() != 2) { + add_error("Error while reading config value"); + continue; + } + config.set_deserialize(key_val[0], key_val[1]); + } + } + + if (!config_ranges.empty()) + m_layer_config_ranges.insert(IdToLayerConfigRangesMap::value_type(object_id, config_ranges)); + } + } + } + void _3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) { if (stat.m_uncomp_size > 0) @@ -1622,6 +1746,7 @@ namespace Slic3r { bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); @@ -1682,6 +1807,16 @@ namespace Slic3r { return false; } + // Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt"). + // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. + // The index differes from the index of an object ID of an object instance of a 3MF file! + if (!_add_layer_config_ranges_file_to_archive(archive, model)) + { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + // Adds sla support points file ("Metadata/Slic3r_PE_sla_support_points.txt"). // All sla support points of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. // The index differes from the index of an object ID of an object instance of a 3MF file! @@ -2012,6 +2147,50 @@ namespace Slic3r { return true; } + bool _3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) + { + std::string out = ""; + char buffer[1024]; + + unsigned int object_cnt = 0; + for (const ModelObject* object : model.objects) + { + object_cnt++; + const t_layer_config_ranges& ranges = object->layer_config_ranges; + if (!ranges.empty()) + { + sprintf(buffer, "object_id=%d\n", object_cnt); + out += buffer; + + // Store the layer config ranges. + for (const auto& range : ranges) + { + // store minX and maxZ + sprintf(buffer, "*z_range = %f;%f\n", range.first.first, range.first.second); + out += buffer; + + // store range configuration + const DynamicPrintConfig& config = range.second; + for (const std::string& key : config.keys()) + out += " " + key + " = " + config.serialize(key) + "\n"; + } + + out += "|"; + } + } + + if (!out.empty()) + { + if (!mz_zip_writer_add_mem(&archive, LAYER_CONFIG_RANGES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) + { + add_error("Unable to add layer heights profile file to archive"); + return false; + } + } + + return true; + } + bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) { std::string out = ""; From 26fb68ba459d11b9bc0df5047ccda8fbd0b9419e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Jun 2019 14:54:31 +0200 Subject: [PATCH 080/627] Added missed include for the OSX build --- src/libslic3r/Format/3mf.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 11202e7b32..9103232a8c 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include From b7e3ee0709c9ca3a6da49f4c933d1f079dba27a0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 11 Jun 2019 15:35:09 +0200 Subject: [PATCH 081/627] Refactor, fix wall normals and gap detection. --- src/libslic3r/SLA/SLABasePool.cpp | 66 +++++++++++++++------------- src/libslic3r/SLA/SLASupportTree.cpp | 12 +++-- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 9b3f80f1a1..a882769f6f 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -857,6 +857,7 @@ Contour3D create_base_pool(const Polygons &ground_layer, if(wingheight > 0) { // Generate the smoothed edge geometry wh = 0; + ob = middle_base; if(s_eradius) pool.merge(round_edges(middle_base, r, phi - 90, // from tangent lines @@ -872,55 +873,58 @@ Contour3D create_base_pool(const Polygons &ground_layer, } if (cfg.embed_object) { - ExPolygons pp = diff_ex(to_polygons(bottom_poly), - to_polygons(obj_self_pad)); + ExPolygons bttms = diff_ex(to_polygons(bottom_poly), + to_polygons(obj_self_pad)); + + assert(!bttms.empty()); + + std::sort(bttms.begin(), bttms.end(), + [](const ExPolygon& e1, const ExPolygon& e2) { + return e1.contour.area() > e2.contour.area(); + }); + + if(wingheight > 0) inner_base.holes = bttms.front().holes; + else top_poly.holes = bttms.front().holes; - // Generate outer walls - auto fp = [](const Point &p, Point::coord_type z) { - return unscale(x(p), y(p), z); - }; - - auto straight_walls = [&pool, s_thickness, fp](const Polygon &cntr) - { + auto straight_walls = + [&pool](const Polygon &cntr, coord_t z_low, coord_t z_high) { + auto lines = cntr.lines(); - bool cclk = cntr.is_counter_clockwise(); for (auto &l : lines) { auto s = coord_t(pool.points.size()); - pool.points.emplace_back(fp(l.a, -s_thickness)); - pool.points.emplace_back(fp(l.b, -s_thickness)); - pool.points.emplace_back(fp(l.a, 0)); - pool.points.emplace_back(fp(l.b, 0)); + auto& pts = pool.points; + pts.emplace_back(unscale(l.a.x(), l.a.y(), z_low)); + pts.emplace_back(unscale(l.b.x(), l.b.y(), z_low)); + pts.emplace_back(unscale(l.a.x(), l.a.y(), z_high)); + pts.emplace_back(unscale(l.b.x(), l.b.y(), z_high)); - if(cclk) { - pool.indices.emplace_back(s + 3, s + 1, s); - pool.indices.emplace_back(s + 2, s + 3, s); - } else { - pool.indices.emplace_back(s, s + 1, s + 3); - pool.indices.emplace_back(s, s + 3, s + 2); - } + pool.indices.emplace_back(s, s + 1, s + 3); + pool.indices.emplace_back(s, s + 3, s + 2); } }; - - for (ExPolygon &ep : pp) { - pool.merge(triangulate_expolygon_3d(ep)); + + coord_t z_lo = -mm(fullheight), z_hi = -mm(wingheight); + for (ExPolygon &ep : bttms) { pool.merge(triangulate_expolygon_3d(ep, -fullheight, true)); - - for (auto &h : ep.holes) straight_walls(h); + for (auto &h : ep.holes) straight_walls(h, z_lo, z_hi); } - // Skip the outer contour. TODO: make sure the first in the list - // IS the outer contour. - for (auto it = std::next(pp.begin()); it != pp.end(); ++it) - straight_walls(it->contour); + // Skip the outer contour, triangulate the holes + for (auto it = std::next(bttms.begin()); it != bttms.end(); ++it) { + pool.merge(triangulate_expolygon_3d(*it, -wingheight)); + straight_walls(it->contour, z_lo, z_hi); + } } else { // Now we need to triangulate the top and bottom plates as well as // the cavity bottom plate which is the same as the bottom plate // but it is elevated by the thickness. - pool.merge(triangulate_expolygon_3d(top_poly)); + pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); } + + pool.merge(triangulate_expolygon_3d(top_poly)); if(wingheight > 0) pool.merge(triangulate_expolygon_3d(inner_base, -wingheight)); diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 43ffaed869..9705e86004 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -2164,7 +2164,10 @@ public: m_cfg.base_radius_mm + EPSILON; while(!found && alpha < 2*PI) { - for (unsigned n = 0; n < needpillars; n++) { + for (unsigned n = 0; + n < needpillars && (!n || canplace[n - 1]); + n++) + { double a = alpha + n * PI / 3; Vec3d s = sp; s(X) += std::cos(a) * r; @@ -2173,11 +2176,12 @@ public: // Check the path vertically down auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); + Vec3d gndsp{s(X), s(Y), gnd}; // If the path is clear, check for pillar base collisions - canplace[n] = std::isinf(hr.distance()) - && m_mesh.squared_distance({s(X), s(Y), gnd}) - > min_dist; + canplace[n] = std::isinf(hr.distance()) && + std::sqrt(m_mesh.squared_distance(gndsp)) > + min_dist; } found = std::all_of(canplace.begin(), canplace.end(), From 0bb8ee149e8702fb644a70ab1f3924ff8e6f3d71 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 11 Jun 2019 17:08:47 +0200 Subject: [PATCH 082/627] Sharing TriangleMesh objects between the front end (UI) and back end (background processing) --- src/PrusaSlicer.cpp | 8 +- src/admesh/connect.cpp | 46 +++++--- src/admesh/stl.h | 38 +++++- src/admesh/stlinit.cpp | 2 +- src/admesh/util.cpp | 2 +- src/libslic3r/Format/3mf.cpp | 27 +++-- src/libslic3r/Format/AMF.cpp | 20 ++-- src/libslic3r/Model.cpp | 118 +++++++++---------- src/libslic3r/Model.hpp | 46 +++++--- src/libslic3r/PrintObject.cpp | 14 ++- src/libslic3r/Slicing.cpp | 2 +- src/libslic3r/TriangleMesh.cpp | 72 ++++++----- src/libslic3r/TriangleMesh.hpp | 2 +- src/slic3r/GUI/3DScene.cpp | 25 ++-- src/slic3r/GUI/3DScene.hpp | 12 +- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 3 +- src/slic3r/GUI/Plater.cpp | 2 +- xs/xsp/Model.xsp | 2 +- 20 files changed, 254 insertions(+), 193 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index fef1f6e7f0..a3247b929d 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -241,8 +241,7 @@ int CLI::run(int argc, char **argv) } else if (opt_key == "cut" || opt_key == "cut_x" || opt_key == "cut_y") { std::vector new_models; for (auto &model : m_models) { - model.repair(); - model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0 + model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0 size_t num_objects = model.objects.size(); for (size_t i = 0; i < num_objects; ++ i) { @@ -301,8 +300,9 @@ int CLI::run(int argc, char **argv) } } } else if (opt_key == "repair") { - for (auto &model : m_models) - model.repair(); + // Models are repaired by default. + //for (auto &model : m_models) + // model.repair(); } else { boost::nowide::cerr << "error: option not implemented yet: " << opt_key << std::endl; return 1; diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index be782b1b7b..1a7b7965d4 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -59,7 +59,7 @@ struct HashEdge { // Ensure identical vertex ordering of equal edges. // This method is numerically robust. - if (stl_vertex_lower(*a, *b)) { + if (vertex_lower(*a, *b)) { } else { // This edge is loaded backwards. std::swap(a, b); @@ -110,6 +110,12 @@ struct HashEdge { } return true; } + +private: + inline bool vertex_lower(const stl_vertex &a, const stl_vertex &b) { + return (a(0) != b(0)) ? (a(0) < b(0)) : + ((a(1) != b(1)) ? (a(1) < b(1)) : (a(2) < b(2))); + } }; struct HashTableEdges { @@ -440,7 +446,9 @@ void stl_check_facets_exact(stl_file *stl) stl_facet &facet = stl->facet_start[i]; if (facet.vertex[0] == facet.vertex[1] || facet.vertex[1] == facet.vertex[2] || facet.vertex[0] == facet.vertex[2]) { // Remove the degenerate facet. - facet = stl->facet_start[--stl->stats.number_of_facets]; + facet = stl->facet_start[-- stl->stats.number_of_facets]; + stl->facet_start.pop_back(); + stl->neighbors_start.pop_back(); stl->stats.facets_removed += 1; stl->stats.degenerate_facets += 1; } else @@ -526,23 +534,25 @@ void stl_remove_unconnected_facets(stl_file *stl) assert(false); } - if (facet_number == -- stl->stats.number_of_facets) - // Removing the last face is easy, just forget the last face. - return; - - // Copy the face and neighborship from the last face to facet_number. - stl->facet_start[facet_number] = stl->facet_start[stl->stats.number_of_facets]; - neighbors = stl->neighbors_start[stl->stats.number_of_facets]; - // Update neighborship of faces, which used to point to the last face, now moved to facet_number. - for (int i = 0; i < 3; ++ i) - if (neighbors.neighbor[i] != -1) { - int &other_face_idx = stl->neighbors_start[neighbors.neighbor[i]].neighbor[(neighbors.which_vertex_not[i] + 1) % 3]; - if (other_face_idx != stl->stats.number_of_facets) { - BOOST_LOG_TRIVIAL(info) << "in remove_facet: neighbor = " << other_face_idx << " numfacets = " << stl->stats.number_of_facets << " this is wrong"; - return; + if (facet_number < -- stl->stats.number_of_facets) { + // Removing a face, which was not the last one. + // Copy the face and neighborship from the last face to facet_number. + stl->facet_start[facet_number] = stl->facet_start[stl->stats.number_of_facets]; + neighbors = stl->neighbors_start[stl->stats.number_of_facets]; + // Update neighborship of faces, which used to point to the last face, now moved to facet_number. + for (int i = 0; i < 3; ++ i) + if (neighbors.neighbor[i] != -1) { + int &other_face_idx = stl->neighbors_start[neighbors.neighbor[i]].neighbor[(neighbors.which_vertex_not[i] + 1) % 3]; + if (other_face_idx != stl->stats.number_of_facets) { + BOOST_LOG_TRIVIAL(info) << "in remove_facet: neighbor = " << other_face_idx << " numfacets = " << stl->stats.number_of_facets << " this is wrong"; + return; + } + other_face_idx = facet_number; } - other_face_idx = facet_number; - } + } + + stl->facet_start.pop_back(); + stl->neighbors_start.pop_back(); }; auto remove_degenerate = [stl, remove_facet](int facet) diff --git a/src/admesh/stl.h b/src/admesh/stl.h index fce23eb3f9..c419c567bd 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -128,6 +128,8 @@ struct indexed_triangle_set void clear() { indices.clear(); vertices.clear(); } std::vector indices; std::vector vertices; + //FIXME add normals once we get rid of the stl_file from TriangleMesh completely. + //std::vector normals }; extern bool stl_open(stl_file *stl, const char *file); @@ -186,7 +188,7 @@ template inline void stl_transform(stl_file *stl, const Eigen::Transform& t) { const Eigen::Matrix r = t.matrix().template block<3, 3>(0, 0); - for (size_t i = 0; i < stl->stats.number_of_facets; ++i) { + for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) { stl_facet &f = stl->facet_start[i]; for (size_t j = 0; j < 3; ++j) f.vertex[j] = (t * f.vertex[j].template cast()).template cast().eval(); @@ -199,7 +201,7 @@ inline void stl_transform(stl_file *stl, const Eigen::Transform inline void stl_transform(stl_file *stl, const Eigen::Matrix& m) { - for (size_t i = 0; i < stl->stats.number_of_facets; ++i) { + for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) { stl_facet &f = stl->facet_start[i]; for (size_t j = 0; j < 3; ++j) f.vertex[j] = (m * f.vertex[j].template cast()).template cast().eval(); @@ -209,6 +211,34 @@ inline void stl_transform(stl_file *stl, const Eigen::Matrix +extern void its_transform(indexed_triangle_set &its, T *trafo3x4) +{ + for (stl_vertex &v_dst : its.vertices) { + stl_vertex &v_dst = face.vertex[i_vertex]; + stl_vertex v_src = v_dst; + v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]); + v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]); + v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]); + } +} + +template +inline void its_transform(indexed_triangle_set &its, const Eigen::Transform& t) +{ + const Eigen::Matrix r = t.matrix().template block<3, 3>(0, 0); + for (stl_vertex &v : its.vertices) + v = (t * v.template cast()).template cast().eval(); +} + +template +inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix& m) +{ + for (stl_vertex &v : its.vertices) + v = (m * v.template cast()).template cast().eval(); +} + extern void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its); extern bool its_write_obj(const indexed_triangle_set &its, const char *file); extern bool its_write_off(const indexed_triangle_set &its, const char *file); @@ -225,10 +255,6 @@ inline void stl_normalize_vector(stl_normal &normal) { else normal *= float(1.0 / length); } -inline bool stl_vertex_lower(const stl_vertex &a, const stl_vertex &b) { - return (a(0) != b(0)) ? (a(0) < b(0)) : - ((a(1) != b(1)) ? (a(1) < b(1)) : (a(2) < b(2))); -} extern void stl_calculate_volume(stl_file *stl); extern void stl_repair(stl_file *stl, bool fixall_flag, bool exact_flag, bool tolerance_flag, float tolerance, bool increment_flag, float increment, bool nearby_flag, int iterations, bool remove_unconnected_flag, bool fill_holes_flag, bool normal_directions_flag, bool normal_values_flag, bool reverse_all_flag, bool verbose_flag); diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index 24fbe9edca..0cc6e50c70 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -147,7 +147,7 @@ static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first) rewind(fp); char normal_buf[3][32]; - for (uint32_t i = first_facet; i < stl->stats.number_of_facets; ++i) { + for (uint32_t i = first_facet; i < stl->stats.number_of_facets; ++ i) { stl_facet facet; if (stl->stats.type == binary) { diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index 6fff8a8ed5..70f4ffc27d 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -108,7 +108,7 @@ void stl_scale_versor(stl_file *stl, const stl_vertex &versor) static void calculate_normals(stl_file *stl) { stl_normal normal; - for (uint32_t i = 0; i < stl->stats.number_of_facets; i++) { + for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { stl_calculate_normal(normal, &stl->facet_start[i]); stl_normalize_vector(normal); stl->facet_start[i].normal = normal; diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 6544265281..4793586e3c 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1489,10 +1489,10 @@ namespace Slic3r { } // splits volume out of imported geometry - unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1; - ModelVolume* volume = object.add_volume(TriangleMesh()); - stl_file& stl = volume->mesh.stl; - stl.stats.type = inmemory; + TriangleMesh triangle_mesh; + stl_file &stl = triangle_mesh.stl; + unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1; + stl.stats.type = inmemory; stl.stats.number_of_facets = (uint32_t)triangles_count; stl.stats.original_num_facets = (int)stl.stats.number_of_facets; stl_allocate(&stl); @@ -1509,9 +1509,11 @@ namespace Slic3r { } } - stl_get_size(&stl); - volume->mesh.repair(); - volume->center_geometry(); + stl_get_size(&stl); + triangle_mesh.repair(); + + ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); + volume->center_geometry_after_creation(); volume->calculate_convex_hull(); // apply volume's name and config data @@ -1879,11 +1881,14 @@ namespace Slic3r { if (volume == nullptr) continue; + if (!volume->mesh().repaired) + throw std::runtime_error("store_3mf() requires repair()"); + if (!volume->mesh().has_shared_vertices()) + throw std::runtime_error("store_3mf() requires shared vertices"); + volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first; - volume->mesh.require_shared_vertices(); - - const indexed_triangle_set &its = volume->mesh.its; + const indexed_triangle_set &its = volume->mesh().its; if (its.vertices.empty()) { add_error("Found invalid mesh"); @@ -1916,7 +1921,7 @@ namespace Slic3r { VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume); assert(volume_it != volumes_offsets.end()); - const indexed_triangle_set &its = volume->mesh.its; + const indexed_triangle_set &its = volume->mesh().its; // updates triangle offsets volume_it->second.first_triangle_id = triangles_count; diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 48887bc789..a33d21c9fa 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -522,7 +522,8 @@ void AMFParserContext::endElement(const char * /* name */) case NODE_TYPE_VOLUME: { assert(m_object && m_volume); - stl_file &stl = m_volume->mesh.stl; + TriangleMesh mesh; + stl_file &stl = mesh.stl; stl.stats.type = inmemory; stl.stats.number_of_facets = int(m_volume_facets.size() / 3); stl.stats.original_num_facets = stl.stats.number_of_facets; @@ -533,8 +534,9 @@ void AMFParserContext::endElement(const char * /* name */) memcpy(facet.vertex[v].data(), &m_object_vertices[m_volume_facets[i ++] * 3], 3 * sizeof(float)); } stl_get_size(&stl); - m_volume->mesh.repair(); - m_volume->center_geometry(); + mesh.repair(); + m_volume->set_mesh(std::move(mesh)); + m_volume->center_geometry_after_creation(); m_volume->calculate_convex_hull(); m_volume_facets.clear(); m_volume = nullptr; @@ -923,10 +925,11 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) int num_vertices = 0; for (ModelVolume *volume : object->volumes) { vertices_offsets.push_back(num_vertices); - if (! volume->mesh.repaired) + if (! volume->mesh().repaired) throw std::runtime_error("store_amf() requires repair()"); - volume->mesh.require_shared_vertices(); - const indexed_triangle_set &its = volume->mesh.its; + if (! volume->mesh().has_shared_vertices()) + throw std::runtime_error("store_amf() requires shared vertices"); + const indexed_triangle_set &its = volume->mesh().its; const Transform3d& matrix = volume->get_matrix(); for (size_t i = 0; i < its.vertices.size(); ++i) { stream << " \n"; @@ -955,10 +958,11 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) if (volume->is_modifier()) stream << " 1\n"; stream << " " << ModelVolume::type_to_string(volume->type()) << "\n"; - for (size_t i = 0; i < (int)volume->mesh.its.indices.size(); ++i) { + const indexed_triangle_set &its = volume->mesh().its; + for (size_t i = 0; i < (int)its.indices.size(); ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) - stream << " " << volume->mesh.its.indices[i][j] + vertices_offset << "\n"; + stream << " " << its.indices[i][j] + vertices_offset << "\n"; stream << " \n"; } stream << " \n"; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 770581c034..113b215033 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -160,12 +160,6 @@ Model Model::read_from_archive(const std::string &input_file, DynamicPrintConfig return model; } -void Model::repair() -{ - for (ModelObject *o : this->objects) - o->repair(); -} - ModelObject* Model::add_object() { this->objects.emplace_back(new ModelObject(this)); @@ -472,7 +466,7 @@ bool Model::looks_like_multipart_object() const if (obj->volumes.size() > 1 || obj->config.keys().size() > 1) return false; for (const ModelVolume *vol : obj->volumes) { - double zmin_this = vol->mesh.bounding_box().min(2); + double zmin_this = vol->mesh().bounding_box().min(2); if (zmin == std::numeric_limits::max()) zmin = zmin_this; else if (std::abs(zmin - zmin_this) > EPSILON) @@ -679,7 +673,7 @@ ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh) { ModelVolume* v = new ModelVolume(this, mesh); this->volumes.push_back(v); - v->center_geometry(); + v->center_geometry_after_creation(); this->invalidate_bounding_box(); return v; } @@ -688,7 +682,7 @@ ModelVolume* ModelObject::add_volume(TriangleMesh &&mesh) { ModelVolume* v = new ModelVolume(this, std::move(mesh)); this->volumes.push_back(v); - v->center_geometry(); + v->center_geometry_after_creation(); this->invalidate_bounding_box(); return v; } @@ -697,7 +691,7 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other) { ModelVolume* v = new ModelVolume(this, other); this->volumes.push_back(v); - v->center_geometry(); + v->center_geometry_after_creation(); this->invalidate_bounding_box(); return v; } @@ -706,7 +700,7 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other, TriangleMesh &&me { ModelVolume* v = new ModelVolume(this, other, std::move(mesh)); this->volumes.push_back(v); - v->center_geometry(); + v->center_geometry_after_creation(); this->invalidate_bounding_box(); return v; } @@ -827,7 +821,7 @@ TriangleMesh ModelObject::raw_mesh() const for (const ModelVolume *v : this->volumes) if (v->is_model_part()) { - TriangleMesh vol_mesh(v->mesh); + TriangleMesh vol_mesh(v->mesh()); vol_mesh.transform(v->get_matrix()); mesh.merge(vol_mesh); } @@ -840,7 +834,7 @@ TriangleMesh ModelObject::full_raw_mesh() const TriangleMesh mesh; for (const ModelVolume *v : this->volumes) { - TriangleMesh vol_mesh(v->mesh); + TriangleMesh vol_mesh(v->mesh()); vol_mesh.transform(v->get_matrix()); mesh.merge(vol_mesh); } @@ -854,7 +848,7 @@ const BoundingBoxf3& ModelObject::raw_mesh_bounding_box() const m_raw_mesh_bounding_box.reset(); for (const ModelVolume *v : this->volumes) if (v->is_model_part()) - m_raw_mesh_bounding_box.merge(v->mesh.transformed_bounding_box(v->get_matrix())); + m_raw_mesh_bounding_box.merge(v->mesh().transformed_bounding_box(v->get_matrix())); } return m_raw_mesh_bounding_box; } @@ -863,7 +857,7 @@ BoundingBoxf3 ModelObject::full_raw_mesh_bounding_box() const { BoundingBoxf3 bb; for (const ModelVolume *v : this->volumes) - bb.merge(v->mesh.transformed_bounding_box(v->get_matrix())); + bb.merge(v->mesh().transformed_bounding_box(v->get_matrix())); return bb; } @@ -881,7 +875,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const for (const ModelVolume *v : this->volumes) { if (v->is_model_part()) - m_raw_bounding_box.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix())); + m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); } } return m_raw_bounding_box; @@ -895,7 +889,7 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_ for (ModelVolume *v : this->volumes) { if (v->is_model_part()) - bb.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix())); + bb.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); } return bb; } @@ -909,10 +903,10 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const for (const ModelVolume *v : this->volumes) if (v->is_model_part()) { Transform3d trafo = trafo_instance * v->get_matrix(); - const indexed_triangle_set &its = v->mesh.its; + const indexed_triangle_set &its = v->mesh().its; if (its.vertices.empty()) { // Using the STL faces. - const stl_file& stl = v->mesh.stl; + const stl_file& stl = v->mesh().stl; for (const stl_facet &facet : stl.facet_start) for (size_t j = 0; j < 3; ++ j) { Vec3d p = trafo * facet.vertex[j].cast(); @@ -1038,6 +1032,7 @@ void ModelObject::mirror(Axis axis) this->invalidate_bounding_box(); } +// This method could only be called before the meshes of this ModelVolumes are not shared! void ModelObject::scale_mesh(const Vec3d &versor) { for (ModelVolume *v : this->volumes) @@ -1061,14 +1056,14 @@ size_t ModelObject::facets_count() const size_t num = 0; for (const ModelVolume *v : this->volumes) if (v->is_model_part()) - num += v->mesh.stl.stats.number_of_facets; + num += v->mesh().stl.stats.number_of_facets; return num; } bool ModelObject::needed_repair() const { for (const ModelVolume *v : this->volumes) - if (v->is_model_part() && v->mesh.needed_repair()) + if (v->is_model_part() && v->mesh().needed_repair()) return true; return false; } @@ -1134,11 +1129,12 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b // Transform the mesh by the combined transformation matrix. // Flip the triangles in case the composite transformation is left handed. - volume->mesh.transform(instance_matrix * volume_matrix, true); + TriangleMesh mesh(volume->mesh()); + mesh.transform(instance_matrix * volume_matrix, true); + volume->reset_mesh(); // Perform cut - volume->mesh.require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer tms(&volume->mesh); + TriangleMeshSlicer tms(&mesh); tms.cut(float(z), &upper_mesh, &lower_mesh); // Reset volume transformation except for offset @@ -1157,14 +1153,14 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b if (keep_upper && upper_mesh.facets_count() > 0) { ModelVolume* vol = upper->add_volume(upper_mesh); - vol->name = volume->name; - vol->config = volume->config; + vol->name = volume->name; + vol->config = volume->config; vol->set_material(volume->material_id(), *volume->material()); } if (keep_lower && lower_mesh.facets_count() > 0) { ModelVolume* vol = lower->add_volume(lower_mesh); - vol->name = volume->name; - vol->config = volume->config; + vol->name = volume->name; + vol->config = volume->config; vol->set_material(volume->material_id(), *volume->material()); // Compute the lower part instances' bounding boxes to figure out where to place @@ -1232,7 +1228,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects) } ModelVolume* volume = this->volumes.front(); - TriangleMeshPtrs meshptrs = volume->mesh.split(); + TriangleMeshPtrs meshptrs = volume->mesh().split(); for (TriangleMesh *mesh : meshptrs) { mesh->repair(); @@ -1259,12 +1255,6 @@ void ModelObject::split(ModelObjectPtrs* new_objects) return; } -void ModelObject::repair() -{ - for (ModelVolume *v : this->volumes) - v->mesh.repair(); -} - // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. // This situation is solved by baking in the instance transformation into the mesh vertices. @@ -1294,8 +1284,8 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) // Adjust the meshes. // Transformation to be applied to the meshes. - Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0); - Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix(); + Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0); + Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix(); for (ModelVolume *model_volume : this->volumes) { const Geometry::Transformation volume_trafo = model_volume->get_transformation(); bool volume_left_handed = volume_trafo.is_left_handed(); @@ -1305,7 +1295,8 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.; // Transform the mesh. Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0); - model_volume->transform_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed); + // Following method creates a new shared_ptr + model_volume->transform_this_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed); // Reset the rotation, scaling and mirroring. model_volume->set_rotation(Vec3d(0., 0., 0.)); model_volume->set_scaling_factor(Vec3d(volume_new_scaling_factor, volume_new_scaling_factor, volume_new_scaling_factor)); @@ -1447,7 +1438,7 @@ std::string ModelObject::get_export_filename() const stl_stats ModelObject::get_object_stl_stats() const { if (this->volumes.size() == 1) - return this->volumes[0]->mesh.stl.stats; + return this->volumes[0]->mesh().stl.stats; stl_stats full_stats; memset(&full_stats, 0, sizeof(stl_stats)); @@ -1458,7 +1449,7 @@ stl_stats ModelObject::get_object_stl_stats() const if (volume->id() == this->volumes[0]->id()) continue; - const stl_stats& stats = volume->mesh.stl.stats; + const stl_stats& stats = volume->mesh().stl.stats; // initialize full_stats (for repaired errors) full_stats.degenerate_facets += stats.degenerate_facets; @@ -1526,30 +1517,30 @@ bool ModelVolume::is_splittable() const { // the call mesh.is_splittable() is expensive, so cache the value to calculate it only once if (m_is_splittable == -1) - m_is_splittable = (int)mesh.is_splittable(); + m_is_splittable = (int)this->mesh().is_splittable(); return m_is_splittable == 1; } -void ModelVolume::center_geometry() +void ModelVolume::center_geometry_after_creation() { - Vec3d shift = mesh.bounding_box().center(); + Vec3d shift = this->mesh().bounding_box().center(); if (!shift.isApprox(Vec3d::Zero())) { - mesh.translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); - m_convex_hull.translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + m_mesh->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); translate(shift); } } void ModelVolume::calculate_convex_hull() { - m_convex_hull = mesh.convex_hull_3d(); + m_convex_hull = std::make_shared(this->mesh().convex_hull_3d()); } int ModelVolume::get_mesh_errors_count() const { - const stl_stats& stats = this->mesh.stl.stats; + const stl_stats& stats = this->mesh().stl.stats; return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + stats.facets_added + stats.facets_reversed + stats.backwards_edges; @@ -1557,7 +1548,7 @@ int ModelVolume::get_mesh_errors_count() const const TriangleMesh& ModelVolume::get_convex_hull() const { - return m_convex_hull; + return *m_convex_hull.get(); } ModelVolumeType ModelVolume::type_from_string(const std::string &s) @@ -1597,7 +1588,7 @@ std::string ModelVolume::type_to_string(const ModelVolumeType t) // This is useful to assign different materials to different volumes of an object. size_t ModelVolume::split(unsigned int max_extruders) { - TriangleMeshPtrs meshptrs = this->mesh.split(); + TriangleMeshPtrs meshptrs = this->mesh().split(); if (meshptrs.size() <= 1) { delete meshptrs.front(); return 1; @@ -1614,7 +1605,7 @@ size_t ModelVolume::split(unsigned int max_extruders) mesh->repair(); if (idx == 0) { - this->mesh = std::move(*mesh); + this->set_mesh(std::move(*mesh)); this->calculate_convex_hull(); // Assign a new unique ID, so that a new GLVolume will be generated. this->set_new_unique_id(); @@ -1623,7 +1614,7 @@ size_t ModelVolume::split(unsigned int max_extruders) this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(*mesh))); this->object->volumes[ivolume]->set_offset(Vec3d::Zero()); - this->object->volumes[ivolume]->center_geometry(); + this->object->volumes[ivolume]->center_geometry_after_creation(); this->object->volumes[ivolume]->translate(offset); this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); this->object->volumes[ivolume]->config.set_deserialize("extruder", Model::get_auto_extruder_id_as_string(max_extruders)); @@ -1689,24 +1680,33 @@ void ModelVolume::mirror(Axis axis) set_mirror(mirror); } +// This method could only be called before the meshes of this ModelVolumes are not shared! void ModelVolume::scale_geometry(const Vec3d& versor) { - mesh.scale(versor); - m_convex_hull.scale(versor); + m_mesh->scale(versor); + m_convex_hull->scale(versor); } -void ModelVolume::transform_mesh(const Transform3d &mesh_trafo, bool fix_left_handed) +void ModelVolume::transform_this_mesh(const Transform3d &mesh_trafo, bool fix_left_handed) { - this->mesh.transform(mesh_trafo, fix_left_handed); - this->m_convex_hull.transform(mesh_trafo, fix_left_handed); + TriangleMesh mesh = this->mesh(); + mesh.transform(mesh_trafo, fix_left_handed); + this->set_mesh(std::move(mesh)); + TriangleMesh convex_hull = this->get_convex_hull(); + convex_hull.transform(mesh_trafo, fix_left_handed); + this->m_convex_hull = std::make_shared(std::move(convex_hull)); // Let the rest of the application know that the geometry changed, so the meshes have to be reloaded. this->set_new_unique_id(); } -void ModelVolume::transform_mesh(const Matrix3d &matrix, bool fix_left_handed) +void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_handed) { - this->mesh.transform(matrix, fix_left_handed); - this->m_convex_hull.transform(matrix, fix_left_handed); + TriangleMesh mesh = this->mesh(); + mesh.transform(matrix, fix_left_handed); + this->set_mesh(std::move(mesh)); + TriangleMesh convex_hull = this->get_convex_hull(); + convex_hull.transform(matrix, fix_left_handed); + this->m_convex_hull = std::make_shared(std::move(convex_hull)); // Let the rest of the application know that the geometry changed, so the meshes have to be reloaded. this->set_new_unique_id(); } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 41bf5bd4be..0fd1140f0a 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -7,7 +7,9 @@ #include "Point.hpp" #include "TriangleMesh.hpp" #include "Slicing.hpp" + #include +#include #include #include #include @@ -261,6 +263,7 @@ public: void rotate(double angle, const Vec3d& axis); void mirror(Axis axis); + // This method could only be called before the meshes of this ModelVolumes are not shared! void scale_mesh(const Vec3d& versor); size_t materials_count() const; @@ -268,7 +271,6 @@ public: bool needed_repair() const; ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); // Note: z is in world coordinates void split(ModelObjectPtrs* new_objects); - void repair(); // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. // This situation is solved by baking in the instance transformation into the mesh vertices. @@ -340,7 +342,12 @@ class ModelVolume : public ModelBase public: std::string name; // The triangular model. - TriangleMesh mesh; + const TriangleMesh& mesh() const { return *m_mesh.get(); } + void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } + void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } + void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } + void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } + void reset_mesh() { m_mesh = std::make_shared(); } // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. DynamicPrintConfig config; @@ -377,13 +384,16 @@ public: void rotate(double angle, const Vec3d& axis); void mirror(Axis axis); + // This method could only be called before the meshes of this ModelVolumes are not shared! void scale_geometry(const Vec3d& versor); - // translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box - void center_geometry(); + // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box. + // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared! + void center_geometry_after_creation(); void calculate_convex_hull(); const TriangleMesh& get_convex_hull() const; + std::shared_ptr get_convex_hull_shared_ptr() const { return m_convex_hull; } // Get count of errors in the mesh int get_mesh_errors_count() const; @@ -430,18 +440,20 @@ protected: explicit ModelVolume(const ModelVolume &rhs) = default; void set_model_object(ModelObject *model_object) { object = model_object; } - void transform_mesh(const Transform3d& t, bool fix_left_handed); - void transform_mesh(const Matrix3d& m, bool fix_left_handed); + void transform_this_mesh(const Transform3d& t, bool fix_left_handed); + void transform_this_mesh(const Matrix3d& m, bool fix_left_handed); private: // Parent object owning this ModelVolume. - ModelObject* object; + ModelObject* object; + // The triangular model. + std::shared_ptr m_mesh; // Is it an object to be printed, or a modifier volume? - ModelVolumeType m_type; - t_model_material_id m_material_id; + ModelVolumeType m_type; + t_model_material_id m_material_id; // The convex hull of this model's mesh. - TriangleMesh m_convex_hull; - Geometry::Transformation m_transformation; + std::shared_ptr m_convex_hull; + Geometry::Transformation m_transformation; // flag to optimize the checking if the volume is splittable // -1 -> is unknown value (before first cheking) @@ -449,24 +461,24 @@ private: // 1 -> is splittable mutable int m_is_splittable{ -1 }; - ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(ModelVolumeType::MODEL_PART), object(object) + ModelVolume(ModelObject *object, const TriangleMesh &mesh) : m_mesh(new TriangleMesh(mesh)), m_type(ModelVolumeType::MODEL_PART), object(object) { if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); } ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : - mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(ModelVolumeType::MODEL_PART), object(object) {} + m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) {} // Copying an existing volume, therefore this volume will get a copy of the ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other) : ModelBase(other), // copy the ID - name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) + name(other.name), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { this->set_material_id(other.material_id()); } // Providing a new mesh, therefore this volume will get a new unique ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : - name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) + name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { this->set_material_id(other.material_id()); if (mesh.stl.stats.number_of_facets > 1) @@ -597,10 +609,6 @@ public: static Model read_from_file(const std::string &input_file, DynamicPrintConfig *config = nullptr, bool add_default_instances = true); static Model read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances = true); - /// Repair the ModelObjects of the current Model. - /// This function calls repair function on each TriangleMesh of each model object volume - void repair(); - // Add a new ModelObject to this Model, generate a new ID for this ModelObject. ModelObject* add_object(); ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 660a2d9398..d99aceabfe 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1797,7 +1797,7 @@ std::vector PrintObject::_slice_volumes(const std::vector &z, if (! volumes.empty()) { // Compose mesh. //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. - TriangleMesh mesh(volumes.front()->mesh); + TriangleMesh mesh(volumes.front()->mesh()); mesh.transform(volumes.front()->get_matrix(), true); assert(mesh.repaired); if (volumes.size() == 1 && mesh.repaired) { @@ -1806,7 +1806,7 @@ std::vector PrintObject::_slice_volumes(const std::vector &z, } for (size_t idx_volume = 1; idx_volume < volumes.size(); ++ idx_volume) { const ModelVolume &model_volume = *volumes[idx_volume]; - TriangleMesh vol_mesh(model_volume.mesh); + TriangleMesh vol_mesh(model_volume.mesh()); vol_mesh.transform(model_volume.get_matrix(), true); mesh.merge(vol_mesh); } @@ -1815,10 +1815,11 @@ std::vector PrintObject::_slice_volumes(const std::vector &z, // apply XY shift mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), 0); // perform actual slicing - TriangleMeshSlicer mslicer; const Print *print = this->print(); auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); - mesh.require_shared_vertices(); // TriangleMeshSlicer needs this + // TriangleMeshSlicer needs shared vertices, also this calls the repair() function. + mesh.require_shared_vertices(); + TriangleMeshSlicer mslicer; mslicer.init(&mesh, callback); mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); m_print->throw_if_canceled(); @@ -1832,7 +1833,7 @@ std::vector PrintObject::_slice_volume(const std::vector &z, std::vector layers; // Compose mesh. //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. - TriangleMesh mesh(volume.mesh); + TriangleMesh mesh(volume.mesh()); mesh.transform(volume.get_matrix(), true); if (mesh.repaired) { //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. @@ -1846,7 +1847,8 @@ std::vector PrintObject::_slice_volume(const std::vector &z, TriangleMeshSlicer mslicer; const Print *print = this->print(); auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); - mesh.require_shared_vertices(); // TriangleMeshSlicer needs this + // TriangleMeshSlicer needs the shared vertices. + mesh.require_shared_vertices(); mslicer.init(&mesh, callback); mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); m_print->throw_if_canceled(); diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 3a05e9d8af..e1bb4b3130 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -227,7 +227,7 @@ std::vector layer_height_profile_adaptive( as.set_slicing_parameters(slicing_params); for (const ModelVolume *volume : volumes) if (volume->is_model_part()) - as.add_mesh(&volume->mesh); + as.add_mesh(&volume->mesh()); as.prepare(); // 2) Generate layers using the algorithm of @platsch diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 5b0ecbd007..11f45147ca 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -82,11 +82,14 @@ TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) // #define SLIC3R_TRACE_REPAIR -void TriangleMesh::repair() +void TriangleMesh::repair(bool update_shared_vertices) { - if (this->repaired) + if (this->repaired) { + if (update_shared_vertices) + this->require_shared_vertices(); return; - + } + // admesh fails when repairing empty meshes if (this->stl.stats.number_of_facets == 0) return; @@ -97,6 +100,7 @@ void TriangleMesh::repair() #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; #endif /* SLIC3R_TRACE_REPAIR */ + assert(stl_validate(&this->stl)); stl_check_facets_exact(&stl); assert(stl_validate(&this->stl)); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); @@ -179,6 +183,12 @@ void TriangleMesh::repair() this->repaired = true; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; + + // This call should be quite cheap, a lot of code requires the indexed_triangle_set data structure, + // and it is risky to generate such a structure once the meshes are shared. Do it now. + this->its.clear(); + if (update_shared_vertices) + this->require_shared_vertices(); } float TriangleMesh::volume() @@ -238,8 +248,7 @@ bool TriangleMesh::needed_repair() const void TriangleMesh::WriteOBJFile(const char* output_file) { - stl_generate_shared_vertices(&stl, its); - its_write_obj(its, output_file); + its_write_obj(this->its, output_file); } void TriangleMesh::scale(float factor) @@ -294,6 +303,7 @@ void TriangleMesh::rotate(float angle, const Vec3d& axis) Transform3d m = Transform3d::Identity(); m.rotate(Eigen::AngleAxisd(angle, axis_norm)); stl_transform(&stl, m); + its_transform(its, m); } void TriangleMesh::mirror(const Axis &axis) @@ -311,22 +321,26 @@ void TriangleMesh::mirror(const Axis &axis) void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) { stl_transform(&stl, t); - this->its.clear(); + its_transform(its, t); if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) { // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. - this->repair(); + this->repair(false); stl_reverse_all_facets(&stl); + this->its.clear(); + this->require_shared_vertices(); } } void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) { stl_transform(&stl, m); - this->its.clear(); + its_transform(its, m); if (fix_left_handed && m.determinant() < 0.) { // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. - this->repair(); + this->repair(false); stl_reverse_all_facets(&stl); + this->its.clear(); + this->require_shared_vertices(); } } @@ -482,7 +496,6 @@ ExPolygons TriangleMesh::horizontal_projection() const // 2D convex hull of a 3D mesh projected into the Z=0 plane. Polygon TriangleMesh::convex_hull() { - this->require_shared_vertices(); Points pp; pp.reserve(this->its.vertices.size()); for (size_t i = 0; i < this->its.vertices.size(); ++ i) { @@ -519,26 +532,32 @@ BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) c TriangleMesh TriangleMesh::convex_hull_3d() const { - // Helper struct for qhull: - struct PointForQHull{ - PointForQHull(float x_p, float y_p, float z_p) : x((realT)x_p), y((realT)y_p), z((realT)z_p) {} - realT x, y, z; - }; - std::vector src_vertices; - - // We will now fill the vector with input points for computation: - for (const stl_facet &facet : stl.facet_start) - for (int i = 0; i < 3; ++ i) { - const stl_vertex& v = facet.vertex[i]; - src_vertices.emplace_back(v(0), v(1), v(2)); - } - // The qhull call: orgQhull::Qhull qhull; qhull.disableOutputStream(); // we want qhull to be quiet - try + std::vector src_vertices; + try { - qhull.runQhull("", 3, (int)src_vertices.size(), (const realT*)(src_vertices.data()), "Qt"); + if (this->has_shared_vertices()) { +#if REALfloat + qhull.runQhull("", 3, (int)this->its.vertices.size() / 3, (const realT*)(this->its.vertices.front().data()), "Qt"); +#else + src_vertices.reserve(this->its.vertices() * 3); + // We will now fill the vector with input points for computation: + for (const stl_vertex &v : ths->its.vertices.size()) + for (int i = 0; i < 3; ++ i) + src_vertices.emplace_back(v(i)); + qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); +#endif + } else { + src_vertices.reserve(this->stl.facet_start.size() * 9); + // We will now fill the vector with input points for computation: + for (const stl_facet &f : this->stl.facet_start) + for (int i = 0; i < 3; ++ i) + for (int j = 0; j < 3; ++ j) + src_vertices.emplace_back(f.vertex[i](j)); + qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); + } } catch (...) { @@ -566,7 +585,6 @@ TriangleMesh TriangleMesh::convex_hull_3d() const TriangleMesh output_mesh(dst_vertices, facets); output_mesh.repair(); - output_mesh.require_shared_vertices(); return output_mesh; } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 75082cfdbd..ffd9b7e628 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -33,7 +33,7 @@ public: bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); } bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); } bool write_binary(const char* output_file) { return stl_write_binary(&this->stl, output_file, ""); } - void repair(); + void repair(bool update_shared_vertices = true); float volume(); void check_topology(); bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; } diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 59480de1ce..33ab1f5d14 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -241,8 +241,6 @@ GLVolume::GLVolume(float r, float g, float b, float a) : m_transformed_bounding_box_dirty(true) , m_sla_shift_z(0.0) , m_transformed_convex_hull_bounding_box_dirty(true) - , m_convex_hull(nullptr) - , m_convex_hull_owned(false) // geometry_id == 0 -> invalid , geometry_id(std::pair(0, 0)) , extruder_id(0) @@ -268,12 +266,6 @@ GLVolume::GLVolume(float r, float g, float b, float a) set_render_color(r, g, b, a); } -GLVolume::~GLVolume() -{ - if (m_convex_hull_owned) - delete m_convex_hull; -} - void GLVolume::set_render_color(float r, float g, float b, float a) { render_color[0] = r; @@ -335,12 +327,6 @@ void GLVolume::set_color_from_model_volume(const ModelVolume *model_volume) color[3] = model_volume->is_model_part() ? 1.f : 0.5f; } -void GLVolume::set_convex_hull(const TriangleMesh *convex_hull, bool owned) -{ - m_convex_hull = convex_hull; - m_convex_hull_owned = owned; -} - Transform3d GLVolume::world_matrix() const { Transform3d m = m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix(); @@ -377,7 +363,7 @@ const BoundingBoxf3& GLVolume::transformed_convex_hull_bounding_box() const BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &trafo) const { - return (m_convex_hull != nullptr && m_convex_hull->stl.stats.number_of_facets > 0) ? + return (m_convex_hull && m_convex_hull->stl.stats.number_of_facets > 0) ? m_convex_hull->transformed_bounding_box(trafo) : bounding_box.transformed(trafo); } @@ -587,7 +573,7 @@ int GLVolumeCollection::load_object_volume( const ModelVolume *model_volume = model_object->volumes[volume_idx]; const int extruder_id = model_volume->extruder_id(); const ModelInstance *instance = model_object->instances[instance_idx]; - const TriangleMesh& mesh = model_volume->mesh; + const TriangleMesh& mesh = model_volume->mesh(); float color[4]; memcpy(color, GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); /* if (model_volume->is_support_blocker()) { @@ -613,7 +599,7 @@ int GLVolumeCollection::load_object_volume( if (model_volume->is_model_part()) { // GLVolume will reference a convex hull from model_volume! - v.set_convex_hull(&model_volume->get_convex_hull(), false); + v.set_convex_hull(model_volume->get_convex_hull_shared_ptr()); if (extruder_id != -1) v.extruder_id = extruder_id; } @@ -656,7 +642,10 @@ void GLVolumeCollection::load_object_auxiliary( v.composite_id = GLVolume::CompositeID(obj_idx, - int(milestone), (int)instance_idx.first); v.geometry_id = std::pair(timestamp, model_instance.id().id); // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. - v.set_convex_hull((&instance_idx == &instances.back()) ? new TriangleMesh(std::move(convex_hull)) : new TriangleMesh(convex_hull), true); + if (&instance_idx == &instances.back()) + v.set_convex_hull(std::move(convex_hull)); + else + v.set_convex_hull(convex_hull); v.is_modifier = false; v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree); v.set_instance_transformation(model_instance.get_transformation()); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 2ca11073be..b9ac668e00 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -10,6 +10,7 @@ #include "slic3r/GUI/GLCanvas3DManager.hpp" #include +#include #ifndef NDEBUG #define HAS_GLSAFE @@ -243,7 +244,6 @@ public: GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f); GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} - ~GLVolume(); private: Geometry::Transformation m_instance_transformation; @@ -255,10 +255,8 @@ private: mutable BoundingBoxf3 m_transformed_bounding_box; // Whether or not is needed to recalculate the transformed bounding box. mutable bool m_transformed_bounding_box_dirty; - // Pointer to convex hull of the original mesh, if any. - // This object may or may not own the convex hull instance based on m_convex_hull_owned - const TriangleMesh* m_convex_hull; - bool m_convex_hull_owned; + // Convex hull of the volume, if any. + std::shared_ptr m_convex_hull; // Bounding box of this volume, in unscaled coordinates. mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box; // Whether or not is needed to recalculate the transformed convex hull bounding box. @@ -395,7 +393,9 @@ public: double get_sla_shift_z() const { return m_sla_shift_z; } void set_sla_shift_z(double z) { m_sla_shift_z = z; } - void set_convex_hull(const TriangleMesh *convex_hull, bool owned); + void set_convex_hull(std::shared_ptr &convex_hull) { m_convex_hull = convex_hull; } + void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared(convex_hull); } + void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared(std::move(convex_hull)); } int object_idx() const { return this->composite_id.object_id; } int volume_idx() const { return this->composite_id.volume_id; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b8ca905d27..f0a40950d3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5498,7 +5498,7 @@ void GLCanvas3D::_load_sla_shells() v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0)); v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation)); v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.); - v.set_convex_hull(new TriangleMesh(std::move(mesh.convex_hull_3d())), true); + v.set_convex_hull(mesh.convex_hull_3d()); }; // adds objects' volumes diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index dffa02e95b..cbf7a5ef57 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -261,7 +261,7 @@ wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx / const stl_stats& stats = vol_idx == -1 ? (*m_objects)[obj_idx]->get_object_stl_stats() : - (*m_objects)[obj_idx]->volumes[vol_idx]->mesh.stl.stats; + (*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stl.stats; std::map error_msg = { { L("degenerate facets"), stats.degenerate_facets }, @@ -1592,7 +1592,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); // Transform the new modifier to be aligned with the print bed. - const BoundingBoxf3 mesh_bb = new_volume->mesh.bounding_box(); + const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); new_volume->set_transformation(volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); // Set the modifier position. auto offset = (type_name == "Slab") ? diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 89475a0582..2a2adcae96 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -388,8 +388,7 @@ void GLGizmoSlaSupports::update_mesh() wxBusyCursor wait; // this way we can use that mesh directly. // This mesh does not account for the possible Z up SLA offset. - m_mesh = &m_model_object->volumes.front()->mesh; - const_cast(m_mesh)->require_shared_vertices(); // TriangleMeshSlicer needs this + m_mesh = &m_model_object->volumes.front()->mesh(); m_its = &m_mesh->its; m_current_mesh_model_id = m_model_object->id(); m_editing_mode = false; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 44f77b3f78..18b3078bb3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3565,7 +3565,7 @@ void Plater::export_stl(bool extended, bool selection_only) else { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - mesh = model_object->volumes[volume->volume_idx()]->mesh; + mesh = model_object->volumes[volume->volume_idx()]->mesh(); mesh.transform(volume->get_volume_transformation().get_matrix()); mesh.translate(-model_object->origin_translation.cast()); } diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index a1c8890ef5..6a2cc60802 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -253,7 +253,7 @@ ModelMaterial::attributes() Ref config() %code%{ RETVAL = &THIS->config; %}; Ref mesh() - %code%{ RETVAL = &THIS->mesh; %}; + %code%{ RETVAL = &THIS->mesh(); %}; bool modifier() %code%{ RETVAL = THIS->is_modifier(); %}; From 3872b939e440421dec0fc2cee8fad40fba6f1c0c Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 11 Jun 2019 17:15:07 +0200 Subject: [PATCH 083/627] Fix of previous commit --- src/admesh/stl.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/admesh/stl.h b/src/admesh/stl.h index c419c567bd..40bced2f4f 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -216,7 +216,6 @@ template extern void its_transform(indexed_triangle_set &its, T *trafo3x4) { for (stl_vertex &v_dst : its.vertices) { - stl_vertex &v_dst = face.vertex[i_vertex]; stl_vertex v_src = v_dst; v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2) + trafo3x4[3]); v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]); From 6877c075dc14c24d8b036ade7cf540ce228dd061 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 11 Jun 2019 17:57:39 +0200 Subject: [PATCH 084/627] SPE-742: Parameter layer for zero elevation feature. --- src/libslic3r/PrintConfig.cpp | 47 ++++++++++++++- src/libslic3r/PrintConfig.hpp | 26 ++++++++- src/libslic3r/SLA/SLABasePool.hpp | 15 +++-- src/libslic3r/SLA/SLACommon.hpp | 6 +- src/libslic3r/SLA/SLASupportTree.cpp | 14 ++--- src/libslic3r/SLA/SLASupportTree.hpp | 11 ++-- src/libslic3r/SLAPrint.cpp | 31 +++++++--- src/slic3r/GUI/Preset.cpp | 4 ++ src/slic3r/GUI/Tab.cpp | 85 ++++++++++++++++++---------- 9 files changed, 179 insertions(+), 60 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 87ea263012..45ff20e789 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2490,6 +2490,19 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("support_base_safety_distance", coFloat); + def->label = L("Support base safety distance"); + def->category = L("Supports"); + def->tooltip = L( + "The minimum distance of the pillar base from the model in mm. " + "Makes sense in zero elevation mode where a gap according " + "to this parameter is inserted between the model and the pad."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.5)); def = this->add("support_critical_angle", coFloat); def->label = L("Critical angle"); @@ -2523,7 +2536,9 @@ void PrintConfigDef::init_sla_params() def = this->add("support_object_elevation", coFloat); def->label = L("Object elevation"); def->category = L("Supports"); - def->tooltip = L("How much the supports should lift up the supported object."); + def->tooltip = L("How much the supports should lift up the supported object. " + "If this value is zero, the bottom of the model geometry " + "will be considered as part of the pad."); def->sidetext = L("mm"); def->min = 0; def->max = 150; // This is the max height of print on SL1 @@ -2609,6 +2624,36 @@ void PrintConfigDef::init_sla_params() def->max = 90; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(45.0)); + + def = this->add("pad_object_connector_stride", coFloat); + def->label = L("Pad object connector stride"); + def->category = L("Pad"); + def->tooltip = L("Distance between two connector sticks between " + "the object pad and the generated pad."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(10)); + + def = this->add("pad_object_connector_width", coFloat); + def->label = L("Pad object connector width"); + def->category = L("Pad"); + def->tooltip = L("The width of the connectors sticks which connect the " + "object pad and the generated pad."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.3)); + + def = this->add("pad_object_connector_penetration", coFloat); + def->label = L("Pad object connector penetration"); + def->category = L("Pad"); + def->tooltip = L( + "How much should the tiny connectors penetrate into the model body."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.3)); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 1da22b377f..e632e6946c 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -983,6 +983,9 @@ public: // The height of the pillar base cone in mm. ConfigOptionFloat support_base_height /*= 1.0*/; + + // The minimum distance of the pillar base from the model in mm. + ConfigOptionFloat support_base_safety_distance; /*= 1.0*/; // The default angle for connecting support sticks and junctions. ConfigOptionFloat support_critical_angle /*= 45*/; @@ -996,7 +999,7 @@ public: // The elevation in Z direction upwards. This is the space between the pad // and the model object's bounding box bottom. Units in mm. ConfigOptionFloat support_object_elevation /*= 5.0*/; - + /////// Following options influence automatic support points placement: ConfigOptionInt support_points_density_relative; ConfigOptionFloat support_points_minimal_distance; @@ -1021,6 +1024,23 @@ public: // The slope of the pad wall... ConfigOptionFloat pad_wall_slope; + + // ///////////////////////////////////////////////////////////////////////// + // Zero elevation mode parameters: + // - The object pad will be derived from the the model geometry. + // - There will be a gap between the object pad and the generated pad + // according to the support_base_safety_distance parameter. + // - The two pads will be connected with tiny connector sticks + // ///////////////////////////////////////////////////////////////////////// + + // How far to place the connector sticks on the object pad perimeter + ConfigOptionFloat pad_object_connector_stride; + + // The width of the connectors sticks + ConfigOptionFloat pad_object_connector_width; + + // How much should the tiny connectors penetrate into the model body + ConfigOptionFloat pad_object_connector_penetration; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) @@ -1038,6 +1058,7 @@ protected: OPT_PTR(support_pillar_widening_factor); OPT_PTR(support_base_diameter); OPT_PTR(support_base_height); + OPT_PTR(support_base_safety_distance); OPT_PTR(support_critical_angle); OPT_PTR(support_max_bridge_length); OPT_PTR(support_max_pillar_link_distance); @@ -1050,6 +1071,9 @@ protected: OPT_PTR(pad_max_merge_distance); OPT_PTR(pad_edge_radius); OPT_PTR(pad_wall_slope); + OPT_PTR(pad_object_connector_stride); + OPT_PTR(pad_object_connector_width); + OPT_PTR(pad_object_connector_penetration); } }; diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 0ed26b6e7d..129f7ccd46 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -42,7 +42,14 @@ struct PoolConfig { double max_merge_distance_mm = 50; double edge_radius_mm = 1; double wall_slope = std::atan(1.0); // Universal constant for Pi/4 - bool embed_object = false; + struct EmbedObject { + double object_gap_mm = 0.5; + double stick_stride_mm = 10; + double stick_width_mm = 0.3; + double stick_penetration_mm = 0.1; + bool enabled = false; + operator bool() const { return enabled; } + } embed_object; ThrowOnCancel throw_on_cancel = [](){}; @@ -61,11 +68,7 @@ void create_base_pool(const Polygons& base_plate, const ExPolygons& holes, const PoolConfig& = PoolConfig()); -/// TODO: Currently the base plate of the pool will have half the height of the -/// whole pool. So the carved out space has also half the height. This is not -/// a particularly elegant solution, the thickness should be exactly -/// min_wall_thickness and it should be corrected in the future. This method -/// will return the correct value for further processing. +/// Returns the elevation needed for compensating the pad. inline double get_pad_elevation(const PoolConfig& cfg) { return cfg.min_wall_thickness_mm; } diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index 2b72aa92c3..2666f2a9c3 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -60,7 +60,7 @@ class EigenMesh3D { Eigen::MatrixXd m_V; Eigen::MatrixXi m_F; - double m_ground_level = 0; + double m_ground_level = 0, m_gnd_offset = 0; std::unique_ptr m_aabb; public: @@ -71,8 +71,8 @@ public: ~EigenMesh3D(); - inline double ground_level() const { return m_ground_level; } - inline double& ground_level() { return m_ground_level; } + inline double ground_level() const { return m_ground_level + m_gnd_offset; } + inline void ground_level_offset(double o) { m_gnd_offset = o; } inline const Eigen::MatrixXd& V() const { return m_V; } inline const Eigen::MatrixXi& F() const { return m_F; } diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 9705e86004..b9f2cc14e5 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -72,8 +72,6 @@ const double SupportConfig::normal_cutoff_angle = 150.0 * M_PI / 180.0; // The shortest distance of any support structure from the model surface const double SupportConfig::safety_distance_mm = 0.5; -const double SupportConfig::pillar_base_safety_distance_mm = 0.5; - const double SupportConfig::max_solo_pillar_height_mm = 15.0; const double SupportConfig::max_dual_pillar_height_mm = 35.0; const double SupportConfig::optimizer_rel_score_diff = 1e-6; @@ -590,10 +588,10 @@ struct Pad { for(auto& poly : modelbase_sticks) sla::offset_with_breakstick_holes( poly, - SupportConfig::pillar_base_safety_distance_mm, // padding - 10, // stride (mm) - 0.3, // stick_width (mm) - 0.1); // penetration (mm) + pcfg.embed_object.object_gap_mm, // padding + pcfg.embed_object.stick_stride_mm, + pcfg.embed_object.stick_width_mm, + pcfg.embed_object.stick_penetration_mm); create_base_pool(basep, tmesh, modelbase_sticks, cfg); } else { @@ -1407,7 +1405,7 @@ class SLASupportTree::Algorithm { double gndlvl = m_result.ground_level; Vec3d endp = {jp(X), jp(Y), gndlvl}; - double sd = SupportConfig::pillar_base_safety_distance_mm; + double sd = m_cfg.pillar_base_safety_distance_mm; int pillar_id = -1; double min_dist = sd + m_cfg.base_radius_mm + EPSILON; double dist = 0; @@ -2160,7 +2158,7 @@ public: std::vector spts(needpillars); // vector of starting points double gnd = m_result.ground_level; - double min_dist = SupportConfig::pillar_base_safety_distance_mm + + double min_dist = m_cfg.pillar_base_safety_distance_mm + m_cfg.base_radius_mm + EPSILON; while(!found && alpha < 2*PI) { diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 1602316e8c..93627d10c2 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -81,6 +81,10 @@ struct SupportConfig { // The elevation in Z direction upwards. This is the space between the pad // and the model object's bounding box bottom. double object_elevation_mm = 10; + + // The shortest distance between a pillar base perimeter from the model + // body. This is only useful when elevation is set to zero. + const double pillar_base_safety_distance_mm = 0.5; // ///////////////////////////////////////////////////////////////////////// // Compile time configuration values (candidates for runtime) @@ -91,10 +95,6 @@ struct SupportConfig { // The shortest distance of any support structure from the model surface static const double safety_distance_mm; - - // The shortest distance between a pillar base perimeter from the model - // body. This is only useful when elevation is set to zero. - static const double pillar_base_safety_distance_mm; static const double max_solo_pillar_height_mm; static const double max_dual_pillar_height_mm; @@ -186,7 +186,8 @@ public: /// Get the sliced 2d layers of the support geometry. std::vector slice(float layerh, float init_layerh = -1.0) const; - std::vector slice(const std::vector&, float closing_radius) const; + std::vector slice(const std::vector &, + float closing_radius) const; /// Adding the "pad" (base pool) under the supports /// modelbase will be used according to the embed_object flag in PoolConfig. diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index f4599a2661..14cf2b6ff4 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -599,9 +599,20 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { return scfg; } -bool use_builtin_pad(const SLAPrintObjectConfig& c) { - return c.support_object_elevation.getFloat() <= EPSILON && - c.pad_enable.getBool(); +sla::PoolConfig::EmbedObject use_builtin_pad(const SLAPrintObjectConfig& c) { + sla::PoolConfig::EmbedObject ret; + + ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && + c.pad_enable.getBool(); + + if(ret.enabled) { + ret.object_gap_mm = c.support_base_safety_distance.getFloat(); + ret.stick_width_mm = c.pad_object_connector_width.getFloat(); + ret.stick_stride_mm = c.pad_object_connector_stride.getFloat(); + ret.stick_width_mm = c.pad_object_connector_penetration.getFloat(); + } + + return ret; } sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { @@ -871,9 +882,10 @@ void SLAPrint::process() if(!po.m_supportdata) return; sla::PoolConfig pcfg = make_pool_config(po.m_config); - - if(pcfg.embed_object) - po.m_supportdata->emesh.ground_level() += pcfg.min_wall_thickness_mm; + + if (pcfg.embed_object) + po.m_supportdata->emesh.ground_level_offset( + pcfg.min_wall_thickness_mm); if(!po.m_config.supports_enable.getBool()) { @@ -1635,13 +1647,18 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector& Preset::sla_print_options() "support_pillar_widening_factor", "support_base_diameter", "support_base_height", + "support_base_safety_distance", "support_critical_angle", "support_max_bridge_length", "support_max_pillar_link_distance", @@ -474,6 +475,9 @@ const std::vector& Preset::sla_print_options() "pad_max_merge_distance", "pad_edge_radius", "pad_wall_slope", + "pad_object_connector_stride", + "pad_object_connector_width", + "pad_object_connector_penetration", "output_filename_format", "default_sla_print_profile", "compatible_printers", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6cd270e5b3..8ca47f1b11 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3481,6 +3481,7 @@ void TabSLAPrint::build() // optgroup->append_single_option_line("support_pillar_widening_factor"); optgroup->append_single_option_line("support_base_diameter"); optgroup->append_single_option_line("support_base_height"); + optgroup->append_single_option_line("support_base_safety_distance"); optgroup->append_single_option_line("support_object_elevation"); optgroup = page->new_optgroup(_(L("Connection of the support sticks and junctions"))); @@ -3501,7 +3502,11 @@ void TabSLAPrint::build() // TODO: Disabling this parameter for the beta release // optgroup->append_single_option_line("pad_edge_radius"); optgroup->append_single_option_line("pad_wall_slope"); - + + optgroup->append_single_option_line("pad_object_connector_stride"); + optgroup->append_single_option_line("pad_object_connector_width"); + optgroup->append_single_option_line("pad_object_connector_penetration"); + page = add_options_page(_(L("Advanced")), "wrench"); optgroup = page->new_optgroup(_(L("Slicing"))); optgroup->append_single_option_line("slice_closing_radius"); @@ -3541,42 +3546,64 @@ void TabSLAPrint::reload_config() void TabSLAPrint::update() { - if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) + if (m_preset_bundle->printers.get_selected_preset().printer_technology() + == ptFFF) return; // #ys_FIXME -// #ys_FIXME - m_update_cnt++; + // #ys_FIXME + m_update_cnt++; - double head_penetration = m_config->opt_float("support_head_penetration"); - double head_width = m_config->opt_float("support_head_width"); - if(head_penetration > head_width) { - wxString msg_text = _(L("Head penetration should not be greater than the head width.")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_OK) { - new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width)); - } + double head_penetration = m_config->opt_float("support_head_penetration"); + double head_width = m_config->opt_float("support_head_width"); + if (head_penetration > head_width) { + wxString msg_text = _( + L("Head penetration should not be greater than the head width.")); - load_config(new_conf); - } + auto dialog = new wxMessageDialog(parent(), + msg_text, + _(L("Invalid Head penetration")), + wxICON_WARNING | wxOK); - double pinhead_d = m_config->opt_float("support_head_front_diameter"); - double pillar_d = m_config->opt_float("support_pillar_diameter"); - if(pinhead_d > pillar_d) { - wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter.")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_OK) { - new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0)); - } + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_OK) { + new_conf.set_key_value("support_head_penetration", + new ConfigOptionFloat(head_width)); + } - load_config(new_conf); - } + load_config(new_conf); + } - m_update_cnt--; + double pinhead_d = m_config->opt_float("support_head_front_diameter"); + double pillar_d = m_config->opt_float("support_pillar_diameter"); + if (pinhead_d > pillar_d) { + wxString msg_text = _(L( + "Pinhead diameter should be smaller than the pillar diameter.")); - if (m_update_cnt == 0) - wxGetApp().mainframe->on_config_changed(m_config); + auto dialog = new wxMessageDialog(parent(), + msg_text, + _(L("Invalid pinhead diameter")), + wxICON_WARNING | wxOK); + + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_OK) { + new_conf.set_key_value("support_head_front_diameter", + new ConfigOptionFloat(pillar_d / 2.0)); + } + + load_config(new_conf); + } + + // if(m_config->opt_float("support_object_elevation") < EPSILON && + // m_config->opt_bool("pad_enable")) { + // // TODO: disable editding of: + // // pad_object_connector_stride + // // pad_object_connector_width + // // pad_object_connector_penetration + // } + + m_update_cnt--; + + if (m_update_cnt == 0) wxGetApp().mainframe->on_config_changed(m_config); } } // GUI From c80aae1bdb3d87a2a27c973232eda33f9444f153 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 11 Jun 2019 18:19:58 +0200 Subject: [PATCH 085/627] Fixes for the parameter layer - Elevation value satisfied with no supports as well - Removed debug svg writing - Gap and sticks made optional in zero elevation pad. --- src/libslic3r/SLA/SLABasePool.cpp | 24 ++++++++++++++---------- src/libslic3r/SLA/SLASupportTree.hpp | 2 +- src/libslic3r/SLAPrint.cpp | 12 ++++++------ 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index a882769f6f..62a078cb73 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -9,7 +9,7 @@ // For debugging: // #include // #include -#include "SVG.hpp" +// #include "SVG.hpp" namespace Slic3r { namespace sla { @@ -390,11 +390,13 @@ void offset_with_breakstick_holes(ExPolygon& poly, double penetration) { // We do the basic offsetting first - const bool dont_round_edges = false; - offset(poly, coord_t(padding / SCALING_FACTOR), dont_round_edges); + static const bool dont_round_edges = false; + + if(padding > 0.0) + offset(poly, coord_t(padding / SCALING_FACTOR), dont_round_edges); - SVG svg("bridgestick_plate.svg"); - svg.draw(poly); + // SVG svg("bridgestick_plate.svg"); + // svg.draw(poly); auto transf = [stick_width, penetration, padding, stride](Points &pts) { // The connector stick will be a small rectangle with dimensions @@ -453,12 +455,14 @@ void offset_with_breakstick_holes(ExPolygon& poly, out.shrink_to_fit(); pts.swap(out); }; - - transf(poly.contour.points); - for (auto &h : poly.holes) transf(h.points); - svg.draw(poly); - svg.Close(); + if(stride > 0.0 && stick_width > 0.0 && padding > 0.0) { + transf(poly.contour.points); + for (auto &h : poly.holes) transf(h.points); + } + + // svg.draw(poly); + // svg.Close(); } /// Only a debug function to generate top and bottom plates from a 2D shape. diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 93627d10c2..8602d8a46d 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -84,7 +84,7 @@ struct SupportConfig { // The shortest distance between a pillar base perimeter from the model // body. This is only useful when elevation is set to zero. - const double pillar_base_safety_distance_mm = 0.5; + double pillar_base_safety_distance_mm = 0.5; // ///////////////////////////////////////////////////////////////////////// // Compile time configuration values (candidates for runtime) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 14cf2b6ff4..78bd502203 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -595,7 +595,10 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); scfg.base_height_mm = c.support_base_height.getFloat(); - + scfg.pillar_base_safety_distance_mm = + c.support_base_safety_distance.getFloat() < EPSILON ? + scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); + return scfg; } @@ -1699,10 +1702,8 @@ bool SLAPrintObject::invalidate_all_steps() } double SLAPrintObject::get_elevation() const { - bool se = m_config.supports_enable.getBool(); - double ret = se? m_config.support_object_elevation.getFloat() : 0; + double ret = m_config.support_object_elevation.getFloat(); - // if the pad is enabled, then half of the pad height is its base plate if(m_config.pad_enable.getBool()) { // Normally the elevation for the pad itself would be the thickness of // its walls but currently it is half of its thickness. Whatever it @@ -1717,14 +1718,13 @@ double SLAPrintObject::get_elevation() const { double SLAPrintObject::get_current_elevation() const { - bool se = m_config.supports_enable.getBool(); bool has_supports = is_step_done(slaposSupportTree); bool has_pad = is_step_done(slaposBasePool); if(!has_supports && !has_pad) return 0; else if(has_supports && !has_pad) { - return se ? m_config.support_object_elevation.getFloat() : 0; + return m_config.support_object_elevation.getFloat(); } return get_elevation(); From d07b3fb08b5f8e81899a4772f8219fde49ef2126 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 12 Jun 2019 10:00:51 +0200 Subject: [PATCH 086/627] Bed shape dialog refactoring --- src/slic3r/GUI/2DBed.cpp | 67 +++++++++---------------------- src/slic3r/GUI/2DBed.hpp | 15 +++---- src/slic3r/GUI/BedShapeDialog.cpp | 18 ++++----- src/slic3r/GUI/BedShapeDialog.hpp | 7 ++-- src/slic3r/GUI/Tab.cpp | 32 +++++++++------ 5 files changed, 57 insertions(+), 82 deletions(-) diff --git a/src/slic3r/GUI/2DBed.cpp b/src/slic3r/GUI/2DBed.cpp index ce20124612..a339f3e66a 100644 --- a/src/slic3r/GUI/2DBed.cpp +++ b/src/slic3r/GUI/2DBed.cpp @@ -19,8 +19,6 @@ wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(25 * wxGetApp().em_unit(), - m_user_drawn_background = false; #endif /*__APPLE__*/ Bind(wxEVT_PAINT, ([this](wxPaintEvent &/* e */) { repaint(); })); - Bind(wxEVT_LEFT_DOWN, ([this](wxMouseEvent &event) { mouse_event(event); })); - Bind(wxEVT_MOTION, ([this](wxMouseEvent &event) { mouse_event(event); })); Bind(wxEVT_SIZE, ([this](wxSizeEvent & /* e */) { Refresh(); })); } void Bed_2D::repaint() @@ -43,22 +41,14 @@ void Bed_2D::repaint() dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight()); } - // turn cw and ch from sizes to max coordinates - cw--; - ch--; + if (m_bed_shape.empty()) + return; + + // reduce size to have some space around the drawn shape + cw -= (2 * Border); + ch -= (2 * Border); auto cbb = BoundingBoxf(Vec2d(0, 0),Vec2d(cw, ch)); - // leave space for origin point - cbb.min(0) += 4; - cbb.max -= Vec2d(4., 4.); - - // leave space for origin label - cbb.max(1) -= 13; - - // read new size - cw = cbb.size()(0); - ch = cbb.size()(1); - auto ccenter = cbb.center(); // get bounding box of bed shape in G - code coordinates @@ -76,17 +66,17 @@ void Bed_2D::repaint() ccenter(0) - bcenter(0) * sfactor, ccenter(1) - bcenter(1) * sfactor ); + m_scale_factor = sfactor; - m_shift = Vec2d(shift(0) + cbb.min(0), - shift(1) - (cbb.max(1) - GetSize().GetHeight())); + m_shift = Vec2d(shift(0) + cbb.min(0), shift(1) - (cbb.max(1) - ch)); // draw bed fill dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID)); wxPointList pt_list; for (auto pt: m_bed_shape) { - Point pt_pix = to_pixels(pt); - pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1))); + Point pt_pix = to_pixels(pt, ch); + pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1))); } dc.DrawPolygon(&pt_list, 0, 0); @@ -105,9 +95,9 @@ void Bed_2D::repaint() for (auto pl : polylines) { for (size_t i = 0; i < pl.points.size()-1; i++) { - Point pt1 = to_pixels(unscale(pl.points[i])); - Point pt2 = to_pixels(unscale(pl.points[i+1])); - dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1)); + Point pt1 = to_pixels(unscale(pl.points[i]), ch); + Point pt2 = to_pixels(unscale(pl.points[i + 1]), ch); + dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1)); } } @@ -116,7 +106,7 @@ void Bed_2D::repaint() dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT)); dc.DrawPolygon(&pt_list, 0, 0); - auto origin_px = to_pixels(Vec2d(0, 0)); + auto origin_px = to_pixels(Vec2d(0, 0), ch); // draw axes auto axes_len = 50; @@ -153,7 +143,7 @@ void Bed_2D::repaint() // draw current position if (m_pos!= Vec2d(0, 0)) { - auto pos_px = to_pixels(m_pos); + auto pos_px = to_pixels(m_pos, ch); dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxPENSTYLE_SOLID)); dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxBRUSHSTYLE_TRANSPARENT)); dc.DrawCircle(pos_px(0), pos_px(1), 5); @@ -161,35 +151,14 @@ void Bed_2D::repaint() dc.DrawLine(pos_px(0) - 15, pos_px(1), pos_px(0) + 15, pos_px(1)); dc.DrawLine(pos_px(0), pos_px(1) - 15, pos_px(0), pos_px(1) + 15); } - - m_painted = true; } + // convert G - code coordinates into pixels -Point Bed_2D::to_pixels(Vec2d point) +Point Bed_2D::to_pixels(Vec2d point, int height) { auto p = point * m_scale_factor + m_shift; - return Point(p(0), GetSize().GetHeight() - p(1)); -} - -void Bed_2D::mouse_event(wxMouseEvent event) -{ - if (!m_interactive) return; - if (!m_painted) return; - - auto pos = event.GetPosition(); - auto point = to_units(Point(pos.x, pos.y)); - if (event.LeftDown() || event.Dragging()) { - if (m_on_move) - m_on_move(point) ; - Refresh(); - } -} - -// convert pixels into G - code coordinates -Vec2d Bed_2D::to_units(Point point) -{ - return (Vec2d(point(0), GetSize().GetHeight() - point(1)) - m_shift) * (1. / m_scale_factor); + return Point(p(0) + Border, height - p(1) + Border); } void Bed_2D::set_pos(Vec2d pos) diff --git a/src/slic3r/GUI/2DBed.hpp b/src/slic3r/GUI/2DBed.hpp index 579ef44458..a61fb313d4 100644 --- a/src/slic3r/GUI/2DBed.hpp +++ b/src/slic3r/GUI/2DBed.hpp @@ -9,20 +9,17 @@ namespace GUI { class Bed_2D : public wxPanel { + static const int Border = 10; + bool m_user_drawn_background = true; - bool m_painted = false; - bool m_interactive = false; - double m_scale_factor; + double m_scale_factor; Vec2d m_shift = Vec2d::Zero(); Vec2d m_pos = Vec2d::Zero(); - std::function m_on_move = nullptr; - Point to_pixels(Vec2d point); - Vec2d to_units(Point point); - void repaint(); - void mouse_event(wxMouseEvent event); - void set_pos(Vec2d pos); + Point to_pixels(Vec2d point, int height); + void repaint(); + void set_pos(Vec2d pos); public: Bed_2D(wxWindow* parent); diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index d471a46c90..cec0f50677 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -30,11 +30,9 @@ void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt) SetMinSize(GetSize()); main_sizer->SetSizeHints(this); - // needed to actually free memory - this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent e) { - EndModal(wxID_OK); - Destroy(); - })); + this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent& evt) { + EndModal(wxID_CANCEL); + })); } void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) @@ -135,7 +133,7 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) // Called from the constructor. // Create a panel for a rectangular / circular / custom bed shape. -ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(wxString title) +ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) { auto panel = new wxPanel(m_shape_options_book); @@ -305,8 +303,9 @@ void BedShapePanel::update_shape() } m_canvas->m_bed_shape = points; } + else if (page_idx == SHAPE_CUSTOM) + m_canvas->m_bed_shape = m_loaded_bed_shape; -// $self->{on_change}->(); update_preview(); } @@ -351,8 +350,9 @@ void BedShapePanel::load_stl() std::vector points; for (auto pt : polygon.points) points.push_back(unscale(pt)); - m_canvas->m_bed_shape = points; - update_preview(); + + m_loaded_bed_shape = points; + update_shape(); } } // GUI diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 72e50a05d5..6600a1c84d 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -16,7 +16,8 @@ namespace GUI { using ConfigOptionsGroupShp = std::shared_ptr; class BedShapePanel : public wxPanel { - Bed_2D* m_canvas; + Bed_2D* m_canvas; + std::vector m_loaded_bed_shape; public: BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY) {} @@ -24,8 +25,8 @@ public: void build_panel(ConfigOptionPoints* default_pt); - ConfigOptionsGroupShp init_shape_options_page(wxString title); - void set_shape(ConfigOptionPoints* points); + ConfigOptionsGroupShp init_shape_options_page(const wxString& title); + void set_shape(ConfigOptionPoints* points); void update_preview(); void update_shape(); void load_stl(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6cd270e5b3..6cd32d3977 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1854,13 +1854,17 @@ void TabPrinter::build_fff() btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { - auto dlg = new BedShapeDialog(this); - dlg->build_dialog(m_config->option("bed_shape")); - if (dlg->ShowModal() == wxID_OK) { - load_key_value("bed_shape", dlg->GetValue()); - update_changed_ui(); - } - })); + BedShapeDialog dlg(this); + dlg.build_dialog(m_config->option("bed_shape")); + if (dlg.ShowModal() == wxID_OK) { + std::vector shape = dlg.GetValue(); + if (!shape.empty()) + { + load_key_value("bed_shape", shape); + update_changed_ui(); + } + } + })); return sizer; }; @@ -2056,11 +2060,15 @@ void TabPrinter::build_sla() btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { - auto dlg = new BedShapeDialog(this); - dlg->build_dialog(m_config->option("bed_shape")); - if (dlg->ShowModal() == wxID_OK) { - load_key_value("bed_shape", dlg->GetValue()); - update_changed_ui(); + BedShapeDialog dlg(this); + dlg.build_dialog(m_config->option("bed_shape")); + if (dlg.ShowModal() == wxID_OK) { + std::vector shape = dlg.GetValue(); + if (!shape.empty()) + { + load_key_value("bed_shape", shape); + update_changed_ui(); + } } })); From abdb5c5d742f17457f5fe0bec572f1fa668e6344 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 11 Jun 2019 09:59:33 +0200 Subject: [PATCH 087/627] Fixed conflicts after git cherry-picking 39cfe819daeb7dd71bae5ff20edfbe6396dffbe8 --- src/slic3r/GUI/Selection.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index ff994c32d5..480b59ba03 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -296,6 +296,9 @@ void Selection::clear() if (!m_valid) return; + if (m_list.empty()) + return; + for (unsigned int i : m_list) { (*m_volumes)[i]->selected = false; From 12396c3051a7abe26d33afe244bcabc938653e3e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 12 Jun 2019 13:15:42 +0200 Subject: [PATCH 088/627] Fine tuning parameters and fixing pad wings when greater gaps are used. --- src/libslic3r/PrintConfig.cpp | 15 +++++++++++++-- src/libslic3r/PrintConfig.hpp | 4 ++++ src/libslic3r/SLA/SLABasePool.cpp | 22 ++++++++-------------- src/libslic3r/SLA/SLABasePool.hpp | 10 +++++----- src/libslic3r/SLA/SLASupportTree.cpp | 18 +++++++++++++----- src/libslic3r/SLAPrint.cpp | 26 +++++++++++++++++++------- src/slic3r/GUI/Preset.cpp | 1 + src/slic3r/GUI/Tab.cpp | 1 + 8 files changed, 64 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 45ff20e789..377d53bef4 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2502,7 +2502,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->max = 10; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(0.5)); + def->set_default_value(new ConfigOptionFloat(1)); def = this->add("support_critical_angle", coFloat); def->label = L("Critical angle"); @@ -2625,6 +2625,17 @@ void PrintConfigDef::init_sla_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(45.0)); + def = this->add("pad_object_gap", coFloat); + def->label = L("Pad object gap"); + def->category = L("Pad"); + def->tooltip = L("The gap between the object bottom and the generated " + "pad in zero elevation mode."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(1)); + def = this->add("pad_object_connector_stride", coFloat); def->label = L("Pad object connector stride"); def->category = L("Pad"); @@ -2643,7 +2654,7 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("mm"); def->min = 0; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(0.3)); + def->set_default_value(new ConfigOptionFloat(0.5)); def = this->add("pad_object_connector_penetration", coFloat); def->label = L("Pad object connector penetration"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index e632e6946c..b5ddc4f137 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1033,6 +1033,9 @@ public: // - The two pads will be connected with tiny connector sticks // ///////////////////////////////////////////////////////////////////////// + // This is the gap between the object bottom and the generated pad + ConfigOptionFloat pad_object_gap; + // How far to place the connector sticks on the object pad perimeter ConfigOptionFloat pad_object_connector_stride; @@ -1071,6 +1074,7 @@ protected: OPT_PTR(pad_max_merge_distance); OPT_PTR(pad_edge_radius); OPT_PTR(pad_wall_slope); + OPT_PTR(pad_object_gap); OPT_PTR(pad_object_connector_stride); OPT_PTR(pad_object_connector_width); OPT_PTR(pad_object_connector_penetration); diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 62a078cb73..4a1259b5a4 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -383,18 +383,12 @@ Polygons unify(const Polygons& shapes) { // inserted along the perimeter in every "stride" distance. The stick rectangles // will have a with about "stick_width". The input dimensions are in world // measure, not the scaled clipper units. -void offset_with_breakstick_holes(ExPolygon& poly, - double padding, - double stride, - double stick_width, - double penetration) -{ - // We do the basic offsetting first - static const bool dont_round_edges = false; - - if(padding > 0.0) - offset(poly, coord_t(padding / SCALING_FACTOR), dont_round_edges); - +void breakstick_holes(ExPolygon& poly, + double padding, + double stride, + double stick_width, + double penetration) +{ // SVG svg("bridgestick_plate.svg"); // svg.draw(poly); @@ -428,8 +422,8 @@ void offset_with_breakstick_holes(ExPolygon& poly, out.emplace_back(a); // dodge the start point, do not make sticks on the joins - while (t < sright) t += sright; - double tend = nrm - sright; + while (t < sbottom) t += sbottom; + double tend = nrm - sbottom; while (t < tend) { // insert the stick on the polygon perimeter diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 129f7ccd46..8aa4f5f412 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -30,11 +30,11 @@ void base_plate(const TriangleMesh& mesh, // input mesh // inserted along the perimeter in every "stride" distance. The stick rectangles // will have a with about "stick_width". The input dimensions are in world // measure, not the scaled clipper units. -void offset_with_breakstick_holes(ExPolygon& poly, - double padding, - double stride, - double stick_width, - double penetration = 0.0); +void breakstick_holes(ExPolygon &poly, + double padding, + double stride, + double stick_width, + double penetration = 0.0); struct PoolConfig { double min_wall_thickness_mm = 2; diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index b9f2cc14e5..ba14b1811f 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -578,23 +578,31 @@ struct Pad { float(cfg.min_wall_height_mm + cfg.min_wall_thickness_mm), 0.1f, pcfg.throw_on_cancel); - // We don't need the holes for the base plate from the supports for (const ExPolygon &bp : platetmp) basep.emplace_back(bp.contour); - for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); + if(pcfg.embed_object) { - auto modelbase_sticks = modelbase; - for(auto& poly : modelbase_sticks) - sla::offset_with_breakstick_holes( + + if (pcfg.embed_object.object_gap_mm > 0.0) + modelbase_sticks + = offset_ex(modelbase_sticks, + coord_t(pcfg.embed_object.object_gap_mm + / SCALING_FACTOR)); + + for(auto& poly : modelbase_sticks) { + basep.emplace_back(poly); + sla::breakstick_holes( poly, pcfg.embed_object.object_gap_mm, // padding pcfg.embed_object.stick_stride_mm, pcfg.embed_object.stick_width_mm, pcfg.embed_object.stick_penetration_mm); + } create_base_pool(basep, tmesh, modelbase_sticks, cfg); } else { + for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); create_base_pool(basep, tmesh, {}, cfg); } diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 78bd502203..bff4c95876 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -602,17 +602,18 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { return scfg; } -sla::PoolConfig::EmbedObject use_builtin_pad(const SLAPrintObjectConfig& c) { +sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { sla::PoolConfig::EmbedObject ret; ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && c.pad_enable.getBool(); if(ret.enabled) { - ret.object_gap_mm = c.support_base_safety_distance.getFloat(); - ret.stick_width_mm = c.pad_object_connector_width.getFloat(); - ret.stick_stride_mm = c.pad_object_connector_stride.getFloat(); - ret.stick_width_mm = c.pad_object_connector_penetration.getFloat(); + ret.object_gap_mm = c.pad_object_gap.getFloat(); + ret.stick_width_mm = c.pad_object_connector_width.getFloat(); + ret.stick_stride_mm = c.pad_object_connector_stride.getFloat(); + ret.stick_penetration_mm = c.pad_object_connector_penetration + .getFloat(); } return ret; @@ -631,7 +632,7 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); // set builtin pad implicitly ON - pcfg.embed_object = use_builtin_pad(c); + pcfg.embed_object = builtin_pad_cfg(c); return pcfg; } @@ -663,6 +664,16 @@ std::string SLAPrint::validate() const if(supports_en && elv > EPSILON && elv < pinhead_width ) return L("Elevation is too low for object."); + + sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config()); + if(supports_en && builtinpad.enabled && + cfg.pillar_base_safety_distance_mm < builtinpad.object_gap_mm) { + return L( + "The endings of the support pillars will be deployed on the " + "gap between the object and the pad. 'Support base safety " + "distance' has to be greater than the 'Pad object gap' " + "parameter to avoid this."); + } } return ""; @@ -861,7 +872,7 @@ void SLAPrint::process() // If the builtin pad mode is engaged, we have to filter out all the // points that are on the bottom of the object - if(use_builtin_pad(po.m_config)) { + if(builtin_pad_cfg(po.m_config)) { double gnd = po.m_supportdata->emesh.ground_level(); auto & pts = po.m_supportdata->support_points; @@ -1658,6 +1669,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector& Preset::sla_print_options() "pad_max_merge_distance", "pad_edge_radius", "pad_wall_slope", + "pad_object_gap", "pad_object_connector_stride", "pad_object_connector_width", "pad_object_connector_penetration", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 8ca47f1b11..032bf95dfb 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3503,6 +3503,7 @@ void TabSLAPrint::build() // optgroup->append_single_option_line("pad_edge_radius"); optgroup->append_single_option_line("pad_wall_slope"); + optgroup->append_single_option_line("pad_object_gap"); optgroup->append_single_option_line("pad_object_connector_stride"); optgroup->append_single_option_line("pad_object_connector_width"); optgroup->append_single_option_line("pad_object_connector_penetration"); From 10897524df042dae63939cec8251f93940de7916 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 12 Jun 2019 15:29:24 +0200 Subject: [PATCH 089/627] Fixes for gap detection and case with no pad, but zero elevation. --- src/libslic3r/SLA/SLASupportTree.cpp | 21 ++++++++++++++++----- src/libslic3r/SLAPrint.cpp | 21 +++++++++++---------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index ba14b1811f..c2540ba282 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1410,6 +1410,7 @@ class SLASupportTree::Algorithm { { // People were killed for this number (seriously) static const double SQR2 = std::sqrt(2.0); + static const Vec3d DOWN = {0.0, 0.0, -1.0}; double gndlvl = m_result.ground_level; Vec3d endp = {jp(X), jp(Y), gndlvl}; @@ -1451,20 +1452,30 @@ class SLASupportTree::Algorithm { initvals(mv), bound(0.0, 2 * min_dist)); mv = std::get<0>(result.optimum); - endp = jp + std::sqrt(2) * mv * dir; + endp = jp + SQR2 * mv * dir; Vec3d pgnd = {endp(X), endp(Y), gndlvl}; can_add_base = result.score > min_dist; + + auto abort_in_shame = + [&normal_mode, &can_add_base, &endp, jp, gndlvl]() + { + normal_mode = true; + can_add_base = false; // Nothing left to do, hope for the best + endp = {jp(X), jp(Y), gndlvl}; + }; // We have to check if the bridge is feasible. - if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) { - normal_mode = true; - endp = {jp(X), jp(Y), gndlvl}; - } + if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) + abort_in_shame(); else { // If the new endpoint is below ground, do not make a pillar if (endp(Z) < gndlvl) endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off else { + + if (!std::isinf(bridge_mesh_intersect(endp, DOWN, radius))) + abort_in_shame(); + Pillar &plr = m_result.add_pillar(endp, pgnd, radius); if (can_add_base) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index bff4c95876..99e2915ea0 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -439,12 +439,10 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf update_apply_status(this->invalidate_all_steps()); m_objects = print_objects_new; // Delete the PrintObjects marked as Unknown or Deleted. - bool deleted_objects = false; for (auto &pos : print_object_status) if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { update_apply_status(pos.print_object->invalidate_all_steps()); delete pos.print_object; - deleted_objects = true; } if (new_objects) update_apply_status(false); @@ -870,19 +868,22 @@ void SLAPrint::process() po.m_supportdata->support_points = po.transformed_support_points(); } - // If the builtin pad mode is engaged, we have to filter out all the + // If the zero elevation mode is engaged, we have to filter out all the // points that are on the bottom of the object - if(builtin_pad_cfg(po.m_config)) { - double gnd = po.m_supportdata->emesh.ground_level(); - auto & pts = po.m_supportdata->support_points; - + if (po.config().support_object_elevation.getFloat() <= EPSILON) { + double gnd = po.m_supportdata->emesh.ground_level(); + auto & pts = po.m_supportdata->support_points; + double tolerance = po.config().pad_enable.getBool() + ? po.m_config.pad_wall_thickness.getFloat() + : po.m_config.support_base_height.getFloat(); + // get iterator to the reorganized vector end auto endit = std::remove_if( pts.begin(), pts.end(), - [&po, gnd](const sla::SupportPoint &sp) { + [tolerance, gnd](const sla::SupportPoint &sp) { double diff = std::abs(gnd - double(sp.pos(Z))); - return diff <= po.m_config.pad_wall_thickness.getFloat(); + return diff <= tolerance; }); // erase all elements after the new end @@ -1352,7 +1353,7 @@ void SLAPrint::process() }; // Rasterizing the model objects, and their supports - auto rasterize = [this, max_objstatus]() { + auto rasterize = [this]() { if(canceled()) return; // collect all the keys From 1694204687ca93e7bd831cf5a5598bc1acfce658 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 12 Jun 2019 16:28:25 +0200 Subject: [PATCH 090/627] Added some logic to layers editor selection --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 126 ++++++++++++++++++---------- src/slic3r/GUI/GUI_ObjectLayers.hpp | 26 ++++-- src/slic3r/GUI/GUI_ObjectList.cpp | 44 ++++++++-- src/slic3r/GUI/GUI_ObjectList.hpp | 4 +- 4 files changed, 136 insertions(+), 64 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index ba0012a2b7..5f0ec259d7 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -39,72 +39,83 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : m_bmp_add = ScalableBitmap(parent, "add_copies"); } +void ObjectLayers::select_editor(LayerRangeEditor* editor, const bool is_last_edited_range) +{ + if (is_last_edited_range && m_selection_type == editor->type()) { + editor->SetFocus(); + editor->SetInsertionPointEnd(); + } +} + wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) { const bool is_last_edited_range = range == m_last_edited_range; + auto set_focus_fn = [range, this](const EditorType type) + { + m_last_edited_range = range; + m_selection_type = type; + }; + // Add control for the "Min Z" - auto temp = new LayerRangeEditor(m_parent, double_to_string(range.first), - [range, this](coordf_t min_z) + auto editor = new LayerRangeEditor(m_parent, double_to_string(range.first), etMinZ, + set_focus_fn, [range, this](coordf_t min_z, bool enter_pressed) { - if (fabs(min_z - range.first) < EPSILON) { - m_selection_type = sitUndef; - return false; // LayersList would not be updated/recreated + if (fabs(min_z - range.first) < EPSILON || min_z > range.second) { + m_selection_type = etUndef; + return false; } // data for next focusing - m_last_edited_range = { min_z, range.second }; - m_selection_type = sitMinZ; + const t_layer_height_range& new_range = { min_z, range.second }; + if (enter_pressed) { + m_last_edited_range = new_range; + m_selection_type = etMinZ; + } - wxGetApp().obj_list()->edit_layer_range(range, m_last_edited_range); - return true; // LayersList will be updated/recreated + return wxGetApp().obj_list()->edit_layer_range(range, new_range); }); - if (is_last_edited_range && m_selection_type == sitMinZ) { - temp->SetFocus(); - temp->SetInsertionPointEnd(); - } - - m_grid_sizer->Add(temp); + select_editor(editor, is_last_edited_range); + m_grid_sizer->Add(editor); // Add control for the "Max Z" - temp = new LayerRangeEditor(m_parent, double_to_string(range.second), - [range, this](coordf_t max_z) + editor = new LayerRangeEditor(m_parent, double_to_string(range.second), etMaxZ, + set_focus_fn, [range, this](coordf_t max_z, bool enter_pressed) { - if (fabs(max_z - range.second) < EPSILON) { - m_selection_type = sitUndef; + if (fabs(max_z - range.second) < EPSILON || range.first > max_z) { + m_selection_type = etUndef; return false; // LayersList would not be updated/recreated } // data for next focusing - m_last_edited_range = { range.first, max_z }; - m_selection_type = sitMaxZ; + const t_layer_height_range& new_range = { range.first, max_z }; + if (enter_pressed) { + m_last_edited_range = new_range; + m_selection_type = etMaxZ; + } - wxGetApp().obj_list()->edit_layer_range(range, m_last_edited_range); - return true; // LayersList will not be updated/recreated + return wxGetApp().obj_list()->edit_layer_range(range, new_range); }); - if (is_last_edited_range && m_selection_type == sitMaxZ) { - temp->SetFocus(); - temp->SetInsertionPointEnd(); - } - - m_grid_sizer->Add(temp); + select_editor(editor, is_last_edited_range); + m_grid_sizer->Add(editor); // Add control for the "Layer height" - temp = new LayerRangeEditor(m_parent, + editor = new LayerRangeEditor(m_parent, double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()), - [range, this](coordf_t layer_height) + etLayerHeight, set_focus_fn, [range, this](coordf_t layer_height, bool) { - wxGetApp().obj_list()->edit_layer_range(range, layer_height); - return false; // LayersList would not be updated/recreated + return wxGetApp().obj_list()->edit_layer_range(range, layer_height); }); + select_editor(editor, is_last_edited_range); + auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(temp); + sizer->Add(editor); m_grid_sizer->Add(sizer); return sizer; @@ -193,34 +204,61 @@ void ObjectLayers::msw_rescale() LayerRangeEditor::LayerRangeEditor( wxWindow* parent, const wxString& value, - std::function edit_fn + EditorType type, + std::function set_focus_fn, + std::function edit_fn ) : + m_valid_value(value), + m_type(type), wxTextCtrl(parent, wxID_ANY, value, wxDefaultPosition, wxSize(8 * em_unit(parent), wxDefaultCoord), wxTE_PROCESS_ENTER) { this->SetFont(wxGetApp().normal_font()); - this->Bind(wxEVT_TEXT_ENTER, ([this, edit_fn](wxEvent& e) + this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&) { m_enter_pressed = true; // If LayersList wasn't updated/recreated, we can call wxEVT_KILL_FOCUS.Skip() - if ( !edit_fn(get_value()) ) + if (m_type&etLayerHeight) { + if (!edit_fn(get_value(), true)) + SetValue(m_valid_value); + else + m_valid_value = double_to_string(get_value()); m_call_kill_focus = true; - }), this->GetId()); + } + else if (!edit_fn(get_value(), true)) { + SetValue(m_valid_value); + m_call_kill_focus = true; + } + }, this->GetId()); - this->Bind(wxEVT_KILL_FOCUS, ([this, edit_fn](wxEvent& e) + this->Bind(wxEVT_KILL_FOCUS, [this, edit_fn](wxFocusEvent& e) { if (!m_enter_pressed) { - m_enter_pressed = false; - // If LayersList wasn't updated/recreated, we should call e.Skip() - if ( !edit_fn(get_value()) ) + if (m_type & etLayerHeight) { + if (!edit_fn(get_value(), false)) + SetValue(m_valid_value); + else + m_valid_value = double_to_string(get_value()); e.Skip(); + } + else if (!edit_fn(get_value(), false)) { + SetValue(m_valid_value); + e.Skip(); + } } - else if (m_call_kill_focus) + else if (m_call_kill_focus) { + m_call_kill_focus = false; e.Skip(); - }), this->GetId()); + } + }, this->GetId()); + this->Bind(wxEVT_LEFT_DOWN, ([this, set_focus_fn](wxEvent& e) + { + set_focus_fn(m_type); + e.Skip(); + })); this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event) { diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 562e049728..9c2af20e57 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -19,18 +19,32 @@ class ConfigOptionsGroup; typedef double coordf_t; typedef std::pair t_layer_height_range; +enum EditorType +{ + etUndef = 0, + etMinZ = 1, + etMaxZ = 2, + etLayerHeight = 4, +}; + class LayerRangeEditor : public wxTextCtrl { bool m_enter_pressed { false }; bool m_call_kill_focus { false }; + wxString m_valid_value; + EditorType m_type; public: LayerRangeEditor( wxWindow* parent, const wxString& value = wxEmptyString, - std::function edit_fn = [](coordf_t) {return false; } + EditorType type = etUndef, + std::function set_focus_fn = [](EditorType) {;}, + std::function edit_fn = [](coordf_t, bool) {return false; } ); ~LayerRangeEditor() {} + EditorType type() const {return m_type;} + private: coordf_t get_value(); }; @@ -43,19 +57,13 @@ class ObjectLayers : public OG_Settings wxFlexGridSizer* m_grid_sizer; t_layer_height_range m_last_edited_range; - - enum SelectedItemType - { - sitUndef, - sitMinZ, - sitMaxZ, - sitLayerHeight, - } m_selection_type {sitUndef}; + EditorType m_selection_type {etUndef}; public: ObjectLayers(wxWindow* parent); ~ObjectLayers() {} + void select_editor(LayerRangeEditor* editor, const bool is_last_edited_range); wxSizer* create_layer(const t_layer_height_range& range); // without_buttons void create_layers_list(); void update_layers_list(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 23eb67c508..b1b57fcd6e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2363,6 +2363,18 @@ void ObjectList::del_layer_range(const t_layer_height_range& range) select_item(selectable_item); } +double get_min_layer_height(const int extruder_idx) +{ + const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + return config.opt_float("min_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1); +} + +double get_max_layer_height(const int extruder_idx) +{ + const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + return config.opt_float("max_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1); +} + void ObjectList::add_layer_range_after_current(const t_layer_height_range& current_range) { const int obj_idx = get_selected_obj_idx(); @@ -2393,13 +2405,14 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre if (current_range.second == next_range.first) { + const auto old_config = ranges.at(next_range); + const coordf_t delta = (next_range.second - next_range.first); - if (delta < 0.05f) // next range division has no sense + if (delta < get_min_layer_height(old_config.opt_int("extruder"))/*0.05f*/) // next range division has no sense return; const coordf_t midl_layer = next_range.first + 0.5f * delta; - const auto old_config = ranges.at(next_range); t_layer_height_range new_range = { midl_layer, next_range.second }; // delete old layer @@ -2450,21 +2463,32 @@ void ObjectList::add_layer_item(const t_layer_height_range& range, select_item(m_objects_model->AddSettingsChild(layer_item)); } -void ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height) +bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height) { const int obj_idx = get_selected_obj_idx(); - if (obj_idx < 0) return; + if (obj_idx < 0) + return false; - t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; + DynamicPrintConfig* config = &object(obj_idx)->layer_config_ranges[range]; + if (fabs(layer_height - config->opt_float("layer_height")) < EPSILON) + return false; - DynamicPrintConfig* config = &ranges[range]; - config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); + const int extruder_idx = config->opt_int("extruder"); + + if (layer_height >= get_min_layer_height(extruder_idx) && + layer_height <= get_max_layer_height(extruder_idx)) + { + config->set_key_value("layer_height", new ConfigOptionFloat(layer_height)); + return true; + } + + return false; } -void ObjectList::edit_layer_range(const t_layer_height_range& range, const t_layer_height_range& new_range) +bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_layer_height_range& new_range) { const int obj_idx = get_selected_obj_idx(); - if (obj_idx < 0) return; + if (obj_idx < 0) return false; t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; @@ -2484,6 +2508,8 @@ void ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay // To update(recreate) layers sizer call select_item for LayerRoot item expand select_item(root_item); Expand(root_item); + + return true; } void ObjectList::init_objects() diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index ed055a3a68..ed19edd624 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -291,8 +291,8 @@ public: void add_layer_item (const t_layer_height_range& range, const wxDataViewItem layers_item, const int layer_idx = -1); - void edit_layer_range(const t_layer_height_range& range, coordf_t layer_height); - void edit_layer_range(const t_layer_height_range& range, + bool edit_layer_range(const t_layer_height_range& range, coordf_t layer_height); + bool edit_layer_range(const t_layer_height_range& range, const t_layer_height_range& new_range); void init_objects(); From 4ffe3278bee9bd7441fd3a5410376cd0586947db Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 12 Jun 2019 17:09:40 +0200 Subject: [PATCH 091/627] Hotfix for pad shape deduction. --- src/libslic3r/SLA/SLASupportTree.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index c2540ba282..4910226cc7 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -580,9 +580,8 @@ struct Pad { for (const ExPolygon &bp : platetmp) basep.emplace_back(bp.contour); - if(pcfg.embed_object) { - auto modelbase_sticks = modelbase; + ExPolygons modelbase_sticks = modelbase; if (pcfg.embed_object.object_gap_mm > 0.0) modelbase_sticks @@ -591,7 +590,7 @@ struct Pad { / SCALING_FACTOR)); for(auto& poly : modelbase_sticks) { - basep.emplace_back(poly); + basep.emplace_back(poly.contour); sla::breakstick_holes( poly, pcfg.embed_object.object_gap_mm, // padding From d1ed3d40c1c1da26f6d0b0da9e2d54a928050796 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 12 Jun 2019 17:23:12 +0200 Subject: [PATCH 092/627] Fix build on windows. This issue is annoying. --- src/libslic3r/SLAPrint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 99e2915ea0..3c73416568 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1353,7 +1353,7 @@ void SLAPrint::process() }; // Rasterizing the model objects, and their supports - auto rasterize = [this]() { + auto rasterize = [this, max_objstatus]() { if(canceled()) return; // collect all the keys From e4cb75eddeaecb6a6c2b6d9694113c8fd82b59f4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 12 Jun 2019 17:33:04 +0200 Subject: [PATCH 093/627] Fix build on Mac --- src/libslic3r/SLA/SLASupportTree.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 4910226cc7..b74f73d172 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1472,8 +1472,8 @@ class SLASupportTree::Algorithm { endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off else { - if (!std::isinf(bridge_mesh_intersect(endp, DOWN, radius))) - abort_in_shame(); + auto hit = bridge_mesh_intersect(endp, DOWN, radius); + if (!std::isinf(hit.distance())) abort_in_shame(); Pillar &plr = m_result.add_pillar(endp, pgnd, radius); From 7bfb0aaac0a8f6ba257610a12347247a6621dbb8 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 13 Jun 2019 08:38:49 +0200 Subject: [PATCH 094/627] Fixed method Camera::get_dir_forward() --- src/slic3r/GUI/Camera.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 6e1b539ab6..adb1488073 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -56,7 +56,7 @@ public: Vec3d get_dir_right() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(0); } Vec3d get_dir_up() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(1); } - Vec3d get_dir_forward() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(2); } + Vec3d get_dir_forward() const { return -m_view_matrix.matrix().block(0, 0, 3, 3).row(2); } Vec3d get_position() const { return m_view_matrix.matrix().block(0, 3, 3, 1); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index d118a6877f..72db3a9dd4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -550,7 +550,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const Selection& selection = m_parent.get_selection(); const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const Transform3d& instance_matrix_no_translation_no_scaling = volume->get_instance_transformation().get_matrix(true,false,true); - Vec3f direction_to_camera = camera.get_dir_forward().cast(); + Vec3f direction_to_camera = -camera.get_dir_forward().cast(); Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast() * direction_to_camera).normalized().eval(); Vec3f scaling = volume->get_instance_scaling_factor().cast(); direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2)); From 9805417028c142784900ec5efbca9cd7f150c80b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 13 Jun 2019 08:47:38 +0200 Subject: [PATCH 095/627] Fixed method Camera::get_position() --- src/slic3r/GUI/Camera.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index adb1488073..1c75ef4b69 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -58,7 +58,7 @@ public: Vec3d get_dir_up() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(1); } Vec3d get_dir_forward() const { return -m_view_matrix.matrix().block(0, 0, 3, 3).row(2); } - Vec3d get_position() const { return m_view_matrix.matrix().block(0, 3, 3, 1); } + Vec3d get_position() const { return m_view_matrix.matrix().inverse().block(0, 3, 3, 1); } void apply_viewport(int x, int y, unsigned int w, unsigned int h) const; void apply_view_matrix() const; From f0b228c4d2f2d385bbfd3fe917fd928ffaadd1d3 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 13 Jun 2019 09:12:44 +0200 Subject: [PATCH 096/627] Added support for distance between camera position and camera target --- src/slic3r/GUI/Camera.cpp | 15 +++++++++++---- src/slic3r/GUI/Camera.hpp | 5 ++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index a9edb76264..4c7cb314ac 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -22,11 +22,13 @@ static const float VIEW_REAR[2] = { 180.0f, 90.0f }; namespace Slic3r { namespace GUI { +const float Camera::DefaultDistance = 1000.0f; + Camera::Camera() : type(Ortho) , zoom(1.0f) , phi(45.0f) -// , distance(0.0f) + , distance(DefaultDistance) , requires_zoom_to_bed(false) , inverted_phi(false) , m_theta(45.0f) @@ -107,12 +109,18 @@ void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h) const void Camera::apply_view_matrix() const { + double theta_rad = Geometry::deg2rad(-(double)m_theta); + double phi_rad = Geometry::deg2rad((double)phi); + double sin_theta = ::sin(theta_rad); + Vec3d camera_pos = m_target + (double)distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad)); + glsafe(::glMatrixMode(GL_MODELVIEW)); glsafe(::glLoadIdentity()); glsafe(::glRotatef(-m_theta, 1.0f, 0.0f, 0.0f)); // pitch glsafe(::glRotatef(phi, 0.0f, 0.0f, 1.0f)); // yaw - glsafe(::glTranslated(-m_target(0), -m_target(1), -m_target(2))); // target to origin + + glsafe(::glTranslated(-camera_pos(0), -camera_pos(1), -camera_pos(2))); glsafe(::glGetDoublev(GL_MODELVIEW_MATRIX, m_view_matrix.data())); } @@ -136,8 +144,7 @@ void Camera::apply_projection(const BoundingBoxf3& box) const // FIXME: calculate a tighter value for depth will improve z-fighting // Set at least some minimum depth in case the bounding box is empty to avoid an OpenGL driver error. double depth = std::max(1.0, 5.0 * box.max_size()); - apply_ortho_projection(-w2, w2, -h2, h2, -depth, depth); - + apply_ortho_projection(-w2, w2, -h2, h2, (double)distance - depth, (double)distance + depth); break; } // case Perspective: diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 4b719dc238..83dbb0f6d8 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -9,6 +9,8 @@ namespace GUI { struct Camera { + static const float DefaultDistance; + enum EType : unsigned char { Unknown, @@ -20,7 +22,8 @@ struct Camera EType type; float zoom; float phi; -// float distance; + // Distance between camera position and camera target measured along the camera Z axis + float distance; bool requires_zoom_to_bed; bool inverted_phi; From a99466ef1df99d52277a5f78a4fbb5481d2dd584 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 13 Jun 2019 10:24:19 +0200 Subject: [PATCH 097/627] Method Camera::apply_projection() called at every rendered frame --- src/slic3r/GUI/Camera.cpp | 5 ++++- src/slic3r/GUI/GLCanvas3D.cpp | 9 ++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 4c7cb314ac..5aa881e3e5 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -48,7 +48,7 @@ std::string Camera::get_type_as_string() const // case Perspective: // return "perspective"; case Ortho: - return "ortho"; + return "orthographic"; }; } @@ -160,12 +160,15 @@ void Camera::debug_render() const imgui.set_next_window_bg_alpha(0.5f); imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + std::string type = get_type_as_string(); Vec3f position = get_position().cast(); Vec3f target = m_target.cast(); Vec3f forward = get_dir_forward().cast(); Vec3f right = get_dir_right().cast(); Vec3f up = get_dir_up().cast(); + ImGui::InputText("Type", const_cast(type.data()), type.length(), ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); ImGui::InputFloat3("Position", position.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Target", target.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::Separator(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 151ec3bdb3..6648d744bd 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1624,6 +1624,7 @@ void GLCanvas3D::render() } m_camera.apply_view_matrix(); + m_camera.apply_projection(_max_bounding_box()); GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f }; glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam)); @@ -2515,7 +2516,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) m_layers_editing.band_width = std::max(std::min(m_layers_editing.band_width * (1.0f + 0.1f * (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()), 10.0f), 1.5f); if (m_canvas != nullptr) m_canvas->Refresh(); - + return; } } @@ -2526,8 +2527,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) return; // Calculate the zoom delta and apply it to the current zoom factor - float zoom = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta(); - set_camera_zoom(zoom); + set_camera_zoom((float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()); } void GLCanvas3D::on_timer(wxTimerEvent& evt) @@ -3293,7 +3293,7 @@ void GLCanvas3D::set_camera_zoom(float zoom) zoom = std::min(zoom, 100.0f); m_camera.zoom = zoom; - _refresh_if_shown_on_screen(); + m_dirty = true; } void GLCanvas3D::update_gizmos_on_off_state() @@ -3609,7 +3609,6 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) // updates camera m_camera.apply_viewport(0, 0, w, h); - m_camera.apply_projection(_max_bounding_box()); m_dirty = false; } From c9dd5f878699e79d64ccb4059f37b8967e872270 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 Jun 2019 11:37:03 +0200 Subject: [PATCH 098/627] Fixed updating of data for LayerEditors selection --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 39 ++++++++++++++++------------- src/slic3r/GUI/GUI_ObjectLayers.hpp | 5 +++- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 5f0ec259d7..df0c4faeb4 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -49,18 +49,27 @@ void ObjectLayers::select_editor(LayerRangeEditor* editor, const bool is_last_ed wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) { - const bool is_last_edited_range = range == m_last_edited_range; + const bool is_last_edited_range = range == m_selectable_range; auto set_focus_fn = [range, this](const EditorType type) { - m_last_edited_range = range; + m_selectable_range = range; m_selection_type = type; }; + auto set_focus = [range, this](const t_layer_height_range& new_range, EditorType type, bool enter_pressed) + { + // change selectable range for new one, if enter was pressed or if same range was selected + if (enter_pressed || m_selectable_range == range) + m_selectable_range = new_range; + if (enter_pressed) + m_selection_type = type; + }; + // Add control for the "Min Z" auto editor = new LayerRangeEditor(m_parent, double_to_string(range.first), etMinZ, - set_focus_fn, [range, this](coordf_t min_z, bool enter_pressed) + set_focus_fn, [range, set_focus, this](coordf_t min_z, bool enter_pressed) { if (fabs(min_z - range.first) < EPSILON || min_z > range.second) { m_selection_type = etUndef; @@ -69,10 +78,7 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) // data for next focusing const t_layer_height_range& new_range = { min_z, range.second }; - if (enter_pressed) { - m_last_edited_range = new_range; - m_selection_type = etMinZ; - } + set_focus(new_range, etMinZ, enter_pressed); return wxGetApp().obj_list()->edit_layer_range(range, new_range); }); @@ -83,7 +89,7 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) // Add control for the "Max Z" editor = new LayerRangeEditor(m_parent, double_to_string(range.second), etMaxZ, - set_focus_fn, [range, this](coordf_t max_z, bool enter_pressed) + set_focus_fn, [range, set_focus, this](coordf_t max_z, bool enter_pressed) { if (fabs(max_z - range.second) < EPSILON || range.first > max_z) { m_selection_type = etUndef; @@ -92,10 +98,7 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) // data for next focusing const t_layer_height_range& new_range = { range.first, max_z }; - if (enter_pressed) { - m_last_edited_range = new_range; - m_selection_type = etMaxZ; - } + set_focus(new_range, etMaxZ, enter_pressed); return wxGetApp().obj_list()->edit_layer_range(range, new_range); }); @@ -210,6 +213,7 @@ LayerRangeEditor::LayerRangeEditor( wxWindow* parent, ) : m_valid_value(value), m_type(type), + m_set_focus(set_focus_fn), wxTextCtrl(parent, wxID_ANY, value, wxDefaultPosition, wxSize(8 * em_unit(parent), wxDefaultCoord), wxTE_PROCESS_ENTER) { @@ -235,6 +239,11 @@ LayerRangeEditor::LayerRangeEditor( wxWindow* parent, this->Bind(wxEVT_KILL_FOCUS, [this, edit_fn](wxFocusEvent& e) { if (!m_enter_pressed) { + // update data for next editor selection + LayerRangeEditor* new_editor = dynamic_cast(e.GetWindow()); + if (new_editor) + new_editor->set_focus(); + // If LayersList wasn't updated/recreated, we should call e.Skip() if (m_type & etLayerHeight) { if (!edit_fn(get_value(), false)) @@ -254,12 +263,6 @@ LayerRangeEditor::LayerRangeEditor( wxWindow* parent, } }, this->GetId()); - this->Bind(wxEVT_LEFT_DOWN, ([this, set_focus_fn](wxEvent& e) - { - set_focus_fn(m_type); - e.Skip(); - })); - this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event) { // select all text using Ctrl+A diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 9c2af20e57..e3366e03ea 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -34,6 +34,8 @@ class LayerRangeEditor : public wxTextCtrl wxString m_valid_value; EditorType m_type; + std::function m_set_focus; + public: LayerRangeEditor( wxWindow* parent, const wxString& value = wxEmptyString, @@ -44,6 +46,7 @@ public: ~LayerRangeEditor() {} EditorType type() const {return m_type;} + void set_focus() const { m_set_focus(m_type);} private: coordf_t get_value(); @@ -56,7 +59,7 @@ class ObjectLayers : public OG_Settings ModelObject* m_object {nullptr}; wxFlexGridSizer* m_grid_sizer; - t_layer_height_range m_last_edited_range; + t_layer_height_range m_selectable_range; EditorType m_selection_type {etUndef}; public: From e00774d2e21319ddfacbad417f7dd7309e8f2f56 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 Jun 2019 13:00:46 +0200 Subject: [PATCH 099/627] Workarounds for selection under OSX and GTK --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index df0c4faeb4..12c1022ceb 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -42,8 +42,19 @@ ObjectLayers::ObjectLayers(wxWindow* parent) : void ObjectLayers::select_editor(LayerRangeEditor* editor, const bool is_last_edited_range) { if (is_last_edited_range && m_selection_type == editor->type()) { + /* Workaround! Under OSX we should use CallAfter() for SetFocus() after LayerEditors "reorganizations", + * because of selected control's strange behavior: + * cursor is set to the control, but blue border - doesn't. + * And as a result we couldn't edit this control. + * */ +#ifdef __WXOSX__ + wxTheApp->CallAfter([editor]() { +#endif editor->SetFocus(); editor->SetInsertionPointEnd(); +#ifdef __WXOSX__ + }); +#endif } } @@ -239,11 +250,15 @@ LayerRangeEditor::LayerRangeEditor( wxWindow* parent, this->Bind(wxEVT_KILL_FOCUS, [this, edit_fn](wxFocusEvent& e) { if (!m_enter_pressed) { - // update data for next editor selection +#ifndef __WXGTK__ + /* Update data for next editor selection. + * But under GTK it lucks like there is no information about selected control at e.GetWindow(), + * so we'll take it from wxEVT_LEFT_DOWN event + * */ LayerRangeEditor* new_editor = dynamic_cast(e.GetWindow()); if (new_editor) new_editor->set_focus(); - +#endif // not __WXGTK__ // If LayersList wasn't updated/recreated, we should call e.Skip() if (m_type & etLayerHeight) { if (!edit_fn(get_value(), false)) @@ -263,6 +278,14 @@ LayerRangeEditor::LayerRangeEditor( wxWindow* parent, } }, this->GetId()); +#ifdef __WXGTK__ // Workaround! To take information about selectable range + this->Bind(wxEVT_LEFT_DOWN, [this](wxEvent& e) + { + set_focus(); + e.Skip(); + }, this->GetId()); +#endif //__WXGTK__ + this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event) { // select all text using Ctrl+A From 821ca0e36a4d85d19ca01b404967f53672b5625f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 13 Jun 2019 13:15:10 +0200 Subject: [PATCH 100/627] Build fix when boost is not in prefix/boost --- CMakeLists.txt | 39 +++++++++++++------ src/CMakeLists.txt | 4 +- src/admesh/CMakeLists.txt | 2 +- src/boost/CMakeLists.txt | 2 +- .../libnest2d/backends/clipper/CMakeLists.txt | 4 +- src/libslic3r/CMakeLists.txt | 3 +- src/slic3r/GUI/ProgressStatusBar.hpp | 1 + xs/CMakeLists.txt | 12 ------ 8 files changed, 36 insertions(+), 31 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de435813d0..b4e0224f70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,17 +240,34 @@ if(NOT WIN32) set(MINIMUM_BOOST_VERSION "1.64.0") endif() find_package(Boost ${MINIMUM_BOOST_VERSION} REQUIRED COMPONENTS system filesystem thread log locale regex) -if(Boost_FOUND) -# message("Boost include dir: ${Boost_INCLUDE_DIRS}") -# message("Boost library dirs: ${Boost_LIBRARY_DIRS}") -# message("Boost libraries: ${Boost_LIBRARIES}") - if (APPLE) - # BOOST_ASIO_DISABLE_KQUEUE : prevents a Boost ASIO bug on OS X: https://svn.boost.org/trac/boost/ticket/5339 - add_definitions(-DBOOST_ASIO_DISABLE_KQUEUE) - endif() - if(NOT SLIC3R_STATIC) - add_definitions(-DBOOST_LOG_DYN_LINK) - endif() + +add_library(boost_libs INTERFACE) +add_library(boost_headeronly INTERFACE) + +if (APPLE) + # BOOST_ASIO_DISABLE_KQUEUE : prevents a Boost ASIO bug on OS X: https://svn.boost.org/trac/boost/ticket/5339 + target_compile_definitions(boost_headeronly INTERFACE BOOST_ASIO_DISABLE_KQUEUE) +endif() + +if(NOT SLIC3R_STATIC) + target_compile_definitions(boost_headeronly INTERFACE BOOST_LOG_DYN_LINK) +endif() + +if(TARGET Boost::system) + message(STATUS "Boost::boost exists") + target_link_libraries(boost_headeronly INTERFACE Boost::boost) + target_link_libraries(boost_libs INTERFACE + boost_headeronly # includes the custom compile definitions as well + Boost::system + Boost::filesystem + Boost::thread + Boost::log + Boost::locale + Boost::regex + ) +else() + target_include_directories(boost_headeronly INTERFACE ${Boost_INCLUDE_DIRS}) + target_link_libraries(boost_libs INTERFACE boost_headeronly ${Boost_LIBRARIES}) endif() # Find and configure intel-tbb diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2f2483eca8..7240634668 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -139,7 +139,7 @@ if (MSVC) target_compile_definitions(PrusaSlicer_app_gui PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE) add_dependencies(PrusaSlicer_app_gui PrusaSlicer) set_target_properties(PrusaSlicer_app_gui PROPERTIES OUTPUT_NAME "prusa-slicer") - target_include_directories(PrusaSlicer_app_gui SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) + target_link_libraries(PrusaSlicer_app_gui PRIVATE boost_headeronly) add_executable(PrusaSlicer_app_console PrusaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.rc) # Generate debug symbols even in release mode. @@ -147,7 +147,7 @@ if (MSVC) target_compile_definitions(PrusaSlicer_app_console PRIVATE -DSLIC3R_WRAPPER_CONSOLE) add_dependencies(PrusaSlicer_app_console PrusaSlicer) set_target_properties(PrusaSlicer_app_console PROPERTIES OUTPUT_NAME "prusa-slicer-console") - target_include_directories(PrusaSlicer_app_console SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) + target_link_libraries(PrusaSlicer_app_console PRIVATE boost_headeronly) endif () # Link the resources dir to where Slic3r GUI expects it diff --git a/src/admesh/CMakeLists.txt b/src/admesh/CMakeLists.txt index 941a7eeb55..7d0177782f 100644 --- a/src/admesh/CMakeLists.txt +++ b/src/admesh/CMakeLists.txt @@ -11,4 +11,4 @@ add_library(admesh STATIC util.cpp ) -target_include_directories(admesh SYSTEM PRIVATE ${Boost_INCLUDE_DIRS}) +target_link_libraries(admesh PRIVATE boost_headeronly) diff --git a/src/boost/CMakeLists.txt b/src/boost/CMakeLists.txt index aae87340b3..12fe6b4e52 100644 --- a/src/boost/CMakeLists.txt +++ b/src/boost/CMakeLists.txt @@ -19,6 +19,6 @@ add_library(nowide STATIC nowide/windows.hpp ) -target_include_directories(nowide SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) +target_link_libraries(nowide PUBLIC boost_headeronly) diff --git a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt index e20cbc70da..cf8a373504 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt +++ b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt @@ -56,12 +56,12 @@ endif() # Clipper backend is not enough on its own, it still needs some functions # from Boost geometry -if(NOT Boost_INCLUDE_DIRS_FOUND) +if(NOT Boost_FOUND) find_package(Boost 1.58 REQUIRED) # TODO automatic download of boost geometry headers endif() -target_include_directories(clipperBackend SYSTEM INTERFACE ${Boost_INCLUDE_DIRS} ) +target_link_libraries(clipperBackend INTERFACE Boost::boost ) #target_sources(ClipperBackend INTERFACE # ${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp # ${CMAKE_CURRENT_SOURCE_DIR}/clipper_polygon.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 833b1ae629..312a82c4c8 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -182,13 +182,12 @@ if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) endif () target_compile_definitions(libslic3r PUBLIC -DUSE_TBB) -target_include_directories(libslic3r SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(libslic3r libnest2d admesh miniz - ${Boost_LIBRARIES} + boost_libs clipper nowide ${EXPAT_LIBRARIES} diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index 225b0331ef..8c65964758 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -3,6 +3,7 @@ #include #include +#include class wxTimer; class wxGauge; diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 4696badc49..aaf943970e 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -119,8 +119,6 @@ target_include_directories(XS PRIVATE src ${LIBDIR}/libslic3r) target_compile_definitions(XS PRIVATE -DSLIC3RXS) set_target_properties(XS PROPERTIES PREFIX "") # Prevent cmake from generating libXS.so instead of XS.so -target_link_libraries(XS ${Boost_LIBRARIES}) - if (APPLE) # -liconv: boost links to libiconv by default target_link_libraries(XS "-liconv -framework IOKit" "-framework CoreFoundation" -lc++) @@ -156,12 +154,6 @@ if (WIN32) target_link_libraries(XS ${PERL_LIBRARY}) endif() -target_link_libraries(XS ${Boost_LIBRARIES}) -target_link_libraries(XS ${TBB_LIBRARIES}) -# target_link_libraries(XS ${wxWidgets_LIBRARIES}) -target_link_libraries(XS ${EXPAT_LIBRARIES}) -# target_link_libraries(XS ${GLEW_LIBRARIES}) - # Install the XS.pm and XS.{so,dll,bundle} into the local-lib directory. set(PERL_LOCAL_LIB_DIR "../../local-lib/lib/perl5/${PerlEmbed_ARCHNAME}") add_custom_command( @@ -181,10 +173,6 @@ if(APPLE) ) endif() -if(SLIC3R_PROFILE) - target_link_libraries(Shiny) -endif() - if (MSVC) # Here we associate some additional properties with the MSVC project to enable compilation and debugging out of the box. get_filename_component(PROPS_PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY) From a9ee4ff4b5439e22ef39fec3d50968261f30eac2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 Jun 2019 15:35:10 +0200 Subject: [PATCH 101/627] Fixed object selection in right panel form the Scene by context menu --- src/slic3r/GUI/GUI_ObjectList.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index dffa02e95b..d8d99fdf46 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2150,9 +2150,11 @@ void ObjectList::update_selections() if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) { const auto item = GetSelection(); - if (selection.is_single_full_object() && - m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx()) - return; + if (selection.is_single_full_object()) { + if ( m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx()) + return; + sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); + } if (selection.is_single_volume() || selection.is_any_modifier()) { const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin()); if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) From 4712c5bbdf807a8e944bd163459027906f5dd79f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 Jun 2019 16:10:33 +0200 Subject: [PATCH 102/627] DoubleSlider manipulation from preview scene --- src/slic3r/GUI/GLCanvas3D.cpp | 15 +++++++++++++++ src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/GUI_Preview.cpp | 6 ++++++ src/slic3r/GUI/GUI_Preview.hpp | 1 + src/slic3r/GUI/Plater.cpp | 1 + 5 files changed, 24 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index dbfde926c9..fd3e0f7c9e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1210,6 +1210,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) : m_canvas(canvas) @@ -2469,6 +2470,20 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) } else if (keyCode == WXK_CONTROL) m_dirty = true; + // DoubleSlider navigation in Preview + else if (keyCode == WXK_LEFT || + keyCode == WXK_RIGHT || + keyCode == WXK_UP || + keyCode == WXK_DOWN || + keyCode == '+' || + keyCode == WXK_NUMPAD_ADD || + keyCode == '-' || + keyCode == 390 || + keyCode == WXK_DELETE || + keyCode == WXK_BACK ) + { + post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, evt)); + } } } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 51a36cf69f..96bb56bd93 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -124,6 +124,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); class GLCanvas3D { diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index d28b921d92..ec7308382a 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -414,6 +414,12 @@ void Preview::msw_rescale() refresh_print(); } +void Preview::move_double_slider(wxKeyEvent& evt) +{ + if (m_slider) + m_slider->OnKeyDown(evt); +} + void Preview::bind_event_handlers() { this->Bind(wxEVT_SIZE, &Preview::on_size, this); diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 1838082c38..ed4555f5cc 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -122,6 +122,7 @@ public: void refresh_print(); void msw_rescale(); + void move_double_slider(wxKeyEvent& evt); private: bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 44f77b3f78..bdf2498770 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1517,6 +1517,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); }); q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); From 86e7a07dd8130462771d91540cc3daa6e0a8bc9c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 Jun 2019 16:17:54 +0200 Subject: [PATCH 103/627] Fixed selection --- src/slic3r/GUI/GUI_ObjectList.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b1b57fcd6e..31396b630c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2543,9 +2543,11 @@ void ObjectList::update_selections() if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) & (itSettings | itLayerRoot | itLayer)) { const auto item = GetSelection(); - if (selection.is_single_full_object() && - m_objects_model->GetIdByItem(m_objects_model->GetParent(item)) == selection.get_object_idx()) - return; + if (selection.is_single_full_object()) { + if (m_objects_model->GetObjectIdByItem(item) == selection.get_object_idx()) + return; + sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); + } if (selection.is_single_volume() || selection.is_any_modifier()) { const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin()); if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) From 9379fedd439826846d97eb02c7d88455aa50c47e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 13 Jun 2019 16:33:50 +0200 Subject: [PATCH 104/627] Further C++isation of the admesh library & TriangleMesh (copy & move constructors / operators) --- src/admesh/connect.cpp | 2 ++ src/admesh/stl.h | 26 ++++++++++++++- src/admesh/stlinit.cpp | 10 +----- src/admesh/util.cpp | 43 ++++++++++++++++++++----- src/libslic3r/TriangleMesh.cpp | 47 +++++++++++++++------------- src/libslic3r/TriangleMesh.hpp | 14 ++++----- src/slic3r/Config/Version.cpp | 5 +++ src/slic3r/Utils/FixModelByWin10.cpp | 4 +-- 8 files changed, 102 insertions(+), 49 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index 1a7b7965d4..5afb85d2c0 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -30,6 +30,8 @@ #include #include +// Boost pool: Don't use mutexes to synchronize memory allocation. +#define BOOST_POOL_NO_MT #include #include "stl.h" diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 40bced2f4f..87210b3c93 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -89,6 +89,8 @@ struct stl_neighbors { }; struct stl_stats { + stl_stats() { this->reset(); } + void reset() { memset(this, 0, sizeof(stl_stats)); this->volume = -1.0; } char header[81]; stl_type type; uint32_t number_of_facets; @@ -117,6 +119,18 @@ struct stl_stats { }; struct stl_file { + stl_file() {} + stl_file(const stl_file &rhs) : facet_start(rhs.facet_start), neighbors_start(rhs.neighbors_start), stats(rhs.stats) {} + stl_file(stl_file &&rhs) : facet_start(std::move(rhs.facet_start)), neighbors_start(std::move(rhs.neighbors_start)), stats(rhs.stats) {} + stl_file& operator=(const stl_file &rhs) { this->facet_start = rhs.facet_start; this->neighbors_start = rhs.neighbors_start; this->stats = rhs.stats; return *this; } + stl_file& operator=(stl_file &&rhs) { this->facet_start = std::move(rhs.facet_start); this->neighbors_start = std::move(rhs.neighbors_start); this->stats = rhs.stats; return *this; } + + void clear() { + this->facet_start.clear(); + this->neighbors_start.clear(); + this->stats.reset(); + } + std::vector facet_start; std::vector neighbors_start; // Statistics @@ -125,7 +139,14 @@ struct stl_file { struct indexed_triangle_set { + indexed_triangle_set() {} + indexed_triangle_set(const indexed_triangle_set &rhs) : indices(rhs.indices), vertices(rhs.vertices) {} + indexed_triangle_set(indexed_triangle_set &&rhs) : indices(std::move(rhs.indices)), vertices(std::move(rhs.vertices)) {} + indexed_triangle_set& operator=(const indexed_triangle_set &rhs) { this->indices = rhs.indices; this->vertices = rhs.vertices; return *this; } + indexed_triangle_set& operator=(indexed_triangle_set &&rhs) { this->indices = std::move(rhs.indices); this->vertices = std::move(rhs.vertices); return *this; } + void clear() { indices.clear(); vertices.clear(); } + std::vector indices; std::vector vertices; //FIXME add normals once we get rid of the stl_file from TriangleMesh completely. @@ -238,6 +259,10 @@ inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix()).template cast().eval(); } +extern void its_rotate_x(indexed_triangle_set &its, float angle); +extern void its_rotate_y(indexed_triangle_set &its, float angle); +extern void its_rotate_z(indexed_triangle_set &its, float angle); + extern void stl_generate_shared_vertices(stl_file *stl, indexed_triangle_set &its); extern bool its_write_obj(const indexed_triangle_set &its, const char *file); extern bool its_write_off(const indexed_triangle_set &its, const char *file); @@ -258,7 +283,6 @@ extern void stl_calculate_volume(stl_file *stl); extern void stl_repair(stl_file *stl, bool fixall_flag, bool exact_flag, bool tolerance_flag, float tolerance, bool increment_flag, float increment, bool nearby_flag, int iterations, bool remove_unconnected_flag, bool fill_holes_flag, bool normal_directions_flag, bool normal_values_flag, bool reverse_all_flag, bool verbose_flag); -extern void stl_reset(stl_file *stl); extern void stl_allocate(stl_file *stl); extern void stl_read(stl_file *stl, int first_facet, bool first); extern void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first); diff --git a/src/admesh/stlinit.cpp b/src/admesh/stlinit.cpp index 0cc6e50c70..a328baa75f 100644 --- a/src/admesh/stlinit.cpp +++ b/src/admesh/stlinit.cpp @@ -221,7 +221,7 @@ static bool stl_read(stl_file *stl, FILE *fp, int first_facet, bool first) bool stl_open(stl_file *stl, const char *file) { - stl_reset(stl); + stl->clear(); FILE *fp = stl_open_count_facets(stl, file); if (fp == nullptr) return false; @@ -231,14 +231,6 @@ bool stl_open(stl_file *stl, const char *file) return result; } -void stl_reset(stl_file *stl) -{ - stl->facet_start.clear(); - stl->neighbors_start.clear(); - memset(&stl->stats, 0, sizeof(stl_stats)); - stl->stats.volume = -1.0; -} - #ifndef BOOST_LITTLE_ENDIAN extern void stl_internal_reverse_quads(char *buf, size_t cnt); #endif /* BOOST_LITTLE_ENDIAN */ diff --git a/src/admesh/util.cpp b/src/admesh/util.cpp index 70f4ffc27d..029e44a28e 100644 --- a/src/admesh/util.cpp +++ b/src/admesh/util.cpp @@ -115,12 +115,12 @@ static void calculate_normals(stl_file *stl) } } -static void rotate_point_2d(float *x, float *y, const double c, const double s) +static inline void rotate_point_2d(float &x, float &y, const double c, const double s) { - double xold = *x; - double yold = *y; - *x = float(c * xold - s * yold); - *y = float(s * xold + c * yold); + double xold = x; + double yold = y; + x = float(c * xold - s * yold); + y = float(s * xold + c * yold); } void stl_rotate_x(stl_file *stl, float angle) @@ -130,7 +130,7 @@ void stl_rotate_x(stl_file *stl, float angle) double s = sin(radian_angle); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) - rotate_point_2d(&stl->facet_start[i].vertex[j](1), &stl->facet_start[i].vertex[j](2), c, s); + rotate_point_2d(stl->facet_start[i].vertex[j](1), stl->facet_start[i].vertex[j](2), c, s); stl_get_size(stl); calculate_normals(stl); } @@ -142,7 +142,7 @@ void stl_rotate_y(stl_file *stl, float angle) double s = sin(radian_angle); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) - rotate_point_2d(&stl->facet_start[i].vertex[j](2), &stl->facet_start[i].vertex[j](0), c, s); + rotate_point_2d(stl->facet_start[i].vertex[j](2), stl->facet_start[i].vertex[j](0), c, s); stl_get_size(stl); calculate_normals(stl); } @@ -154,11 +154,38 @@ void stl_rotate_z(stl_file *stl, float angle) double s = sin(radian_angle); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) for (int j = 0; j < 3; ++ j) - rotate_point_2d(&stl->facet_start[i].vertex[j](0), &stl->facet_start[i].vertex[j](1), c, s); + rotate_point_2d(stl->facet_start[i].vertex[j](0), stl->facet_start[i].vertex[j](1), c, s); stl_get_size(stl); calculate_normals(stl); } +void its_rotate_x(indexed_triangle_set &its, float angle) +{ + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); + for (stl_vertex &v : its.vertices) + rotate_point_2d(v(1), v(2), c, s); +} + +void its_rotate_y(indexed_triangle_set& its, float angle) +{ + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); + for (stl_vertex& v : its.vertices) + rotate_point_2d(v(2), v(0), c, s); +} + +void its_rotate_z(indexed_triangle_set& its, float angle) +{ + double radian_angle = (angle / 180.0) * M_PI; + double c = cos(radian_angle); + double s = sin(radian_angle); + for (stl_vertex& v : its.vertices) + rotate_point_2d(v(0), v(1), c, s); +} + void stl_get_size(stl_file *stl) { if (stl->stats.number_of_facets == 0) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 11f45147ca..56accfefa9 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -42,10 +42,8 @@ namespace Slic3r { -TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets) - : repaired(false) +TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& facets) : repaired(false) { - stl_reset(&this->stl); stl_file &stl = this->stl; stl.stats.type = inmemory; @@ -72,14 +70,6 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& f stl_get_size(&stl); } -TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) -{ - stl_reset(&this->stl); - this->stl = other.stl; - this->repaired = other.repaired; - return *this; -} - // #define SLIC3R_TRACE_REPAIR void TriangleMesh::repair(bool update_shared_vertices) @@ -254,13 +244,18 @@ void TriangleMesh::WriteOBJFile(const char* output_file) void TriangleMesh::scale(float factor) { stl_scale(&(this->stl), factor); - this->its.clear(); + for (stl_vertex& v : this->its.vertices) + v *= factor; } void TriangleMesh::scale(const Vec3d &versor) { stl_scale_versor(&this->stl, versor.cast()); - this->its.clear(); + for (stl_vertex& v : this->its.vertices) { + v.x() *= versor.x(); + v.y() *= versor.y(); + v.z() *= versor.z(); + } } void TriangleMesh::translate(float x, float y, float z) @@ -268,7 +263,9 @@ void TriangleMesh::translate(float x, float y, float z) if (x == 0.f && y == 0.f && z == 0.f) return; stl_translate_relative(&(this->stl), x, y, z); - this->its.clear(); + stl_vertex shift(x, y, z); + for (stl_vertex& v : this->its.vertices) + v += shift; } void TriangleMesh::translate(const Vec3f &displacement) @@ -285,13 +282,15 @@ void TriangleMesh::rotate(float angle, const Axis &axis) angle = Slic3r::Geometry::rad2deg(angle); if (axis == X) { - stl_rotate_x(&(this->stl), angle); + stl_rotate_x(&this->stl, angle); + its_rotate_x(this->its, angle); } else if (axis == Y) { - stl_rotate_y(&(this->stl), angle); + stl_rotate_y(&this->stl, angle); + its_rotate_y(this->its, angle); } else if (axis == Z) { - stl_rotate_z(&(this->stl), angle); + stl_rotate_z(&this->stl, angle); + its_rotate_z(this->its, angle); } - this->its.clear(); } void TriangleMesh::rotate(float angle, const Vec3d& axis) @@ -310,12 +309,17 @@ void TriangleMesh::mirror(const Axis &axis) { if (axis == X) { stl_mirror_yz(&this->stl); + for (stl_vertex &v : this->its.vertices) + v(0) *= -1.0; } else if (axis == Y) { stl_mirror_xz(&this->stl); + for (stl_vertex &v : this->its.vertices) + v(1) *= -1.0; } else if (axis == Z) { stl_mirror_xy(&this->stl); + for (stl_vertex &v : this->its.vertices) + v(2) *= -1.0; } - this->its.clear(); } void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) @@ -358,7 +362,8 @@ void TriangleMesh::rotate(double angle, Point* center) return; Vec2f c = center->cast(); this->translate(-c(0), -c(1), 0); - stl_rotate_z(&(this->stl), (float)angle); + stl_rotate_z(&this->stl, (float)angle); + its_rotate_z(this->its, (float)angle); this->translate(c(0), c(1), 0); } @@ -540,7 +545,7 @@ TriangleMesh TriangleMesh::convex_hull_3d() const { if (this->has_shared_vertices()) { #if REALfloat - qhull.runQhull("", 3, (int)this->its.vertices.size() / 3, (const realT*)(this->its.vertices.front().data()), "Qt"); + qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt"); #else src_vertices.reserve(this->its.vertices() * 3); // We will now fill the vector with input points for computation: diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index ffd9b7e628..54c6dc5d00 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -21,15 +21,13 @@ typedef std::vector TriangleMeshPtrs; class TriangleMesh { public: - TriangleMesh() : repaired(false) { stl_reset(&this->stl); } + TriangleMesh() : repaired(false) {} TriangleMesh(const Pointf3s &points, const std::vector &facets); - TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_reset(&this->stl); *this = other; } - TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_reset(&this->stl); this->swap(other); } - ~TriangleMesh() { clear(); } - TriangleMesh& operator=(const TriangleMesh &other); - TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; } - void clear() { stl_reset(&this->stl); this->repaired = false; } - void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); } + TriangleMesh(const TriangleMesh& rhs) : stl(rhs.stl), its(rhs.its), repaired(rhs.repaired) {} + TriangleMesh(TriangleMesh&& rhs) : stl(std::move(rhs.stl)), its(std::move(rhs.its)), repaired(rhs.repaired) {} + TriangleMesh& operator=(const TriangleMesh& rhs) { this->stl = rhs.stl; this->its = rhs.its; this->repaired = rhs.repaired; return *this; } + TriangleMesh& operator=(TriangleMesh &&rhs) { this->stl = std::move(rhs.stl); this->its = std::move(rhs.its); this->repaired = rhs.repaired; return *this; } + void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; } bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); } bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); } bool write_binary(const char* output_file) { return stl_write_binary(&this->stl, output_file, ""); } diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp index 2eda135d6e..fe3adfd7f1 100644 --- a/src/slic3r/Config/Version.cpp +++ b/src/slic3r/Config/Version.cpp @@ -198,6 +198,11 @@ size_t Index::load(const boost::filesystem::path &path) size_t idx_line = 0; Version ver; while (std::getline(ifs, line)) { +#ifndef _MSVCVER + // On a Unix system, getline does not remove the trailing carriage returns, if the index is shared over a Windows filesystem. Remove them manually. + while (! line.empty() && line.back() == '\r') + line.pop_back(); +#endif ++ idx_line; // Skip the initial white spaces. char *key = left_trim(const_cast(line.data())); diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 1daeaff269..710f19090b 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -389,10 +389,10 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) throw std::runtime_error(L("Repaired 3MF file does not contain any volume")); if (model.objects.front()->volumes.size() > 1) throw std::runtime_error(L("Repaired 3MF file contains more than one volume")); - meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh)); + meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh())); } for (size_t i = 0; i < volumes.size(); ++ i) { - volumes[i]->mesh = std::move(meshes_repaired[i]); + volumes[i]->set_mesh(std::move(meshes_repaired[i])); volumes[i]->set_new_unique_id(); } model_object.invalidate_bounding_box(); From dbfa4e6c838eda34fd059ca2f6ce71f2130c5180 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 13 Jun 2019 16:55:12 +0200 Subject: [PATCH 105/627] Fix of a smart pointer gymnastics from previous commit --- src/slic3r/GUI/3DScene.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index b9ac668e00..0414a1ed3c 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -393,7 +393,7 @@ public: double get_sla_shift_z() const { return m_sla_shift_z; } void set_sla_shift_z(double z) { m_sla_shift_z = z; } - void set_convex_hull(std::shared_ptr &convex_hull) { m_convex_hull = convex_hull; } + void set_convex_hull(std::shared_ptr convex_hull) { m_convex_hull = std::move(convex_hull); } void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared(convex_hull); } void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared(std::move(convex_hull)); } From 9b7bb41db5b949285d28206a4ff44078bce37ecf Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 13 Jun 2019 17:24:37 +0200 Subject: [PATCH 106/627] ModelObject::add_volume(const ModelVolume &other) shall not re-center the volume as it will share meshes (object mesh, convex hull mesh) of the source, which may be in use by the background processing. --- src/libslic3r/Model.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 113b215033..8e879a3e68 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -691,8 +691,9 @@ ModelVolume* ModelObject::add_volume(const ModelVolume &other) { ModelVolume* v = new ModelVolume(this, other); this->volumes.push_back(v); - v->center_geometry_after_creation(); - this->invalidate_bounding_box(); + // The volume should already be centered at this point of time when copying shared pointers of the triangle mesh and convex hull. +// v->center_geometry_after_creation(); +// this->invalidate_bounding_box(); return v; } From d750d4f9255bfb388ba7b53d3a366ac16d84a7d0 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 13 Jun 2019 17:42:55 +0200 Subject: [PATCH 107/627] Re-enable high power graphics card on Windows. This is a regression issue against 1.41.3 --- src/PrusaSlicer.cpp | 3 +++ src/PrusaSlicer_app_msvc.cpp | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index a3247b929d..2becb8071a 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -7,10 +7,13 @@ #include #include #ifdef SLIC3R_GUI + extern "C" + { // Let the NVIDIA and AMD know we want to use their graphics card // on a dual graphics card system. __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + } #endif /* SLIC3R_GUI */ #endif /* WIN32 */ diff --git a/src/PrusaSlicer_app_msvc.cpp b/src/PrusaSlicer_app_msvc.cpp index 5b01751b9d..ee8cdf6968 100644 --- a/src/PrusaSlicer_app_msvc.cpp +++ b/src/PrusaSlicer_app_msvc.cpp @@ -8,10 +8,14 @@ #include #ifdef SLIC3R_GUI +//Turn on high power graphics for NVidia cards on laptops (with built in graphics cards + Nvidia cards) +extern "C" +{ // Let the NVIDIA and AMD know we want to use their graphics card // on a dual graphics card system. __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +} #endif /* SLIC3R_GUI */ #include From 1a91add2e60f3a4d22cdd6c8a10e57dc8095e0a2 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 14 Jun 2019 10:38:09 +0200 Subject: [PATCH 108/627] Tighter camera frustrum to reduce z-fighting --- src/slic3r/GUI/3DBed.cpp | 26 +++++++- src/slic3r/GUI/3DBed.hpp | 8 ++- src/slic3r/GUI/Camera.cpp | 55 ++++++++++++++-- src/slic3r/GUI/Camera.hpp | 9 +++ src/slic3r/GUI/GLCanvas3D.cpp | 76 ++++++++++------------- src/slic3r/GUI/GLCanvas3D.hpp | 3 +- src/slic3r/GUI/GLTexture.hpp | 1 + src/slic3r/GUI/GLToolbar.cpp | 7 --- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 9 --- 9 files changed, 123 insertions(+), 71 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 8392e534a4..c82b34f498 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -273,6 +273,7 @@ void Bed3D::Axes::render_axis(double length) const Bed3D::Bed3D() : m_type(Custom) + , m_extended_bounding_box_dirty(true) #if ENABLE_TEXTURES_FROM_SVG , m_vbo_id(0) #endif // ENABLE_TEXTURES_FROM_SVG @@ -290,7 +291,7 @@ bool Bed3D::set_shape(const Pointfs& shape) m_shape = shape; m_type = new_type; - calc_bounding_box(); + calc_bounding_boxes(); ExPolygon poly; for (const Vec2d& p : m_shape) @@ -311,7 +312,9 @@ bool Bed3D::set_shape(const Pointfs& shape) // Set the origin and size for painting of the coordinate system axes. m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z); - m_axes.length = 0.1 * get_bounding_box().max_size() * Vec3d::Ones(); + m_axes.length = 0.1 * m_bounding_box.max_size() * Vec3d::Ones(); + + m_extended_bounding_box_dirty = true; // Let the calee to update the UI. return true; @@ -400,13 +403,27 @@ void Bed3D::render_axes() const m_axes.render(); } -void Bed3D::calc_bounding_box() +void Bed3D::calc_bounding_boxes() const { m_bounding_box = BoundingBoxf3(); for (const Vec2d& p : m_shape) { m_bounding_box.merge(Vec3d(p(0), p(1), 0.0)); } + + m_extended_bounding_box = m_bounding_box; + + if (m_extended_bounding_box_dirty) + { + // extend to contain Z axis + m_extended_bounding_box.merge(0.1 * m_bounding_box.max_size() * Vec3d::UnitZ()); + + if (!m_model.get_filename().empty()) + // extend to contain model + m_extended_bounding_box.merge(m_model.get_bounding_box()); + + m_extended_bounding_box_dirty = false; + } } void Bed3D::calc_triangles(const ExPolygon& poly) @@ -539,6 +556,9 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const offset += Vec3d(0.0, 0.0, -0.03); m_model.center_around(offset); + + // update extended bounding box + calc_bounding_boxes(); } if (!m_model.get_filename().empty()) diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index e60cdf94e1..79e96fdf01 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -85,7 +85,9 @@ public: private: EType m_type; Pointfs m_shape; - BoundingBoxf3 m_bounding_box; + mutable BoundingBoxf3 m_bounding_box; + mutable BoundingBoxf3 m_extended_bounding_box; + mutable bool m_extended_bounding_box_dirty; Polygon m_polygon; GeometryBuffer m_triangles; GeometryBuffer m_gridlines; @@ -117,7 +119,7 @@ public: // Return true if the bed shape changed, so the calee will update the UI. bool set_shape(const Pointfs& shape); - const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } + const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; } bool contains(const Point& point) const; Point point_projection(const Point& point) const; @@ -125,7 +127,7 @@ public: void render_axes() const; private: - void calc_bounding_box(); + void calc_bounding_boxes() const; void calc_triangles(const ExPolygon& poly); void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); EType detect_type(const Pointfs& shape) const; diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 5aa881e3e5..1fc2f6be36 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -23,6 +23,8 @@ namespace Slic3r { namespace GUI { const float Camera::DefaultDistance = 1000.0f; +double Camera::FrustrumMinZSize = 50.0; +double Camera::FrustrumZMargin = 10.0; Camera::Camera() : type(Ortho) @@ -127,6 +129,8 @@ void Camera::apply_view_matrix() const void Camera::apply_projection(const BoundingBoxf3& box) const { + m_frustrum_zs = calc_tight_frustrum_zs_around(box); + switch (type) { case Ortho: @@ -141,10 +145,7 @@ void Camera::apply_projection(const BoundingBoxf3& box) const h2 *= inv_two_zoom; } - // FIXME: calculate a tighter value for depth will improve z-fighting - // Set at least some minimum depth in case the bounding box is empty to avoid an OpenGL driver error. - double depth = std::max(1.0, 5.0 * box.max_size()); - apply_ortho_projection(-w2, w2, -h2, h2, (double)distance - depth, (double)distance + depth); + apply_ortho_projection(-w2, w2, -h2, h2, m_frustrum_zs.first, m_frustrum_zs.second); break; } // case Perspective: @@ -166,6 +167,8 @@ void Camera::debug_render() const Vec3f forward = get_dir_forward().cast(); Vec3f right = get_dir_right().cast(); Vec3f up = get_dir_up().cast(); + float nearZ = (float)m_frustrum_zs.first; + float farZ = (float)m_frustrum_zs.second; ImGui::InputText("Type", const_cast(type.data()), type.length(), ImGuiInputTextFlags_ReadOnly); ImGui::Separator(); @@ -175,6 +178,9 @@ void Camera::debug_render() const ImGui::InputFloat3("Forward", forward.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Right", right.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Up", up.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); + ImGui::InputFloat("Near Z", &nearZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat("Far Z", &farZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); imgui.end(); } #endif // ENABLE_CAMERA_STATISTICS @@ -190,6 +196,47 @@ void Camera::apply_ortho_projection(double x_min, double x_max, double y_min, do glsafe(::glMatrixMode(GL_MODELVIEW)); } +std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const +{ + std::pair ret = std::make_pair(DBL_MAX, -DBL_MAX); + + Vec3d bb_min = box.min; + Vec3d bb_max = box.max; + + // bbox vertices in world space + std::vector vertices; + vertices.reserve(8); + vertices.push_back(bb_min); + vertices.emplace_back(bb_max(0), bb_min(1), bb_min(2)); + vertices.emplace_back(bb_max(0), bb_max(1), bb_min(2)); + vertices.emplace_back(bb_min(0), bb_max(1), bb_min(2)); + vertices.emplace_back(bb_min(0), bb_min(1), bb_max(2)); + vertices.emplace_back(bb_max(0), bb_min(1), bb_max(2)); + vertices.push_back(bb_max); + vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2)); + + // set the Z range in eye coordinates (only negative Zs are in front of the camera) + for (const Vec3d& v : vertices) + { + // ensure non-negative values + double z = std::max(-(m_view_matrix * v)(2), 0.0); + ret.first = std::min(ret.first, z); + ret.second = std::max(ret.second, z); + } + + // apply margin + ret.first -= FrustrumZMargin; + ret.second += FrustrumZMargin; + + // ensure min size + if (ret.second - ret.first < FrustrumMinZSize) + ret.second = ret.first + FrustrumMinZSize; + + assert(ret.first > 0.0); + + return ret; +} + } // GUI } // Slic3r diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 83dbb0f6d8..18dec6083c 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -10,6 +10,8 @@ namespace GUI { struct Camera { static const float DefaultDistance; + static double FrustrumMinZSize; + static double FrustrumZMargin; enum EType : unsigned char { @@ -34,6 +36,7 @@ private: mutable std::array m_viewport; mutable Transform3d m_view_matrix; mutable Transform3d m_projection_matrix; + mutable std::pair m_frustrum_zs; BoundingBoxf3 m_scene_box; @@ -63,6 +66,9 @@ public: Vec3d get_position() const { return m_view_matrix.matrix().inverse().block(0, 3, 3, 1); } + double get_near_z() const { return m_frustrum_zs.first; } + double get_far_z() const { return m_frustrum_zs.second; } + void apply_viewport(int x, int y, unsigned int w, unsigned int h) const; void apply_view_matrix() const; void apply_projection(const BoundingBoxf3& box) const; @@ -73,6 +79,9 @@ public: private: void apply_ortho_projection(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max) const; + // returns tight values for nearZ and farZ plane around the given bounding box + // the camera MUST be outside of the bounding box in eye coordinate of the given box + std::pair calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const; }; } // GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6648d744bd..6980e5267d 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -304,22 +304,10 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const const Rect& bar_rect = get_bar_rect_viewport(canvas); const Rect& reset_rect = get_reset_rect_viewport(canvas); - glsafe(::glDisable(GL_DEPTH_TEST)); - - // The viewport and camera are set to complete view and glOrtho(-$x / 2, $x / 2, -$y / 2, $y / 2, -$depth, $depth), - // where x, y is the window size divided by $self->_zoom. - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - _render_tooltip_texture(canvas, bar_rect, reset_rect); _render_reset_texture(reset_rect); _render_active_object_annotations(canvas, bar_rect); _render_profile(bar_rect); - - // Revert the matrices. - glsafe(::glPopMatrix()); - - glsafe(::glEnable(GL_DEPTH_TEST)); } float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) @@ -880,10 +868,6 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) { - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - const Size& cnv_size = canvas.get_canvas_size(); float zoom = canvas.get_camera().zoom; float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; @@ -904,9 +888,6 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const uvs.right_top = { uv_right, uv_top }; GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs); - - glsafe(::glPopMatrix()); - glsafe(::glEnable(GL_DEPTH_TEST)); } } @@ -1160,10 +1141,6 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const { if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) { - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - const Size& cnv_size = canvas.get_canvas_size(); float zoom = canvas.get_camera().zoom; float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; @@ -1184,9 +1161,6 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const uvs.right_top = { uv_right, uv_top }; GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs); - - glsafe(::glPopMatrix()); - glsafe(::glEnable(GL_DEPTH_TEST)); } } @@ -1466,7 +1440,7 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const BoundingBoxf3 GLCanvas3D::scene_bounding_box() const { BoundingBoxf3 bb = volumes_bounding_box(); - bb.merge(m_bed.get_bounding_box()); + bb.merge(m_bed.get_bounding_box(false)); if (m_config != nullptr) { @@ -1550,7 +1524,7 @@ void GLCanvas3D::allow_multisample(bool allow) void GLCanvas3D::zoom_to_bed() { - _zoom_to_bounding_box(m_bed.get_bounding_box()); + _zoom_to_bounding_box(m_bed.get_bounding_box(false)); } void GLCanvas3D::zoom_to_volumes() @@ -1624,7 +1598,7 @@ void GLCanvas3D::render() } m_camera.apply_view_matrix(); - m_camera.apply_projection(_max_bounding_box()); + m_camera.apply_projection(_max_bounding_box(true)); GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f }; glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam)); @@ -1686,16 +1660,7 @@ void GLCanvas3D::render() m_rectangle_selection.render(*this); // draw overlays - _render_gizmos_overlay(); - _render_warning_texture(); - _render_legend_texture(); -#if !ENABLE_SVG_ICONS - _resize_toolbars(); -#endif // !ENABLE_SVG_ICONS - _render_toolbar(); - _render_view_toolbar(); - if ((m_layers_editing.last_object_id >= 0) && (m_layers_editing.object_max_z() > 0.0f)) - m_layers_editing.render_overlay(*this); + _render_overlays(); #if ENABLE_RENDER_STATISTICS ImGuiWrapper& imgui = *wxGetApp().imgui(); @@ -3285,7 +3250,7 @@ void GLCanvas3D::set_camera_zoom(float zoom) zoom = m_camera.zoom / (1.0f - zoom); // Don't allow to zoom too far outside the scene. - float zoom_min = _get_zoom_to_bounding_box_factor(_max_bounding_box()); + float zoom_min = _get_zoom_to_bounding_box_factor(_max_bounding_box(false)); if (zoom_min > 0.0f) zoom = std::max(zoom, zoom_min * 0.7f); @@ -3375,7 +3340,7 @@ Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const { - return factor * m_bed.get_bounding_box().max_size(); + return factor * m_bed.get_bounding_box(false).max_size(); } void GLCanvas3D::set_cursor(ECursorType type) @@ -3613,10 +3578,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) m_dirty = false; } -BoundingBoxf3 GLCanvas3D::_max_bounding_box() const +BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_bed_model) const { BoundingBoxf3 bb = volumes_bounding_box(); - bb.merge(m_bed.get_bounding_box()); + bb.merge(m_bed.get_bounding_box(include_bed_model)); return bb; } @@ -3907,7 +3872,7 @@ void GLCanvas3D::_render_objects() const if (m_config != nullptr) { - const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(); + const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(false); m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); m_volumes.check_outside_state(m_config, nullptr); } @@ -3989,6 +3954,29 @@ void GLCanvas3D::_render_selection_center() const } #endif // ENABLE_RENDER_SELECTION_CENTER +void GLCanvas3D::_render_overlays() const +{ + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glPushMatrix()); + glsafe(::glLoadIdentity()); + // ensure the textures are renderered inside the frustrum + glsafe(::glTranslated(0.0, 0.0, -(m_camera.get_near_z() + 0.5))); + + _render_gizmos_overlay(); + _render_warning_texture(); + _render_legend_texture(); +#if !ENABLE_SVG_ICONS + _resize_toolbars(); +#endif // !ENABLE_SVG_ICONS + _render_toolbar(); + _render_view_toolbar(); + + if ((m_layers_editing.last_object_id >= 0) && (m_layers_editing.object_max_z() > 0.0f)) + m_layers_editing.render_overlay(*this); + + glsafe(::glPopMatrix()); +} + void GLCanvas3D::_render_warning_texture() const { m_warning_texture.render(*this); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 51a36cf69f..a0174dd7f4 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -635,7 +635,7 @@ private: bool _set_current(); void _resize(unsigned int w, unsigned int h); - BoundingBoxf3 _max_bounding_box() const; + BoundingBoxf3 _max_bounding_box(bool include_bed_model) const; void _zoom_to_bounding_box(const BoundingBoxf3& bbox); float _get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const; @@ -652,6 +652,7 @@ private: #if ENABLE_RENDER_SELECTION_CENTER void _render_selection_center() const; #endif // ENABLE_RENDER_SELECTION_CENTER + void _render_overlays() const; void _render_warning_texture() const; void _render_legend_texture() const; void _render_volumes_for_picking() const; diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index e00b3a3bea..0c5c0efbe1 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -62,6 +62,7 @@ namespace GUI { protected: unsigned int generate_mipmaps(wxImage& image); + private: bool load_from_png(const std::string& filename, bool use_mipmaps); bool load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 00cbdfec71..e73533d37f 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -390,19 +390,12 @@ void GLToolbar::render(const GLCanvas3D& parent) const generate_icons_texture(); #endif // ENABLE_SVG_ICONS - glsafe(::glDisable(GL_DEPTH_TEST)); - - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - switch (m_layout.type) { default: case Layout::Horizontal: { render_horizontal(parent); break; } case Layout::Vertical: { render_vertical(parent); break; } } - - glsafe(::glPopMatrix()); } bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 1006d2bd1e..32809c01d3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -531,18 +531,9 @@ void GLGizmosManager::render_overlay(const GLCanvas3D& canvas, const Selection& generate_icons_texture(); #endif // ENABLE_SVG_ICONS - glsafe(::glDisable(GL_DEPTH_TEST)); - - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - do_render_overlay(canvas, selection); - - glsafe(::glPopMatrix()); } - - bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas) { bool processed = false; From ed4b71eb1535c5773fb789d41c874b13875ee3f2 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 14 Jun 2019 10:55:56 +0200 Subject: [PATCH 109/627] Layers editing overlay rendering modified to use a texture drawn on a quad lying in a plane parallel to the camera viewport --- resources/shaders/variable_layer_height.vs | 9 ++++++++- src/slic3r/GUI/GLCanvas3D.cpp | 22 ++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/resources/shaders/variable_layer_height.vs b/resources/shaders/variable_layer_height.vs index 9763859d04..4f98dfa56e 100644 --- a/resources/shaders/variable_layer_height.vs +++ b/resources/shaders/variable_layer_height.vs @@ -15,6 +15,7 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); #define INTENSITY_AMBIENT 0.3 uniform mat4 volume_world_matrix; +uniform float object_max_z; // x = tainted, y = specular; varying vec2 intensity; @@ -42,6 +43,12 @@ void main() intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; // Scaled to widths of the Z texture. - object_z = (volume_world_matrix * gl_Vertex).z; + if (object_max_z > 0.0) + // when rendering the overlay + object_z = object_max_z * gl_MultiTexCoord0.y; + else + // when rendering the volumes + object_z = (volume_world_matrix * gl_Vertex).z; + gl_Position = ftransform(); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index dbfde926c9..21f1d23cdc 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -63,11 +63,6 @@ static const float GROUND_Z = -0.02f; static const float GIZMO_RESET_BUTTON_HEIGHT = 22.0f; static const float GIZMO_RESET_BUTTON_WIDTH = 70.f; -static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; - static const float DEFAULT_BG_DARK_COLOR[3] = { 0.478f, 0.478f, 0.478f }; static const float DEFAULT_BG_LIGHT_COLOR[3] = { 0.753f, 0.753f, 0.753f }; static const float ERROR_BG_DARK_COLOR[3] = { 0.478f, 0.192f, 0.039f }; @@ -452,8 +447,7 @@ void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas m_shader.set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height); m_shader.set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas)); m_shader.set_uniform("z_cursor_band_width", band_width); - // The shader requires the original model coordinates when rendering to the texture, so we pass it the unit matrix - m_shader.set_uniform("volume_world_matrix", UNIT_MATRIX); + m_shader.set_uniform("object_max_z", m_object_max_z); glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); @@ -466,10 +460,10 @@ void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas ::glBegin(GL_QUADS); ::glNormal3f(0.0f, 0.0f, 1.0f); - ::glVertex3f(l, b, 0.0f); - ::glVertex3f(r, b, 0.0f); - ::glVertex3f(r, t, m_object_max_z); - ::glVertex3f(l, t, m_object_max_z); + ::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(l, b); + ::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(r, b); + ::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(r, t); + ::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(l, t); glsafe(::glEnd()); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); @@ -522,6 +516,7 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G GLint z_cursor_id = ::glGetUniformLocation(shader_id, "z_cursor"); GLint z_cursor_band_width_id = ::glGetUniformLocation(shader_id, "z_cursor_band_width"); GLint world_matrix_id = ::glGetUniformLocation(shader_id, "volume_world_matrix"); + GLint object_max_z_id = ::glGetUniformLocation(shader_id, "object_max_z"); glcheck(); if (z_to_texture_row_id != -1 && z_texture_row_to_normalized_id != -1 && z_cursor_id != -1 && z_cursor_band_width_id != -1 && world_matrix_id != -1) @@ -548,7 +543,10 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G // Render the object using the layer editing shader and texture. if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) continue; - glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast().data())); + if (world_matrix_id != -1) + glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast().data())); + if (object_max_z_id != -1) + glsafe(::glUniform1f(object_max_z_id, GLfloat(0))); glvolume->render(); } // Revert back to the previous shader. From 77954a13b9c34c74a46ac652ef58118b3b9c6977 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 14 Jun 2019 11:07:07 +0200 Subject: [PATCH 110/627] Fix of admesh import due to boost::pool::destroy taking O(n). Why on earth?! --- src/admesh/connect.cpp | 16 +++++----------- src/admesh/normals.cpp | 8 +++++--- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index 5afb85d2c0..e729c89229 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -130,18 +130,12 @@ struct HashTableEdges { this->heads[i] = this->tail; } ~HashTableEdges() { - for (int i = 0; i < this->M; ++ i) { - for (HashEdge *temp = this->heads[i]; this->heads[i] != this->tail; temp = this->heads[i]) { - this->heads[i] = this->heads[i]->next; - pool.destroy(temp); #ifndef NDEBUG + for (int i = 0; i < this->M; ++ i) + for (HashEdge *temp = this->heads[i]; this->heads[i] != this->tail; temp = this->heads[i]) ++ this->freed; -#endif /* NDEBUG */ - } - } - this->heads.clear(); - pool.destroy(this->tail); this->tail = nullptr; +#endif /* NDEBUG */ } void insert_edge_exact(stl_file *stl, const HashEdge &edge) @@ -197,7 +191,7 @@ private: match_neighbors(edge, *link); // Delete the matched edge from the list. this->heads[chain_number] = link->next; - pool.destroy(link); + // pool.destroy(link); #ifndef NDEBUG ++ this->freed; #endif /* NDEBUG */ @@ -224,7 +218,7 @@ private: // Delete the matched edge from the list. HashEdge *temp = link->next; link->next = link->next->next; - pool.destroy(temp); + // pool.destroy(temp); #ifndef NDEBUG ++ this->freed; #endif /* NDEBUG */ diff --git a/src/admesh/normals.cpp b/src/admesh/normals.cpp index 4d47573f6c..16bb3daab5 100644 --- a/src/admesh/normals.cpp +++ b/src/admesh/normals.cpp @@ -25,6 +25,8 @@ #include #include +// Boost pool: Don't use mutexes to synchronize memory allocation. +#define BOOST_POOL_NO_MT #include #include "stl.h" @@ -192,7 +194,7 @@ void stl_fix_normal_directions(stl_file *stl) } stl_normal *temp = head->next; // Delete this facet from the list. head->next = head->next->next; - pool.destroy(temp); + // pool.destroy(temp); } else { // If we ran out of facets to fix: All of the facets in this part have been fixed. ++ stl->stats.number_of_parts; if (checked >= stl->stats.number_of_facets) @@ -214,8 +216,8 @@ void stl_fix_normal_directions(stl_file *stl) } } - pool.destroy(head); - pool.destroy(tail); + // pool.destroy(head); + // pool.destroy(tail); } void stl_fix_normal_values(stl_file *stl) From 6f7051c3b10fde8062a4ac8844e49efbe910aa24 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 10 Jun 2019 11:49:15 +0200 Subject: [PATCH 111/627] GCode preview shows correct volumetric flow for the wipe tower The neccessary annotations for the GCodeAnalyzer were missing --- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c42669de0d..436628d8ac 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2554,7 +2554,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, gcode += buf; } - if (m_last_mm3_per_mm != path.mm3_per_mm) + if (last_was_wipe_tower || (m_last_mm3_per_mm != path.mm3_per_mm)) { m_last_mm3_per_mm = path.mm3_per_mm; diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index edfe475b56..ffc7376079 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -68,6 +68,16 @@ public: return *this; } + Writer& change_analyzer_mm3_per_mm(float len, float e) { + static const float area = M_PI * 1.75f * 1.75f / 4.f; + float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); + // adds tag for analyzer: + char buf[64]; + sprintf(buf, ";%s%f\n", GCodeAnalyzer::Mm3_Per_Mm_Tag.c_str(), mm3_per_mm); + m_gcode += buf; + return *this; + } + Writer& set_initial_position(const WipeTower::xy &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { m_wipe_tower_width = width; m_wipe_tower_depth = depth; @@ -127,12 +137,12 @@ public: if (record_length) m_used_filament_length += e; - // Now do the "internal rotation" with respect to the wipe tower center WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go if (! m_preview_suppressed && e > 0.f && len > 0.) { + change_analyzer_mm3_per_mm(len, e); // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. float width = float(double(e) * /*Filament_Area*/2.40528 / (len * m_layer_height)); @@ -155,7 +165,7 @@ public: m_gcode += set_format_E(e); if (f != 0.f && f != m_current_feedrate) - m_gcode += set_format_F(f); + m_gcode += set_format_F(f); m_current_pos.x = x; m_current_pos.y = y; From 079e63e19082f75e33fa73c099c7901395779d40 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 10 Jun 2019 12:26:47 +0200 Subject: [PATCH 112/627] The wipe tower now respects filament max volumetric flow The odd commands that lowered the speed override values for PVA, FLEX etc. were removed Now the wipe tower backups user speed override, sets it to 100%, does what is needed and restores the old value when finished. There are no special cases - lowering the speed for certain materials can be achieved by lowering the volumetric flow. --- src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 55 ++++++++++-------------- src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 47 +++++++++----------- src/libslic3r/Print.cpp | 3 +- 3 files changed, 46 insertions(+), 59 deletions(-) diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index ffc7376079..5165ac358c 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -40,7 +40,7 @@ namespace PrusaMultiMaterial { class Writer { public: - Writer(float layer_height, float line_width, GCodeFlavor flavor) : + Writer(float layer_height, float line_width, GCodeFlavor flavor, const std::vector& filament_parameters) : m_current_pos(std::numeric_limits::max(), std::numeric_limits::max()), m_current_z(0.f), m_current_feedrate(0.f), @@ -49,7 +49,8 @@ public: m_preview_suppressed(false), m_elapsed_time(0.f), m_default_analyzer_line_width(line_width), - m_gcode_flavor(flavor) + m_gcode_flavor(flavor), + m_filpar(filament_parameters) { // adds tag for analyzer: char buf[64]; @@ -125,7 +126,7 @@ public: float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; } // Extrude with an explicitely provided amount of extrusion. - Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false) + Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) { if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate)) // Neither extrusion nor a travel move. @@ -164,8 +165,13 @@ public: if (e != 0.f) m_gcode += set_format_E(e); - if (f != 0.f && f != m_current_feedrate) - m_gcode += set_format_F(f); + if (f != 0.f && f != m_current_feedrate) { + if (limit_volumetric_flow) { + float e_speed = e / (((len == 0) ? std::abs(e) : len) / f * 60.f); + f /= std::max(1.f, e_speed / m_filpar[m_current_tool].max_e_speed); + } + m_gcode += set_format_F(f); + } m_current_pos.x = x; m_current_pos.y = y; @@ -176,7 +182,7 @@ public: return *this; } - Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false) + Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) { return extrude_explicit(dest.x, dest.y, e, f, record_length); } // Travel to a new XY position. f=0 means use the current value. @@ -273,8 +279,8 @@ public: // extrude quickly amount e to x2 with feed f. Writer& ram(float x1, float x2, float dy, float e0, float e, float f) { - extrude_explicit(x1, m_current_pos.y + dy, e0, f, true); - extrude_explicit(x2, m_current_pos.y, e, 0.f, true); + extrude_explicit(x1, m_current_pos.y + dy, e0, f, true, false); + extrude_explicit(x2, m_current_pos.y, e, 0.f, true, false); return *this; } @@ -422,6 +428,7 @@ private: const float m_default_analyzer_line_width; float m_used_filament_length = 0.f; GCodeFlavor m_gcode_flavor; + const std::vector& m_filpar; std::string set_format_X(float x) { @@ -528,15 +535,14 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( const float prime_section_width = std::min(240.f / tools.size(), 60.f); box_coordinates cleaning_box(xy(5.f, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor); + PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) .append(";--------------------\n" "; CP PRIMING START\n") .append(";--------------------\n"); - if (m_retain_speed_override) - writer.speed_override_backup(); + writer.speed_override_backup(); writer.speed_override(100); writer.set_initial_position(xy(0.f, 0.f)) // Always move to the starting position @@ -571,8 +577,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // Reset the extruder current to a normal value. if (m_set_extruder_trimpot) writer.set_extruder_trimpot(550); - if (m_retain_speed_override) - writer.speed_override_restore(); + writer.speed_override_restore(); writer.feedrate(6000) .flush_planner_queue() .reset_extruder() @@ -630,7 +635,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo (tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width : m_wipe_tower_depth-m_perimeter_width)); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor); + PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) @@ -640,8 +645,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo .comment_with_value(" toolchange #", m_num_tool_changes + 1) // the number is zero-based .comment_material(m_filpar[m_current_tool].material) .append(";--------------------\n"); - if (m_retain_speed_override) - writer.speed_override_backup(); + writer.speed_override_backup(); writer.speed_override(100); xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed); @@ -679,8 +683,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo if (m_set_extruder_trimpot) writer.set_extruder_trimpot(550); // Reset the extruder current to a normal value. - if (m_retain_speed_override) - writer.speed_override_restore(); + writer.speed_override_restore(); writer.feedrate(6000) .flush_planner_queue() .reset_extruder() @@ -711,7 +714,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo m_wipe_tower_width, m_wipe_tower_depth); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor); + PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow * 1.1f) .set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop. .set_initial_tool(m_current_tool) @@ -917,19 +920,7 @@ void WipeTowerPrusaMM::toolchange_Change( if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - // Speed override for the material. Go slow for flex and soluble materials. - int speed_override; - switch (new_material) { - case PVA: speed_override = (m_z_pos < 0.80f) ? 60 : 80; break; - case SCAFF: speed_override = 35; break; - case FLEX: speed_override = 35; break; - default: speed_override = 100; - } writer.set_tool(new_tool); - if (m_retain_speed_override) - assert(speed_override == 100); - else - writer.speed_override(speed_override); writer.flush_planner_queue(); m_current_tool = new_tool; } @@ -1040,7 +1031,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() // Otherwise the caller would likely travel to the wipe tower in vain. assert(! this->layer_finished()); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor); + PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index f8adf4c5f7..512733a204 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -73,17 +73,12 @@ public: // Set the extruder properties. void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start, float unloading_speed, float unloading_speed_start, float delay, int cooling_moves, - float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float nozzle_diameter) + float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float max_volumetric_speed, float nozzle_diameter) { //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector m_filpar.push_back(FilamentParameters()); m_filpar[idx].material = material; - if (material == FLEX || material == SCAFF || material == PVA) { - // MMU2 lowers the print speed using the speed override (M220) for printing of soluble PVA/BVOH and flex materials. - // Therefore it does not make sense to use the new M220 B and M220 R (backup / restore). - m_retain_speed_override = false; - } m_filpar[idx].temperature = temp; m_filpar[idx].first_layer_temperature = first_layer_temp; m_filpar[idx].loading_speed = loading_speed; @@ -94,6 +89,8 @@ public: m_filpar[idx].cooling_moves = cooling_moves; m_filpar[idx].cooling_initial_speed = cooling_initial_speed; m_filpar[idx].cooling_final_speed = cooling_final_speed; + if (max_volumetric_speed != 0.f) + m_filpar[idx].max_e_speed = (max_volumetric_speed / Filament_Area); m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter @@ -188,6 +185,24 @@ public: virtual std::vector get_used_filament() const override { return m_used_filament_length; } virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; } + struct FilamentParameters { + material_type material = PLA; + int temperature = 0; + int first_layer_temperature = 0; + float loading_speed = 0.f; + float loading_speed_start = 0.f; + float unloading_speed = 0.f; + float unloading_speed_start = 0.f; + float delay = 0.f ; + int cooling_moves = 0; + float cooling_initial_speed = 0.f; + float cooling_final_speed = 0.f; + float ramming_line_width_multiplicator = 0.f; + float ramming_step_multiplicator = 0.f; + float max_e_speed = std::numeric_limits::max(); + std::vector ramming_speed; + float nozzle_diameter; + }; private: WipeTowerPrusaMM(); @@ -224,32 +239,12 @@ private: float m_extra_loading_move = 0.f; float m_bridging = 0.f; bool m_set_extruder_trimpot = false; - bool m_retain_speed_override = true; bool m_adhesion = true; GCodeFlavor m_gcode_flavor; float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. - - struct FilamentParameters { - material_type material = PLA; - int temperature = 0; - int first_layer_temperature = 0; - float loading_speed = 0.f; - float loading_speed_start = 0.f; - float unloading_speed = 0.f; - float unloading_speed_start = 0.f; - float delay = 0.f ; - int cooling_moves = 0; - float cooling_initial_speed = 0.f; - float cooling_final_speed = 0.f; - float ramming_line_width_multiplicator = 0.f; - float ramming_step_multiplicator = 0.f; - std::vector ramming_speed; - float nozzle_diameter; - }; - // Extruder specific parameters. std::vector m_filpar; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index f9129f15a1..02e130573c 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -121,7 +121,6 @@ bool Print::invalidate_state_by_config_options(const std::vector( From da1aea889f3cb0390b7a752b42065f6021829f84 Mon Sep 17 00:00:00 2001 From: Thomas Moore Date: Fri, 3 May 2019 00:17:24 -0400 Subject: [PATCH 113/627] Enable wipe tower for all multi-extruder configurations --- src/libslic3r/GCode.cpp | 83 ++++++++++++++++-------- src/libslic3r/GCode.hpp | 4 +- src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 76 ++++++++++++---------- src/libslic3r/Print.cpp | 81 ++++++++++++++++------- src/slic3r/GUI/GLCanvas3D.cpp | 5 +- src/slic3r/GUI/Tab.cpp | 7 +- 6 files changed, 166 insertions(+), 90 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 436628d8ac..d6355e4d2e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -167,7 +167,7 @@ static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Wipe return Point(scale_(wipe_tower_pt.x - gcodegen.origin()(0)), scale_(wipe_tower_pt.y - gcodegen.origin()(1))); } -std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const +std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, float print_z) const { std::string gcode; @@ -195,24 +195,57 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T "Travel to a Wipe Tower"); gcode += gcodegen.unretract(); - // Let the tool change be executed by the wipe tower class. - // Inform the G-code writer about the changes done behind its back. - gcode += tcr_rotated_gcode; - // Let the m_writer know the current extruder_id, but ignore the generated G-code. - if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) - gcodegen.writer().toolchange(new_extruder_id); - gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); - - // Always append the filament start G-code even if the extruder did not switch, - // because the wipe tower resets the linear advance and we want it to be re-enabled. + // Process the end filament gcode. + std::string end_filament_gcode_str; + if (gcodegen.writer().extruder() != nullptr) { + // Process the custom end_filament_gcode in case of single_extruder_multi_material. + unsigned int old_extruder_id = gcodegen.writer().extruder()->id(); + const std::string &end_filament_gcode = gcodegen.config().end_filament_gcode.get_at(old_extruder_id); + if (gcodegen.writer().extruder() != nullptr && ! end_filament_gcode.empty()) { + end_filament_gcode_str = gcodegen.placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); + check_add_eol(end_filament_gcode_str); + } + } + + // Process the tool chagne gcode. + std::string toolchange_gcode_str; + const std::string &toolchange_gcode = gcodegen.config().toolchange_gcode.value; + if (gcodegen.writer().extruder() != nullptr && ! toolchange_gcode.empty()) { + // Process the custom toolchange_gcode. + DynamicConfig config; + config.set_key_value("previous_extruder", new ConfigOptionInt((int)gcodegen.writer().extruder()->id())); + config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); + check_add_eol(toolchange_gcode_str); + } + + // Process the start filament gcode. + std::string start_filament_gcode_str; const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id); if (! start_filament_gcode.empty()) { // Process the start_filament_gcode for the active filament only. DynamicConfig config; config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_extruder_id)); - gcode += gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); - check_add_eol(gcode); + start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); + check_add_eol(start_filament_gcode_str); } + + // Insert the end filament, toolchange, and start filament gcode. + DynamicConfig config; + config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str)); + config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str)); + config.set_key_value("start_filament_gcode", new ConfigOptionString(start_filament_gcode_str)); + std::string tcr_gcode, tcr_escaped_gcode = gcodegen.placeholder_parser_process("tcr_rotated_gcode", tcr_rotated_gcode, new_extruder_id, &config); + unescape_string_cstyle(tcr_escaped_gcode, tcr_gcode); + gcode += tcr_gcode; + check_add_eol(toolchange_gcode_str); + + // Let the m_writer know the current extruder_id, but ignore the generated G-code. + if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) + gcodegen.writer().toolchange(new_extruder_id); + // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(Vec2d(end_pos.x, end_pos.y)); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); @@ -313,14 +346,14 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) return gcode; } -std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) +std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer, float print_z) { std::string gcode; assert(m_layer_idx >= 0 && m_layer_idx <= m_tool_changes.size()); if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { if (m_layer_idx < m_tool_changes.size()) { assert(m_tool_change_idx < m_tool_changes[m_layer_idx].size()); - gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id); + gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, print_z); } m_brim_done = true; } @@ -333,7 +366,7 @@ std::string WipeTowerIntegration::finalize(GCode &gcodegen) std::string gcode; if (std::abs(gcodegen.writer().get_position()(2) - m_final_purge.print_z) > EPSILON) gcode += gcodegen.change_layer(m_final_purge.print_z); - gcode += append_tcr(gcodegen, m_final_purge, -1); + gcode += append_tcr(gcodegen, m_final_purge, -1, m_final_purge.print_z); return gcode; } @@ -805,15 +838,13 @@ void GCode::_do_export(Print &print, FILE *file) // Write the custom start G-code _writeln(file, start_gcode); // Process filament-specific gcode in extruder order. - if (print.config().single_extruder_multi_material) { - if (has_wipe_tower) { - // Wipe tower will control the extruder switching, it will call the start_filament_gcode. - } else { - // Only initialize the initial extruder. - DynamicConfig config; - config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); - _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); - } + if (has_wipe_tower) { + // Wipe tower will control the extruder switching, it will call the start_filament_gcode. + } else if (print.config().single_extruder_multi_material) { + // Only initialize the initial extruder. + DynamicConfig config; + config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); + _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); } else { DynamicConfig config; for (const std::string &start_gcode : print.config().start_filament_gcode.values) { @@ -1598,7 +1629,7 @@ void GCode::process_layer( for (unsigned int extruder_id : layer_tools.extruders) { gcode += (layer_tools.has_wipe_tower && m_wipe_tower) ? - m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) : + m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back(), print_z) : this->set_extruder(extruder_id, print_z); // let analyzer tag generator aware of a role type change diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 21957d32c0..94f5a76aa1 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -99,13 +99,13 @@ public: std::string prime(GCode &gcodegen); void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } - std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer); + std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer, float print_z); std::string finalize(GCode &gcodegen); std::vector used_filament_length() const; private: WipeTowerIntegration& operator=(const WipeTowerIntegration&); - std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const; + std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, float print_z) const; // Postprocesses gcode: rotates and moves all G1 extrusions and returns result std::string rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const; diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 5165ac358c..dfcecc46b7 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -857,19 +857,21 @@ void WipeTowerPrusaMM::toolchange_Unload( // Retraction: float old_x = writer.x(); float turning_point = (!m_left_to_right ? xl : xr ); - float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming - writer.suppress_preview() - .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s - .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f) - .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f) - .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) - - /*.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed - .load_move_x_advanced(old_x, -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed) - .load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed) - .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) - .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ - .resume_preview(); + if ((m_cooling_tube_retraction != 0 || m_cooling_tube_length != 0) && m_filpar[m_current_tool].unloading_speed_start != 0 && m_filpar[m_current_tool].unloading_speed != 0) { + float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming + writer.suppress_preview() + .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s + .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f) + .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f) + .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) + + /*.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed + .load_move_x_advanced(old_x, -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed) + .load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed) + .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) + .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ + .resume_preview(); + } if (new_temperature != 0 && (new_temperature != m_old_temperature || m_is_first_layer) ) { // Set the extruder temperature, but don't wait. // If the required temperature is the same as last time, don't emit the M104 again (if user adjusted the value, it would be reset) // However, always change temperatures on the first layer (this is to avoid issues with priming lines turned off). @@ -920,7 +922,11 @@ void WipeTowerPrusaMM::toolchange_Change( if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + writer.append("[end_filament_gcode]\n"); + writer.append("[toolchange_gcode]\n"); writer.set_tool(new_tool); + writer.append("[start_filament_gcode]\n"); + writer.flush_planner_queue(); m_current_tool = new_tool; } @@ -928,32 +934,34 @@ void WipeTowerPrusaMM::toolchange_Change( void WipeTowerPrusaMM::toolchange_Load( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box) -{ - float xl = cleaning_box.ld.x + m_perimeter_width * 0.75f; - float xr = cleaning_box.rd.x - m_perimeter_width * 0.75f; - float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position +{ + if ((m_parking_pos_retraction != 0 || m_extra_loading_move != 0) && m_filpar[m_current_tool].loading_speed_start != 0 && m_filpar[m_current_tool].loading_speed != 0) { + float xl = cleaning_box.ld.x + m_perimeter_width * 0.75f; + float xr = cleaning_box.rd.x - m_perimeter_width * 0.75f; + float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position - // Load the filament while moving left / right, so the excess material will not create a blob at a single position. - float turning_point = ( oldx-xl < xr-oldx ? xr : xl ); - float edist = m_parking_pos_retraction+m_extra_loading_move; + // Load the filament while moving left / right, so the excess material will not create a blob at a single position. + float turning_point = ( oldx-xl < xr-oldx ? xr : xl ); + float edist = m_parking_pos_retraction+m_extra_loading_move; - writer.append("; CP TOOLCHANGE LOAD\n") - .suppress_preview() - /*.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration - .load_move_x_advanced(oldx, 0.5f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase - .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Slowing down - .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ + writer.append("; CP TOOLCHANGE LOAD\n") + .suppress_preview() + /*.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration + .load_move_x_advanced(oldx, 0.5f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase + .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Slowing down + .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ - .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start) - .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase - .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ + .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start) + .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase + .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ - .travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate - .resume_preview(); + .travel(oldx, writer.y()) // in case last move was shortened to limit x feedrate + .resume_preview(); - // Reset the extruder current to the normal value. - if (m_set_extruder_trimpot) - writer.set_extruder_trimpot(550); + // Reset the extruder current to the normal value. + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); + } } // Wipe the newly loaded filament until the end of the assigned wipe area. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 02e130573c..d09fd2bfe1 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1211,17 +1211,15 @@ std::string Print::validate() const return L("The Spiral Vase option can only be used when printing single material objects."); } - if (m_config.single_extruder_multi_material) { - for (size_t i=1; ihas_wipe_tower() && ! m_objects.empty()) { if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin) return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors."); if (! m_config.use_relative_e_distances) return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); + + for (size_t i=1; i 1) { bool has_custom_layering = false; @@ -1730,7 +1728,6 @@ void Print::_make_brim() bool Print::has_wipe_tower() const { return - m_config.single_extruder_multi_material.value && ! m_config.spiral_vase.value && m_config.wipe_tower.value && m_config.nozzle_diameter.values.size() > 1; @@ -1792,38 +1789,78 @@ void Print::_make_wipe_tower() } } this->throw_if_canceled(); + + bool semm = m_config.single_extruder_multi_material.value; + float cooling_tube_retraction = 0.0; + float cooling_tube_length = 0.0; + float parking_pos_retraction = 0.0; + bool extra_loading_move = 0.0; + bool high_current_on_filament_swap = false; + + if (semm) { + cooling_tube_retraction = float(m_config.cooling_tube_retraction.value); + cooling_tube_length = float(m_config.cooling_tube_length.value); + parking_pos_retraction = float(m_config.parking_pos_retraction.value); + extra_loading_move = float(m_config.extra_loading_move.value); + high_current_on_filament_swap = m_config.high_current_on_filament_swap.value; + } // Initialize the wipe tower. WipeTowerPrusaMM wipe_tower( float(m_config.wipe_tower_x.value), float(m_config.wipe_tower_y.value), float(m_config.wipe_tower_width.value), - float(m_config.wipe_tower_rotation_angle.value), float(m_config.cooling_tube_retraction.value), - float(m_config.cooling_tube_length.value), float(m_config.parking_pos_retraction.value), - float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), - m_config.high_current_on_filament_swap.value, m_config.gcode_flavor, wipe_volumes, + float(m_config.wipe_tower_rotation_angle.value), cooling_tube_retraction, + cooling_tube_length, parking_pos_retraction, + extra_loading_move, float(m_config.wipe_tower_bridging), + high_current_on_filament_swap, m_config.gcode_flavor, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder()); //wipe_tower.set_retract(); //wipe_tower.set_zhop(); // Set the extruder & material properties at the wipe tower object. - for (size_t i = 0; i < number_of_extruders; ++ i) + for (size_t i = 0; i < number_of_extruders; ++ i) { + float loading_speed = 0.0; + float loading_speed_start = 0.0; + float unloading_speed = 0.0; + float unloading_speed_start = 0.0; + float toolchange_delay = 0.0; + int cooling_moves = 0; + float cooling_initial_speed = 0.0; + float cooling_final_speed = 0.0; + float max_volumetric_speed = 0.f; + std::string ramming_parameters; + + if (semm) { + loading_speed = m_config.filament_loading_speed.get_at(i); + loading_speed_start = m_config.filament_loading_speed_start.get_at(i); + unloading_speed = m_config.filament_unloading_speed.get_at(i); + unloading_speed_start = m_config.filament_unloading_speed_start.get_at(i); + toolchange_delay = m_config.filament_toolchange_delay.get_at(i); + cooling_moves = m_config.filament_cooling_moves.get_at(i); + cooling_initial_speed = m_config.filament_cooling_initial_speed.get_at(i); + cooling_final_speed = m_config.filament_cooling_final_speed.get_at(i); + ramming_parameters = m_config.filament_ramming_parameters.get_at(i); + max_volumetric_speed = m_config.filament_max_volumetric_speed.get_at(i); + } + wipe_tower.set_extruder( i, WipeTowerPrusaMM::parse_material(m_config.filament_type.get_at(i).c_str()), m_config.temperature.get_at(i), m_config.first_layer_temperature.get_at(i), - m_config.filament_loading_speed.get_at(i), - m_config.filament_loading_speed_start.get_at(i), - m_config.filament_unloading_speed.get_at(i), - m_config.filament_unloading_speed_start.get_at(i), - m_config.filament_toolchange_delay.get_at(i), - m_config.filament_cooling_moves.get_at(i), - m_config.filament_cooling_initial_speed.get_at(i), - m_config.filament_cooling_final_speed.get_at(i), - m_config.filament_ramming_parameters.get_at(i), - m_config.filament_max_volumetric_speed.get_at(i), + loading_speed, + loading_speed_start, + unloading_speed, + unloading_speed_start, + toolchange_delay, + cooling_moves, + cooling_initial_speed, + cooling_final_speed, + ramming_parameters, + max_volumetric_speed, m_config.nozzle_diameter.get_at(i)); + } m_wipe_tower_data.priming = Slic3r::make_unique( wipe_tower.prime(this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 21f1d23cdc..216848477c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2049,11 +2049,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // Should the wipe tower be visualized ? unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); - bool semm = dynamic_cast(m_config->option("single_extruder_multi_material"))->value; bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; bool co = dynamic_cast(m_config->option("complete_objects"))->value; - if ((extruders_count > 1) && semm && wt && !co) + if ((extruders_count > 1) && wt && !co) { // Height of a print (Show at least a slab) double height = std::max(m_model->bounding_box().max(2), 10.0); @@ -5466,7 +5465,7 @@ void GLCanvas3D::_load_fff_shells() double max_z = print->objects()[0]->model_object()->get_model()->bounding_box().max(2); const PrintConfig& config = print->config(); unsigned int extruders_count = config.nozzle_diameter.size(); - if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { + if ((extruders_count > 1) && config.wipe_tower && !config.complete_objects) { float depth = print->get_wipe_tower_depth(); // Calculate wipe tower brim spacing. diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6cd32d3977..078293d676 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -874,11 +874,10 @@ void Tab::update_wiping_button_visibility() { return; // ys_FIXME bool wipe_tower_enabled = dynamic_cast( (m_preset_bundle->prints.get_edited_preset().config ).option("wipe_tower"))->value; bool multiple_extruders = dynamic_cast((m_preset_bundle->printers.get_edited_preset().config).option("nozzle_diameter"))->values.size() > 1; - bool single_extruder_mm = dynamic_cast( (m_preset_bundle->printers.get_edited_preset().config).option("single_extruder_multi_material"))->value; auto wiping_dialog_button = wxGetApp().sidebar().get_wiping_dialog_button(); if (wiping_dialog_button) { - wiping_dialog_button->Show(wipe_tower_enabled && multiple_extruders && single_extruder_mm); + wiping_dialog_button->Show(wipe_tower_enabled && multiple_extruders); wiping_dialog_button->GetParent()->Layout(); } } @@ -1557,6 +1556,9 @@ void TabFilament::build() }; optgroup->append_line(line); + optgroup = page->new_optgroup(_(L("Wipe tower parameters"))); + optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); + optgroup = page->new_optgroup(_(L("Toolchange parameters with single extruder MM printers"))); optgroup->append_single_option_line("filament_loading_speed_start"); optgroup->append_single_option_line("filament_loading_speed"); @@ -1568,7 +1570,6 @@ void TabFilament::build() optgroup->append_single_option_line("filament_cooling_moves"); optgroup->append_single_option_line("filament_cooling_initial_speed"); optgroup->append_single_option_line("filament_cooling_final_speed"); - optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); line = optgroup->create_single_option_line("filament_ramming_parameters");// { _(L("Ramming")), "" }; line.widget = [this](wxWindow* parent) { From 9df93c012506763b7348b60fa14d9f3883018347 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 12 Jun 2019 10:54:52 +0200 Subject: [PATCH 114/627] Mostly refactoring of the wipe tower improvements - setting of the wipe tower parameters based od whether SE MM printer is selected is done in the WipeTowerPrusaMM constructor, so it does not distract in Print.cpp - WipeTowerPrusaMM.cpp conditions checking for SE MM printer are now using a more descriptive const member variable, not the loading/unloading speeds (hopefully the functionality is the same) --- src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 4 +- src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 51 ++++++++++------- src/libslic3r/Print.cpp | 72 ++++++------------------ 3 files changed, 51 insertions(+), 76 deletions(-) diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index dfcecc46b7..2a74d8248e 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -857,7 +857,7 @@ void WipeTowerPrusaMM::toolchange_Unload( // Retraction: float old_x = writer.x(); float turning_point = (!m_left_to_right ? xl : xr ); - if ((m_cooling_tube_retraction != 0 || m_cooling_tube_length != 0) && m_filpar[m_current_tool].unloading_speed_start != 0 && m_filpar[m_current_tool].unloading_speed != 0) { + if (m_semm && (m_cooling_tube_retraction != 0 || m_cooling_tube_length != 0)) { float total_retraction_distance = m_cooling_tube_retraction + m_cooling_tube_length/2.f - 15.f; // the 15mm is reserved for the first part after ramming writer.suppress_preview() .retract(15.f, m_filpar[m_current_tool].unloading_speed_start * 60.f) // feedrate 5000mm/min = 83mm/s @@ -935,7 +935,7 @@ void WipeTowerPrusaMM::toolchange_Load( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box) { - if ((m_parking_pos_retraction != 0 || m_extra_loading_move != 0) && m_filpar[m_current_tool].loading_speed_start != 0 && m_filpar[m_current_tool].loading_speed != 0) { + if (m_semm && (m_parking_pos_retraction != 0 || m_extra_loading_move != 0)) { float xl = cleaning_box.ld.x + m_perimeter_width * 0.75f; float xr = cleaning_box.rd.x - m_perimeter_width * 0.75f; float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index 512733a204..8e1e494a7a 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -46,26 +46,32 @@ public: // y -- y coordinates of wipe tower in mm ( left bottom corner ) // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) // wipe_area -- space available for one toolchange in mm - WipeTowerPrusaMM(float x, float y, float width, float rotation_angle, float cooling_tube_retraction, + WipeTowerPrusaMM(bool semm, float x, float y, float width, float rotation_angle, float cooling_tube_retraction, float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, float bridging, bool set_extruder_trimpot, GCodeFlavor flavor, const std::vector>& wiping_matrix, unsigned int initial_tool) : - m_wipe_tower_pos(x, y), + m_semm(semm), + m_wipe_tower_pos(x, y), m_wipe_tower_width(width), m_wipe_tower_rotation_angle(rotation_angle), m_y_shift(0.f), m_z_pos(0.f), m_is_first_layer(false), - m_cooling_tube_retraction(cooling_tube_retraction), - m_cooling_tube_length(cooling_tube_length), - m_parking_pos_retraction(parking_pos_retraction), - m_extra_loading_move(extra_loading_move), - m_bridging(bridging), - m_set_extruder_trimpot(set_extruder_trimpot), m_gcode_flavor(flavor), + m_bridging(bridging), m_current_tool(initial_tool), wipe_volumes(wiping_matrix) - {} + { + // If this is a single extruder MM printer, we will use all the SE-specific config values. + // Otherwise, the defaults will be used to turn off the SE stuff. + if (m_semm) { + m_cooling_tube_retraction = cooling_tube_retraction; + m_cooling_tube_length = cooling_tube_length; + m_parking_pos_retraction = parking_pos_retraction; + m_extra_loading_move = extra_loading_move; + m_set_extruder_trimpot = set_extruder_trimpot; + } + } virtual ~WipeTowerPrusaMM() {} @@ -81,21 +87,27 @@ public: m_filpar[idx].material = material; m_filpar[idx].temperature = temp; m_filpar[idx].first_layer_temperature = first_layer_temp; - m_filpar[idx].loading_speed = loading_speed; - m_filpar[idx].loading_speed_start = loading_speed_start; - m_filpar[idx].unloading_speed = unloading_speed; - m_filpar[idx].unloading_speed_start = unloading_speed_start; - m_filpar[idx].delay = delay; - m_filpar[idx].cooling_moves = cooling_moves; - m_filpar[idx].cooling_initial_speed = cooling_initial_speed; - m_filpar[idx].cooling_final_speed = cooling_final_speed; + + // If this is a single extruder MM printer, we will use all the SE-specific config values. + // Otherwise, the defaults will be used to turn off the SE stuff. + if (m_semm) { + m_filpar[idx].loading_speed = loading_speed; + m_filpar[idx].loading_speed_start = loading_speed_start; + m_filpar[idx].unloading_speed = unloading_speed; + m_filpar[idx].unloading_speed_start = unloading_speed_start; + m_filpar[idx].delay = delay; + m_filpar[idx].cooling_moves = cooling_moves; + m_filpar[idx].cooling_initial_speed = cooling_initial_speed; + m_filpar[idx].cooling_final_speed = cooling_final_speed; + } + if (max_volumetric_speed != 0.f) m_filpar[idx].max_e_speed = (max_volumetric_speed / Filament_Area); m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter - std::stringstream stream{ramming_parameters}; + std::stringstream stream{m_semm ? ramming_parameters : std::string()}; float speed = 0.f; stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; m_filpar[idx].ramming_line_width_multiplicator /= 100; @@ -220,7 +232,8 @@ private: const float WT_EPSILON = 1e-3f; - xy m_wipe_tower_pos; // Left front corner of the wipe tower in mm. + bool m_semm = true; // Are we using a single extruder multimaterial printer? + xy m_wipe_tower_pos; // Left front corner of the wipe tower in mm. float m_wipe_tower_width; // Width of the wipe tower. float m_wipe_tower_depth = 0.f; // Depth of the wipe tower float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index d09fd2bfe1..0b73f56e8b 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1789,78 +1789,40 @@ void Print::_make_wipe_tower() } } this->throw_if_canceled(); - - bool semm = m_config.single_extruder_multi_material.value; - float cooling_tube_retraction = 0.0; - float cooling_tube_length = 0.0; - float parking_pos_retraction = 0.0; - bool extra_loading_move = 0.0; - bool high_current_on_filament_swap = false; - - if (semm) { - cooling_tube_retraction = float(m_config.cooling_tube_retraction.value); - cooling_tube_length = float(m_config.cooling_tube_length.value); - parking_pos_retraction = float(m_config.parking_pos_retraction.value); - extra_loading_move = float(m_config.extra_loading_move.value); - high_current_on_filament_swap = m_config.high_current_on_filament_swap.value; - } // Initialize the wipe tower. WipeTowerPrusaMM wipe_tower( + m_config.single_extruder_multi_material.value, float(m_config.wipe_tower_x.value), float(m_config.wipe_tower_y.value), float(m_config.wipe_tower_width.value), - float(m_config.wipe_tower_rotation_angle.value), cooling_tube_retraction, - cooling_tube_length, parking_pos_retraction, - extra_loading_move, float(m_config.wipe_tower_bridging), - high_current_on_filament_swap, m_config.gcode_flavor, wipe_volumes, + float(m_config.wipe_tower_rotation_angle.value), float(m_config.cooling_tube_retraction.value), + float(m_config.cooling_tube_length.value), float(m_config.parking_pos_retraction.value), + float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), + m_config.high_current_on_filament_swap.value, m_config.gcode_flavor, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder()); //wipe_tower.set_retract(); //wipe_tower.set_zhop(); // Set the extruder & material properties at the wipe tower object. - for (size_t i = 0; i < number_of_extruders; ++ i) { - float loading_speed = 0.0; - float loading_speed_start = 0.0; - float unloading_speed = 0.0; - float unloading_speed_start = 0.0; - float toolchange_delay = 0.0; - int cooling_moves = 0; - float cooling_initial_speed = 0.0; - float cooling_final_speed = 0.0; - float max_volumetric_speed = 0.f; - std::string ramming_parameters; - - if (semm) { - loading_speed = m_config.filament_loading_speed.get_at(i); - loading_speed_start = m_config.filament_loading_speed_start.get_at(i); - unloading_speed = m_config.filament_unloading_speed.get_at(i); - unloading_speed_start = m_config.filament_unloading_speed_start.get_at(i); - toolchange_delay = m_config.filament_toolchange_delay.get_at(i); - cooling_moves = m_config.filament_cooling_moves.get_at(i); - cooling_initial_speed = m_config.filament_cooling_initial_speed.get_at(i); - cooling_final_speed = m_config.filament_cooling_final_speed.get_at(i); - ramming_parameters = m_config.filament_ramming_parameters.get_at(i); - max_volumetric_speed = m_config.filament_max_volumetric_speed.get_at(i); - } - + for (size_t i = 0; i < number_of_extruders; ++ i) + wipe_tower.set_extruder( i, WipeTowerPrusaMM::parse_material(m_config.filament_type.get_at(i).c_str()), m_config.temperature.get_at(i), m_config.first_layer_temperature.get_at(i), - loading_speed, - loading_speed_start, - unloading_speed, - unloading_speed_start, - toolchange_delay, - cooling_moves, - cooling_initial_speed, - cooling_final_speed, - ramming_parameters, - max_volumetric_speed, + m_config.filament_loading_speed.get_at(i), + m_config.filament_loading_speed_start.get_at(i), + m_config.filament_unloading_speed.get_at(i), + m_config.filament_unloading_speed_start.get_at(i), + m_config.filament_toolchange_delay.get_at(i), + m_config.filament_cooling_moves.get_at(i), + m_config.filament_cooling_initial_speed.get_at(i), + m_config.filament_cooling_final_speed.get_at(i), + m_config.filament_ramming_parameters.get_at(i), + m_config.filament_max_volumetric_speed.get_at(i), m_config.nozzle_diameter.get_at(i)); - } m_wipe_tower_data.priming = Slic3r::make_unique( wipe_tower.prime(this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); From 41164a9cb3ce34f26f4a5d4424423fb6639f87eb Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 12 Jun 2019 15:47:05 +0200 Subject: [PATCH 115/627] Multimaterial printing: Changed the way how custom gcodes are inserted Each toolchange now emits: - end filament custom gcode - toolchange custom gcode; if not provided, a standard Tn command is inserted - start filament gcode Hopefully it is now consistent for SE/ME printers with/without the wipe tower The priming line does not work - will be fixed in the next commit --- src/libslic3r/GCode.cpp | 119 +++++++++++++---------- src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 7 +- 2 files changed, 75 insertions(+), 51 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d6355e4d2e..3cd381acae 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -195,6 +195,7 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T "Travel to a Wipe Tower"); gcode += gcodegen.unretract(); + // Process the end filament gcode. std::string end_filament_gcode_str; if (gcodegen.writer().extruder() != nullptr) { @@ -206,21 +207,36 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T check_add_eol(end_filament_gcode_str); } } - - // Process the tool chagne gcode. + + // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament. + // Otherwise, leave control to the user completely. std::string toolchange_gcode_str; - const std::string &toolchange_gcode = gcodegen.config().toolchange_gcode.value; - if (gcodegen.writer().extruder() != nullptr && ! toolchange_gcode.empty()) { - // Process the custom toolchange_gcode. - DynamicConfig config; - config.set_key_value("previous_extruder", new ConfigOptionInt((int)gcodegen.writer().extruder()->id())); - config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); - check_add_eol(toolchange_gcode_str); + if (gcodegen.writer().extruder() != nullptr) { + const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; + if (!toolchange_gcode.empty()) { + DynamicConfig config; + config.set_key_value("previous_extruder", new ConfigOptionInt((int)gcodegen.writer().extruder()->id())); + config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); + check_add_eol(toolchange_gcode_str); + } + + std::string toolchange_command; + if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) + toolchange_command = gcodegen.writer().toolchange(new_extruder_id); + if (toolchange_gcode.empty()) + toolchange_gcode_str = toolchange_command; + else { + // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. + } } - + + + + gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); + // Process the start filament gcode. std::string start_filament_gcode_str; const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id); @@ -231,8 +247,8 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T start_filament_gcode_str = gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id, &config); check_add_eol(start_filament_gcode_str); } - - // Insert the end filament, toolchange, and start filament gcode. + + // Insert the end filament, toolchange, and start filament gcode into the generated gcode. DynamicConfig config; config.set_key_value("end_filament_gcode", new ConfigOptionString(end_filament_gcode_str)); config.set_key_value("toolchange_gcode", new ConfigOptionString(toolchange_gcode_str)); @@ -242,9 +258,6 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T gcode += tcr_gcode; check_add_eol(toolchange_gcode_str); - // Let the m_writer know the current extruder_id, but ignore the generated G-code. - if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) - gcodegen.writer().toolchange(new_extruder_id); // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(Vec2d(end_pos.x, end_pos.y)); @@ -837,22 +850,16 @@ void GCode::_do_export(Print &print, FILE *file) // Write the custom start G-code _writeln(file, start_gcode); - // Process filament-specific gcode in extruder order. - if (has_wipe_tower) { + + // Process filament-specific gcode. + /* if (has_wipe_tower) { // Wipe tower will control the extruder switching, it will call the start_filament_gcode. - } else if (print.config().single_extruder_multi_material) { - // Only initialize the initial extruder. - DynamicConfig config; - config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); - _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); } else { - DynamicConfig config; - for (const std::string &start_gcode : print.config().start_filament_gcode.values) { - int extruder_id = (unsigned int)(&start_gcode - &print.config().start_filament_gcode.values.front()); - config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); - _writeln(file, this->placeholder_parser_process("start_filament_gcode", start_gcode, extruder_id, &config)); - } + DynamicConfig config; + config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(initial_extruder_id))); + _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config().start_filament_gcode.values[initial_extruder_id], initial_extruder_id, &config)); } +*/ this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true); print.throw_if_canceled(); @@ -2763,38 +2770,50 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) m_wipe.reset_path(); if (m_writer.extruder() != nullptr) { - // Process the custom end_filament_gcode in case of single_extruder_multi_material. + // Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower + // so it should not be injected twice. unsigned int old_extruder_id = m_writer.extruder()->id(); const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id); - if (m_config.single_extruder_multi_material && ! end_filament_gcode.empty()) { + if (! end_filament_gcode.empty()) { gcode += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); check_add_eol(gcode); } } - m_placeholder_parser.set("current_extruder", extruder_id); - - if (m_writer.extruder() != nullptr && ! m_config.toolchange_gcode.value.empty()) { - // Process the custom toolchange_gcode. - DynamicConfig config; - config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id())); - config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - gcode += placeholder_parser_process("toolchange_gcode", m_config.toolchange_gcode.value, extruder_id, &config); - check_add_eol(gcode); - } // If ooze prevention is enabled, park current extruder in the nearest // standby point and set it to the standby temperature. if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) gcode += m_ooze_prevention.pre_toolchange(*this); - // Append the toolchange command. - gcode += m_writer.toolchange(extruder_id); - // Append the filament start G-code for single_extruder_multi_material. + + const std::string& toolchange_gcode = m_config.toolchange_gcode.value; + if (m_writer.extruder() != nullptr) { + // Process the custom toolchange_gcode. If it is empty, insert just a Tn command. + if (!toolchange_gcode.empty()) { + DynamicConfig config; + config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id())); + config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += placeholder_parser_process("toolchange_gcode", toolchange_gcode, extruder_id, &config); + check_add_eol(gcode); + } + } + + // We inform the writer about what is happening, but we may not use the resulting gcode. + std::string toolchange_command = m_writer.toolchange(extruder_id); + if (toolchange_gcode.empty()) + gcode += toolchange_command; + else { + // user provided his own toolchange gcode, no need to do anything + } + + m_placeholder_parser.set("current_extruder", extruder_id); + + // Append the filament start G-code. const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); - if (m_config.single_extruder_multi_material && ! start_filament_gcode.empty()) { - // Process the start_filament_gcode for the active filament only. + if (! start_filament_gcode.empty()) { + // Process the start_filament_gcode for the new filament. gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); check_add_eol(gcode); } diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 2a74d8248e..0d0422fdcf 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -922,9 +922,14 @@ void WipeTowerPrusaMM::toolchange_Change( if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + // This is where we want to place the custom gcodes. We will use placeholders for this. + // These will be substituted by the actual gcodes when the gcode is generated. writer.append("[end_filament_gcode]\n"); writer.append("[toolchange_gcode]\n"); - writer.set_tool(new_tool); + + // The toolchange Tn command will be inserted later, only in case that the user does + // not provide a custom toolchange gcode. + //writer.set_tool(new_tool); writer.append("[start_filament_gcode]\n"); writer.flush_planner_queue(); From aee376762e508df543d9805f0233a3f987999b32 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 14 Jun 2019 12:28:24 +0200 Subject: [PATCH 116/627] Changed handling of priming extrusions to allow injection of filament and toolchange custom gcodes The priming extrusions were handled separately from the rest of the wipe tower toolchanges. In order to be able to use the logic from previous commit for them (custom toolchange gcodes etc), some unpleasant code shuffling was needed --- src/libslic3r/GCode.cpp | 81 ++++++++++------- src/libslic3r/GCode.hpp | 4 +- src/libslic3r/GCode/PrintExtents.cpp | 25 ++--- src/libslic3r/GCode/WipeTower.hpp | 8 +- src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 111 +++++++++++++++-------- src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 2 +- src/libslic3r/Print.cpp | 2 +- src/libslic3r/Print.hpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 3 +- 9 files changed, 146 insertions(+), 92 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 3cd381acae..768cec6f4a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -176,24 +176,29 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T float alpha = m_wipe_tower_rotation/180.f * float(M_PI); WipeTower::xy start_pos = tcr.start_pos; WipeTower::xy end_pos = tcr.end_pos; - start_pos.rotate(alpha); - start_pos.translate(m_wipe_tower_pos); - end_pos.rotate(alpha); - end_pos.translate(m_wipe_tower_pos); - std::string tcr_rotated_gcode = rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha); + if (!tcr.priming) { + start_pos.rotate(alpha); + start_pos.translate(m_wipe_tower_pos); + end_pos.rotate(alpha); + end_pos.translate(m_wipe_tower_pos); + } + std::string tcr_rotated_gcode = tcr.priming ? tcr.gcode : rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha); // Disable linear advance for the wipe tower operations. gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); - // Move over the wipe tower. - // Retract for a tool change, using the toolchange retract value and setting the priming extra length. - gcode += gcodegen.retract(true); - gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; - gcode += gcodegen.travel_to( - wipe_tower_point_to_object_point(gcodegen, start_pos), - erMixed, - "Travel to a Wipe Tower"); - gcode += gcodegen.unretract(); + + if (!tcr.priming) { + // Move over the wipe tower. + // Retract for a tool change, using the toolchange retract value and setting the priming extra length. + gcode += gcodegen.retract(true); + gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; + gcode += gcodegen.travel_to( + wipe_tower_point_to_object_point(gcodegen, start_pos), + erMixed, + "Travel to a Wipe Tower"); + gcode += gcodegen.unretract(); + } // Process the end filament gcode. @@ -211,11 +216,12 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament. // Otherwise, leave control to the user completely. std::string toolchange_gcode_str; - if (gcodegen.writer().extruder() != nullptr) { + if (true /*gcodegen.writer().extruder() != nullptr*/) { const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; if (!toolchange_gcode.empty()) { DynamicConfig config; - config.set_key_value("previous_extruder", new ConfigOptionInt((int)gcodegen.writer().extruder()->id())); + int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; + config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); @@ -224,7 +230,7 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T } std::string toolchange_command; - if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) + if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) toolchange_command = gcodegen.writer().toolchange(new_extruder_id); if (toolchange_gcode.empty()) toolchange_gcode_str = toolchange_command; @@ -233,8 +239,6 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T } } - - gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); // Process the start filament gcode. @@ -334,27 +338,36 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) assert(m_layer_idx == 0); std::string gcode; - if (&m_priming != nullptr && ! m_priming.extrusions.empty()) { + if (&m_priming != nullptr) { // Disable linear advance for the wipe tower operations. - gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); - // Let the tool change be executed by the wipe tower class. - // Inform the G-code writer about the changes done behind its back. - gcode += m_priming.gcode; - // Let the m_writer know the current extruder_id, but ignore the generated G-code. - unsigned int current_extruder_id = m_priming.extrusions.back().tool; - gcodegen.writer().toolchange(current_extruder_id); - gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); + //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); + + for (const WipeTower::ToolChangeResult& tcr : m_priming) { + if (!tcr.extrusions.empty()) + gcode += append_tcr(gcodegen, tcr, tcr.new_tool, tcr.print_z); + + + // Let the tool change be executed by the wipe tower class. + // Inform the G-code writer about the changes done behind its back. + //gcode += tcr.gcode; + // Let the m_writer know the current extruder_id, but ignore the generated G-code. + // unsigned int current_extruder_id = tcr.extrusions.back().tool; + // gcodegen.writer().toolchange(current_extruder_id); + // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); + + } + // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(Vec2d(m_priming.end_pos.x, m_priming.end_pos.y)); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); + /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y)); + gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); // Prepare a future wipe. gcodegen.m_wipe.path.points.clear(); // Start the wipe at the current position. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - WipeTower::xy((std::abs(m_left - m_priming.end_pos.x) < std::abs(m_right - m_priming.end_pos.x)) ? m_right : m_left, - m_priming.end_pos.y))); + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, + WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left, + m_priming.back().end_pos.y)));*/ } return gcode; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 94f5a76aa1..35f5fb4851 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -83,7 +83,7 @@ class WipeTowerIntegration { public: WipeTowerIntegration( const PrintConfig &print_config, - const WipeTower::ToolChangeResult &priming, + const std::vector &priming, const std::vector> &tool_changes, const WipeTower::ToolChangeResult &final_purge) : m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f), @@ -116,7 +116,7 @@ private: const WipeTower::xy m_wipe_tower_pos; const float m_wipe_tower_rotation; // Reference to cached values at the Printer class. - const WipeTower::ToolChangeResult &m_priming; + const std::vector &m_priming; const std::vector> &m_tool_changes; const WipeTower::ToolChangeResult &m_final_purge; // Current layer index. diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index 92a58fdf06..4ff7c1cbd5 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -165,18 +165,19 @@ BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print) { BoundingBoxf bbox; if (print.wipe_tower_data().priming != nullptr) { - const WipeTower::ToolChangeResult &tcr = *print.wipe_tower_data().priming; - for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { - const WipeTower::Extrusion &e = tcr.extrusions[i]; - if (e.width > 0) { - Vec2d p1((&e - 1)->pos.x, (&e - 1)->pos.y); - Vec2d p2(e.pos.x, e.pos.y); - bbox.merge(p1); - coordf_t radius = 0.5 * e.width; - bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius); - bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius); - bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius); - bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius); + for (const WipeTower::ToolChangeResult &tcr : *print.wipe_tower_data().priming) { + for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { + const WipeTower::Extrusion &e = tcr.extrusions[i]; + if (e.width > 0) { + Vec2d p1((&e - 1)->pos.x, (&e - 1)->pos.y); + Vec2d p2(e.pos.x, e.pos.y); + bbox.merge(p1); + coordf_t radius = 0.5 * e.width; + bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius); + bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius); + bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius); + bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius); + } } } } diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 8ea3abd93f..ba841fdd79 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -119,6 +119,12 @@ public: // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later) bool priming; + // Initial tool + int initial_tool; + + // New tool + int new_tool; + // Sum the total length of the extrusion. float total_extrusion_length_in_plane() { float e_length = 0.f; @@ -134,7 +140,7 @@ public: }; // Returns gcode to prime the nozzles at the front edge of the print bed. - virtual ToolChangeResult prime( + virtual std::vector prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 0d0422fdcf..6923972768 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -515,7 +515,7 @@ std::string WipeTowerPrusaMM::to_string(material_type material) } // Returns gcode to prime the nozzles at the front edge of the print bed. -WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( +std::vector WipeTowerPrusaMM::prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. @@ -535,22 +535,34 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( const float prime_section_width = std::min(240.f / tools.size(), 60.f); box_coordinates cleaning_box(xy(5.f, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); - writer.set_extrusion_flow(m_extrusion_flow) - .set_z(m_z_pos) - .set_initial_tool(m_current_tool) - .append(";--------------------\n" - "; CP PRIMING START\n") - .append(";--------------------\n"); - writer.speed_override_backup(); - writer.speed_override(100); - writer.set_initial_position(xy(0.f, 0.f)) // Always move to the starting position - .travel(cleaning_box.ld, 7200); - if (m_set_extruder_trimpot) - writer.set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. + std::vector results; + // Iterate over all priming toolchanges and push respective ToolChangeResults into results vector. for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { + int old_tool = m_current_tool; + + PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + writer.set_extrusion_flow(m_extrusion_flow) + .set_z(m_z_pos) + .set_initial_tool(m_current_tool); + + // This is the first toolchange - initiate priming + if (idx_tool == 0) { + writer.append(";--------------------\n" + "; CP PRIMING START\n") + .append(";--------------------\n") + .speed_override_backup() + .speed_override(100) + .set_initial_position(xy(0.f, 0.f)) // Always move to the starting position + .travel(cleaning_box.ld, 7200); + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. + } + else + writer.set_initial_position(results.back().end_pos); + + unsigned int tool = tools[idx_tool]; m_left_to_right = true; toolchange_Change(writer, tool, m_filpar[tool].material); // Select the tool, set a speed override for soluble and flex materials. @@ -569,39 +581,48 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( writer.travel(cleaning_box.ld, 7200); } ++ m_num_tool_changes; + + + // Ask our writer about how much material was consumed: + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + + ToolChangeResult result; + result.priming = true; + result.initial_tool = old_tool; + result.new_tool = m_current_tool; + result.print_z = this->m_z_pos; + result.layer_height = this->m_layer_height; + result.gcode = writer.gcode(); + result.elapsed_time = writer.elapsed_time(); + result.extrusions = writer.extrusions(); + result.start_pos = writer.start_pos_rotated(); + result.end_pos = writer.pos_rotated(); + + results.push_back(std::move(result)); + + // This is the last priming toolchange - finish priming + if (idx_tool+1 == tools.size()) { + // Reset the extruder current to a normal value. + if (m_set_extruder_trimpot) + writer.set_extruder_trimpot(550); + writer.speed_override_restore() + .feedrate(6000) + .flush_planner_queue() + .reset_extruder() + .append("; CP PRIMING END\n" + ";------------------\n" + "\n\n"); + } } m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear // in the output gcode - we should not remember emitting them (we will output them twice in the worst case) - // Reset the extruder current to a normal value. - if (m_set_extruder_trimpot) - writer.set_extruder_trimpot(550); - writer.speed_override_restore(); - writer.feedrate(6000) - .flush_planner_queue() - .reset_extruder() - .append("; CP PRIMING END\n" - ";------------------\n" - "\n\n"); - // so that tool_change() will know to extrude the wipe tower brim: m_print_brim = true; - // Ask our writer about how much material was consumed: - if (m_current_tool < m_used_filament_length.size()) - m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - - ToolChangeResult result; - result.priming = true; - result.print_z = this->m_z_pos; - result.layer_height = this->m_layer_height; - result.gcode = writer.gcode(); - result.elapsed_time = writer.elapsed_time(); - result.extrusions = writer.extrusions(); - result.start_pos = writer.start_pos_rotated(); - result.end_pos = writer.pos_rotated(); - return result; + return results; } WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, bool last_in_layer) @@ -609,6 +630,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo if ( m_print_brim ) return toolchange_Brim(); + int old_tool = m_current_tool; + float wipe_area = 0.f; bool last_change_in_layer = false; float wipe_volume = 0.f; @@ -697,6 +720,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo ToolChangeResult result; result.priming = false; + result.initial_tool = old_tool; + result.new_tool = m_current_tool; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -709,6 +734,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset) { + int old_tool = m_current_tool; + const box_coordinates wipeTower_box( WipeTower::xy(0.f, 0.f), m_wipe_tower_width, @@ -751,6 +778,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo ToolChangeResult result; result.priming = false; + result.initial_tool = old_tool; + result.new_tool = m_current_tool; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); @@ -1044,6 +1073,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() // Otherwise the caller would likely travel to the wipe tower in vain. assert(! this->layer_finished()); + int old_tool = m_current_tool; + PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) @@ -1125,6 +1156,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() ToolChangeResult result; result.priming = false; + result.initial_tool = old_tool; + result.new_tool = m_current_tool; result.print_z = this->m_z_pos; result.layer_height = this->m_layer_height; result.gcode = writer.gcode(); diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index 8e1e494a7a..575234c4cb 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -172,7 +172,7 @@ public: virtual bool finished() const { return m_max_color_changes == 0; } // Returns gcode to prime the nozzles at the front edge of the print bed. - virtual ToolChangeResult prime( + virtual std::vector prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 0b73f56e8b..1cf79340e7 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1824,7 +1824,7 @@ void Print::_make_wipe_tower() m_config.filament_max_volumetric_speed.get_at(i), m_config.nozzle_diameter.get_at(i)); - m_wipe_tower_data.priming = Slic3r::make_unique( + m_wipe_tower_data.priming = Slic3r::make_unique>( wipe_tower.prime(this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); // Lets go through the wipe tower layers and determine pairs of extruder changes for each diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 53d6d692db..431f8023e9 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -213,7 +213,7 @@ struct WipeTowerData // Cache it here, so it does not need to be recalculated during the G-code generation. ToolOrdering tool_ordering; // Cache of tool changes per print layer. - std::unique_ptr priming; + std::unique_ptr> priming; std::vector> tool_changes; std::unique_ptr final_purge; std::vector used_filament; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 216848477c..65e06e4323 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4804,7 +4804,8 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ ctxt.print = print; ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors; if (print->wipe_tower_data().priming && print->config().single_extruder_multi_material_priming) - ctxt.priming.emplace_back(*print->wipe_tower_data().priming.get()); + for (int i=0; iwipe_tower_data().priming.get()->size(); ++i) + ctxt.priming.emplace_back(print->wipe_tower_data().priming.get()->at(i)); if (print->wipe_tower_data().final_purge) ctxt.final.emplace_back(*print->wipe_tower_data().final_purge.get()); From 678d0e18a7c18717edbd5b8ead848926f3dd6342 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 14 Jun 2019 12:49:43 +0200 Subject: [PATCH 117/627] WipeTowerIntegration class: print_z is not passed around, ToolChangeResult objects are aware of it --- src/libslic3r/GCode.cpp | 14 +++++++------- src/libslic3r/GCode.hpp | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 768cec6f4a..a606f56662 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -167,7 +167,7 @@ static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Wipe return Point(scale_(wipe_tower_pt.x - gcodegen.origin()(0)), scale_(wipe_tower_pt.y - gcodegen.origin()(1))); } -std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, float print_z) const +std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const { std::string gcode; @@ -224,7 +224,7 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); check_add_eol(toolchange_gcode_str); } @@ -344,7 +344,7 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) for (const WipeTower::ToolChangeResult& tcr : m_priming) { if (!tcr.extrusions.empty()) - gcode += append_tcr(gcodegen, tcr, tcr.new_tool, tcr.print_z); + gcode += append_tcr(gcodegen, tcr, tcr.new_tool); // Let the tool change be executed by the wipe tower class. @@ -372,14 +372,14 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) return gcode; } -std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer, float print_z) +std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) { std::string gcode; assert(m_layer_idx >= 0 && m_layer_idx <= m_tool_changes.size()); if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { if (m_layer_idx < m_tool_changes.size()) { assert(m_tool_change_idx < m_tool_changes[m_layer_idx].size()); - gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, print_z); + gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id); } m_brim_done = true; } @@ -392,7 +392,7 @@ std::string WipeTowerIntegration::finalize(GCode &gcodegen) std::string gcode; if (std::abs(gcodegen.writer().get_position()(2) - m_final_purge.print_z) > EPSILON) gcode += gcodegen.change_layer(m_final_purge.print_z); - gcode += append_tcr(gcodegen, m_final_purge, -1, m_final_purge.print_z); + gcode += append_tcr(gcodegen, m_final_purge, -1); return gcode; } @@ -1649,7 +1649,7 @@ void GCode::process_layer( for (unsigned int extruder_id : layer_tools.extruders) { gcode += (layer_tools.has_wipe_tower && m_wipe_tower) ? - m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back(), print_z) : + m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) : this->set_extruder(extruder_id, print_z); // let analyzer tag generator aware of a role type change diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 35f5fb4851..639c83aa4b 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -99,13 +99,13 @@ public: std::string prime(GCode &gcodegen); void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } - std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer, float print_z); + std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer); std::string finalize(GCode &gcodegen); std::vector used_filament_length() const; private: WipeTowerIntegration& operator=(const WipeTowerIntegration&); - std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, float print_z) const; + std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const; // Postprocesses gcode: rotates and moves all G1 extrusions and returns result std::string rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const; From 0eecfc660435b591a3fccf58e6d583c8fc3e0273 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 14 Jun 2019 14:31:53 +0200 Subject: [PATCH 118/627] Wipe tower - removed the obsolete material_type enum no longer necessary because the speed overrides that the enum controlled were recently removed the comment in gcode is now just about appending the config string --- src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 64 +++--------------------- src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 27 ++-------- src/libslic3r/Print.cpp | 2 +- 3 files changed, 13 insertions(+), 80 deletions(-) diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 6923972768..0c32bd50c5 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -398,13 +398,6 @@ public: return *this; } - Writer& comment_material(WipeTowerPrusaMM::material_type material) - { - m_gcode += "; material : "; - m_gcode += WipeTowerPrusaMM::to_string(material) + "\n"; - return *this; - }; - Writer& append(const char *text) { m_gcode += text; return *this; } private: @@ -470,50 +463,6 @@ private: }; // namespace PrusaMultiMaterial - -WipeTowerPrusaMM::material_type WipeTowerPrusaMM::parse_material(const char *name) -{ - if (strcasecmp(name, "PLA") == 0) - return PLA; - if (strcasecmp(name, "ABS") == 0) - return ABS; - if (strcasecmp(name, "PET") == 0) - return PET; - if (strcasecmp(name, "HIPS") == 0) - return HIPS; - if (strcasecmp(name, "FLEX") == 0) - return FLEX; - if (strcasecmp(name, "SCAFF") == 0) - return SCAFF; - if (strcasecmp(name, "EDGE") == 0) - return EDGE; - if (strcasecmp(name, "NGEN") == 0) - return NGEN; - if (strcasecmp(name, "PVA") == 0) - return PVA; - if (strcasecmp(name, "PC") == 0) - return PC; - return INVALID; -} - -std::string WipeTowerPrusaMM::to_string(material_type material) -{ - switch (material) { - case PLA: return "PLA"; - case ABS: return "ABS"; - case PET: return "PET"; - case HIPS: return "HIPS"; - case FLEX: return "FLEX"; - case SCAFF: return "SCAFF"; - case EDGE: return "EDGE"; - case NGEN: return "NGEN"; - case PVA: return "PVA"; - case PC: return "PC"; - case INVALID: - default: return "INVALID"; - } -} - // Returns gcode to prime the nozzles at the front edge of the print bed. std::vector WipeTowerPrusaMM::prime( // print_z of the first layer. @@ -665,9 +614,12 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo .set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f)) .append(";--------------------\n" "; CP TOOLCHANGE START\n") - .comment_with_value(" toolchange #", m_num_tool_changes + 1) // the number is zero-based - .comment_material(m_filpar[m_current_tool].material) - .append(";--------------------\n"); + .comment_with_value(" toolchange #", m_num_tool_changes + 1); // the number is zero-based + + if (tool != (unsigned)(-1)) + writer.append(std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + " -> " + m_filpar[tool].material + "\n").c_str()) + .append(";--------------------\n"); + writer.speed_override_backup(); writer.speed_override(100); @@ -796,7 +748,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo void WipeTowerPrusaMM::toolchange_Unload( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box, - const material_type current_material, + const std::string& current_material, const int new_temperature) { float xl = cleaning_box.ld.x + 1.f * m_perimeter_width; @@ -945,7 +897,7 @@ void WipeTowerPrusaMM::toolchange_Unload( void WipeTowerPrusaMM::toolchange_Change( PrusaMultiMaterial::Writer &writer, const unsigned int new_tool, - material_type new_material) + const std::string& new_material) { // Ask the writer about how much of the old filament we consumed: if (m_current_tool < m_used_filament_length.size()) diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index 575234c4cb..a6478ee58a 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -23,25 +23,6 @@ namespace PrusaMultiMaterial { class WipeTowerPrusaMM : public WipeTower { public: - enum material_type - { - INVALID = -1, - PLA = 0, // E:210C B:55C - ABS = 1, // E:255C B:100C - PET = 2, // E:240C B:90C - HIPS = 3, // E:220C B:100C - FLEX = 4, // E:245C B:80C - SCAFF = 5, // E:215C B:55C - EDGE = 6, // E:240C B:80C - NGEN = 7, // E:230C B:80C - PVA = 8, // E:210C B:80C - PC = 9 - }; - - // Parse material name into material_type. - static material_type parse_material(const char *name); - static std::string to_string(material_type material); - // x -- x coordinates of wipe tower in mm ( left bottom corner ) // y -- y coordinates of wipe tower in mm ( left bottom corner ) // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) @@ -77,7 +58,7 @@ public: // Set the extruder properties. - void set_extruder(size_t idx, material_type material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start, + void set_extruder(size_t idx, std::string material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start, float unloading_speed, float unloading_speed_start, float delay, int cooling_moves, float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float max_volumetric_speed, float nozzle_diameter) { @@ -198,7 +179,7 @@ public: virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; } struct FilamentParameters { - material_type material = PLA; + std::string material = "PLA"; int temperature = 0; int first_layer_temperature = 0; float loading_speed = 0.f; @@ -370,13 +351,13 @@ private: void toolchange_Unload( PrusaMultiMaterial::Writer &writer, const box_coordinates &cleaning_box, - const material_type current_material, + const std::string& current_material, const int new_temperature); void toolchange_Change( PrusaMultiMaterial::Writer &writer, const unsigned int new_tool, - material_type new_material); + const std::string& new_material); void toolchange_Load( PrusaMultiMaterial::Writer &writer, diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 1cf79340e7..7a9bb785f8 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1809,7 +1809,7 @@ void Print::_make_wipe_tower() wipe_tower.set_extruder( i, - WipeTowerPrusaMM::parse_material(m_config.filament_type.get_at(i).c_str()), + m_config.filament_type.get_at(i), m_config.temperature.get_at(i), m_config.first_layer_temperature.get_at(i), m_config.filament_loading_speed.get_at(i), From 9f236bc6030a2a59db714e41d366e63d8855683e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 17 May 2019 14:17:49 +0200 Subject: [PATCH 119/627] Added transformation reset buttons in object manipulation panel --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 91 ++++++++++++++++++++++- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 10 ++- src/slic3r/GUI/OptionsGroup.cpp | 2 +- 3 files changed, 95 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 5bf25f3fc0..a67eaa4f06 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -190,8 +190,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : def.set_default_value(new ConfigOptionFloat(0.0)); def.width = field_width/*50*/; - // Add "uniform scaling" button in front of "Scale" option if (option_name == "Scale") { + // Add "uniform scaling" button in front of "Scale" option line.near_label_widget = [this](wxWindow* parent) { auto btn = new LockButton(parent, wxID_ANY); btn->Bind(wxEVT_BUTTON, [btn, this](wxCommandEvent &event){ @@ -201,8 +201,52 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_lock_bnt = btn; return btn; }; + // Add reset scale button + auto reset_scale_button = [=](wxWindow* parent) { + auto btn = new ScalableButton(parent, wxID_ANY, "colorchange_delete_off.png"); + btn->SetToolTip(_(L("Reset scale"))); + m_reset_scale_button = btn; + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn, wxBU_EXACTFIT); + btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + change_scale_value(0, 100.); + change_scale_value(1, 100.); + change_scale_value(2, 100.); + }); + return sizer; + }; + line.append_widget(reset_scale_button); } + else if (option_name == "Rotation") { + // Add reset rotation button + auto reset_rotation_button = [=](wxWindow* parent) { + auto btn = new ScalableButton(parent, wxID_ANY, "colorchange_delete_off.png"); + btn->SetToolTip(_(L("Reset rotation"))); + m_reset_rotation_button = btn; + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn, wxBU_EXACTFIT); + btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + // The following makes sure that the LOCAL coordinate system rotation is reset by calling + // methods that are readily available here - it is not nice but gets the work done. + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); + if (selection.is_single_volume() || selection.is_single_modifier()) + volume->set_volume_rotation(Vec3d::Zero()); + else if (selection.is_single_full_instance()) + volume->set_instance_rotation(Vec3d::Zero()); + else + return; + + canvas->do_rotate(); + + UpdateAndShow(true); + }); + return sizer; + }; + line.append_widget(reset_rotation_button); + } // Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment else if (option_name == "Size") { line.near_label_widget = [this](wxWindow* parent) { @@ -224,8 +268,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : return line; }; - // Settings table + m_og->sidetext_width = 3; m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")), &m_move_Label); m_og->append_line(add_og_to_object_settings(L("Rotation"), "°"), &m_rotate_Label); m_og->append_line(add_og_to_object_settings(L("Scale"), "%"), &m_scale_Label); @@ -408,9 +452,49 @@ void ObjectManipulation::update_if_dirty() else m_og->disable(); + update_reset_buttons_visibility(); + m_dirty = false; } + + +void ObjectManipulation::update_reset_buttons_visibility() +{ + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + if (!canvas) + return; + const Selection& selection = canvas->get_selection(); + + bool show_rotation = false; + bool show_scale = false; + + if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Vec3d rotation; + Vec3d scale; + + if (selection.is_single_full_instance()) { + rotation = volume->get_instance_rotation(); + scale = volume->get_instance_scaling_factor(); + } + else { + rotation = volume->get_volume_rotation(); + scale = volume->get_volume_scaling_factor(); + } + show_rotation = !rotation.isApprox(Vec3d::Zero()); + show_scale = !scale.isApprox(Vec3d::Ones()); + } + + wxGetApp().CallAfter([this, show_rotation, show_scale]{ + m_reset_rotation_button->Show(show_rotation); + m_reset_scale_button->Show(show_scale); + }); +} + + + + #ifndef __APPLE__ void ObjectManipulation::emulate_kill_focus() { @@ -493,7 +577,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value) m_cache.rotation = rotation; m_cache.rotation_rounded(axis) = DBL_MAX; - this->UpdateAndShow(true); + this->UpdateAndShow(true); } void ObjectManipulation::change_scale_value(int axis, double value) @@ -511,6 +595,7 @@ void ObjectManipulation::change_scale_value(int axis, double value) this->UpdateAndShow(true); } + void ObjectManipulation::change_size_value(int axis, double value) { if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 7c359f3d72..6a13a3c091 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -53,6 +53,10 @@ class ObjectManipulation : public OG_Settings wxStaticText* m_scale_Label = nullptr; wxStaticText* m_rotate_Label = nullptr; + // Non-owning pointers to the reset buttons, so we can hide and show them. + ScalableButton* m_reset_scale_button = nullptr; + ScalableButton* m_reset_rotation_button = nullptr; + // Needs to be updated from OnIdle? bool m_dirty = false; // Cached labels for the delayed update, not localized! @@ -111,10 +115,8 @@ private: void reset_settings_value(); void update_settings_value(const Selection& selection); - // update size values after scale unit changing or "gizmos" - void update_size_value(const Vec3d& size); - // update rotation value after "gizmos" - void update_rotation_value(const Vec3d& rotation); + // Show or hide scale/rotation reset buttons if needed + void update_reset_buttons_visibility(); // change values void change_position_value(int axis, double value); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 2ac6b00af6..0149329005 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -276,7 +276,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // add sidetext if any if (option.sidetext != "") { auto sidetext = new wxStaticText( this->ctrl_parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition, - /*wxSize(sidetext_width*wxGetApp().em_unit(), -1)*/wxDefaultSize, wxALIGN_LEFT); + wxSize(sidetext_width != -1 ? sidetext_width*wxGetApp().em_unit() : -1, -1) /*wxDefaultSize*/, wxALIGN_LEFT); sidetext->SetBackgroundStyle(wxBG_STYLE_PAINT); sidetext->SetFont(wxGetApp().normal_font()); sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); From a3c1644ead545bfac6aab46d5761c8521fc8d48c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 3 Jun 2019 10:40:15 +0200 Subject: [PATCH 120/627] Added mirroring buttons into object manipulation panel --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 75 ++++++++++++++++++++++- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 5 ++ 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index a67eaa4f06..c2782df2de 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -92,6 +92,7 @@ void msw_rescale_word_local_combo(wxBitmapComboBox* combo) combo->SetValue(selection); } + ObjectManipulation::ObjectManipulation(wxWindow* parent) : OG_Settings(parent, true) #ifndef __APPLE__ @@ -162,16 +163,50 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const int field_width = 5; + // Mirror button size: + const int mirror_btn_width = 3; + // Legend for object modification line = Line{ "", "" }; def.label = ""; def.type = coString; - def.width = field_width/*50*/; + def.width = field_width - mirror_btn_width;//field_width/*50*/; for (const std::string axis : { "x", "y", "z" }) { const std::string label = boost::algorithm::to_upper_copy(axis); def.set_default_value(new ConfigOptionString{ " " + label }); Option option = Option(def, axis + "_axis_legend"); + + // We will add a button to toggle mirroring to each axis: + auto mirror_button = [=](wxWindow* parent) { + m_mirror_bitmap_on = ScalableBitmap(parent, "colorchange_add_on.png"); + m_mirror_bitmap_off = ScalableBitmap(parent, "colorchange_delete_off.png"); + auto btn = new ScalableButton(parent, wxID_ANY, "colorchange_delete_off.png", wxEmptyString, wxSize(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width)); + btn->SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), boost::algorithm::to_upper_copy(axis))); + m_mirror_buttons[axis] = btn; + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn, wxBU_EXACTFIT/* | wxRESERVE_SPACE_EVEN_IF_HIDDEN*/); + btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + wxWindow* btn = dynamic_cast(e.GetEventObject()); + Axis axis = (btn == m_mirror_buttons["x"] ? X : ( btn == m_mirror_buttons["y"] ? Y : Z)); + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); + + if (selection.is_single_volume() || selection.is_single_modifier()) + volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis)); + else if (selection.is_single_full_instance()) + volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis)); + else + return; + canvas->do_mirror(); + canvas->set_as_dirty(); + UpdateAndShow(true); + }); + return sizer; + }; + + option.side_widget = mirror_button; line.append_option(option); } line.near_label_widget = [this](wxWindow* parent) { @@ -226,8 +261,6 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn, wxBU_EXACTFIT); btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { - // The following makes sure that the LOCAL coordinate system rotation is reset by calling - // methods that are readily available here - it is not nice but gets the work done. GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); @@ -283,6 +316,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : ctrl->msw_rescale(); }; } + + void ObjectManipulation::Show(const bool show) { @@ -453,6 +488,7 @@ void ObjectManipulation::update_if_dirty() m_og->disable(); update_reset_buttons_visibility(); + update_mirror_buttons_visibility(); m_dirty = false; } @@ -494,6 +530,34 @@ void ObjectManipulation::update_reset_buttons_visibility() +void ObjectManipulation::update_mirror_buttons_visibility() +{ + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + std::array show = {false, false, false}; + + if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Vec3d mirror; + + if (selection.is_single_full_instance()) + mirror = volume->get_instance_mirror(); + else + mirror = volume->get_volume_mirror(); + + for (unsigned char i=0; i<3; ++i) + show[i] = mirror[i] < 0.; + } + + wxGetApp().CallAfter([this, show]{ + m_mirror_buttons["x"]->SetBitmap(show[0] ? m_mirror_bitmap_on.bmp() : m_mirror_bitmap_off.bmp()); + m_mirror_buttons["y"]->SetBitmap(show[1] ? m_mirror_bitmap_on.bmp() : m_mirror_bitmap_off.bmp()); + m_mirror_buttons["z"]->SetBitmap(show[2] ? m_mirror_bitmap_on.bmp() : m_mirror_bitmap_off.bmp()); + }); +} + + + #ifndef __APPLE__ void ObjectManipulation::emulate_kill_focus() @@ -751,6 +815,11 @@ void ObjectManipulation::msw_rescale() m_manifold_warning_bmp.msw_rescale(); m_fix_throught_netfab_bitmap->SetBitmap(m_manifold_warning_bmp.bmp()); + m_mirror_bitmap_on.msw_rescale(); + m_mirror_bitmap_off.msw_rescale(); + m_reset_scale_button->msw_rescale(); + m_reset_rotation_button->msw_rescale(); + get_og()->msw_rescale(); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 6a13a3c091..11b07cda6f 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -56,6 +56,9 @@ class ObjectManipulation : public OG_Settings // Non-owning pointers to the reset buttons, so we can hide and show them. ScalableButton* m_reset_scale_button = nullptr; ScalableButton* m_reset_rotation_button = nullptr; + std::map m_mirror_buttons; + ScalableBitmap m_mirror_bitmap_on; + ScalableBitmap m_mirror_bitmap_off; // Needs to be updated from OnIdle? bool m_dirty = false; @@ -117,6 +120,8 @@ private: // Show or hide scale/rotation reset buttons if needed void update_reset_buttons_visibility(); + //Show or hide mirror buttons + void update_mirror_buttons_visibility(); // change values void change_position_value(int axis, double value); From 980c6673d42e561d7bf53937e8213e2ce1f0e943 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 5 Jun 2019 11:23:30 +0200 Subject: [PATCH 121/627] Reset buttons - fixed rotation of instances with multiple volumes Mirroring buttons now hide where appropriate --- resources/icons/mirroring_off.png | Bin 0 -> 589 bytes resources/icons/mirroring_on.png | Bin 0 -> 600 bytes resources/icons/mirroring_transparent.png | Bin 0 -> 93 bytes src/slic3r/GUI/GUI_ObjectManipulation.cpp | 104 +++++++++++++++------- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 12 ++- 5 files changed, 83 insertions(+), 33 deletions(-) create mode 100644 resources/icons/mirroring_off.png create mode 100644 resources/icons/mirroring_on.png create mode 100644 resources/icons/mirroring_transparent.png diff --git a/resources/icons/mirroring_off.png b/resources/icons/mirroring_off.png new file mode 100644 index 0000000000000000000000000000000000000000..c16655271a7e10d9d83047189086d4d6b509abc3 GIT binary patch literal 589 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S0wixl{&NRXEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZEE?e4>iGH4#PT$HOWYUVmw=D)W4<$2R?NV2M}KA?FdtNTmJp~!15S*|LT8;MoC zXsZ3%W24$-moh2c@9l*fmo+q>Gq62KSXA699k*xgMiDE{G|yhA4QhurtG<}@uu!C_ z<$>}m-=I62B~!k9UHS2l6URxP__|{b{!P~`Som^z%$(k+UE0pWDc5vL>wMPBh1~ac zOgPu;&>~>6W*@_Frwy&D=l0S8q@B+Qm*U`007}-kq7#>;Nm&p5M&yqHoBEQzy zY0CSzuP^(vnT8meSs9vI xnHp;w7+M(^oS(SL7)3*FeoAIqCAtPfD`O*whU@kljsZ0=c)I$ztaD0e0ssxu+3)}W literal 0 HcmV?d00001 diff --git a/resources/icons/mirroring_on.png b/resources/icons/mirroring_on.png new file mode 100644 index 0000000000000000000000000000000000000000..6ddeccbe0a2a82019c5c2efd1773c20c0833c4d4 GIT binary patch literal 600 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBC{YpM6XNqfL!5oT`ng2Uv7KO- z`>oK>c;wi#w-Y8-9OfwbtPfPmnB?v5!uX#__a2bLS>O>_%)p?h48n{ROYO^mg6t)p zzOL*KxFrR+#s37PM*)TAdb&7gOf=4WlTK;F^nd^Dc_ijpxxR1T zX#3n&TDl=r(%}>cpt3=meXk`S|u=mmKH$V*x Mp00i_>zopr04OBpdH?_b literal 0 HcmV?d00001 diff --git a/resources/icons/mirroring_transparent.png b/resources/icons/mirroring_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..841010fcc14ba7f06f09feae3affb3d9ba64c0f5 GIT binary patch literal 93 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1|*9D%+3HQmSQK*5Dp-y;YjHK@SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), boost::algorithm::to_upper_copy(axis))); - m_mirror_buttons[axis] = btn; + wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width); + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off.png", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + btn->SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), label)); + + m_mirror_buttons[axis_idx].first = btn; + m_mirror_buttons[axis_idx].second = mbShown; auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn, wxBU_EXACTFIT/* | wxRESERVE_SPACE_EVEN_IF_HIDDEN*/); + sizer->Add(btn); + btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { - wxWindow* btn = dynamic_cast(e.GetEventObject()); - Axis axis = (btn == m_mirror_buttons["x"] ? X : ( btn == m_mirror_buttons["y"] ? Y : Z)); + Axis axis = (Axis)(axis_idx + X); + if (m_mirror_buttons[axis_idx].second == mbHidden) + return; + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); - if (selection.is_single_volume() || selection.is_single_modifier()) + if (selection.is_single_volume() || selection.is_single_modifier()) { + GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis)); - else if (selection.is_single_full_instance()) - volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis)); + } + else if (selection.is_single_full_instance()) { + for (unsigned int idx : selection.get_volume_idxs()){ + GLVolume* volume = const_cast(selection.get_volume(idx)); + volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis)); + } + } else return; + canvas->do_mirror(); canvas->set_as_dirty(); UpdateAndShow(true); @@ -238,7 +255,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : }; // Add reset scale button auto reset_scale_button = [=](wxWindow* parent) { - auto btn = new ScalableButton(parent, wxID_ANY, "colorchange_delete_off.png"); + auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); btn->SetToolTip(_(L("Reset scale"))); m_reset_scale_button = btn; auto sizer = new wxBoxSizer(wxHORIZONTAL); @@ -255,7 +272,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : else if (option_name == "Rotation") { // Add reset rotation button auto reset_rotation_button = [=](wxWindow* parent) { - auto btn = new ScalableButton(parent, wxID_ANY, "colorchange_delete_off.png"); + auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); btn->SetToolTip(_(L("Reset rotation"))); m_reset_rotation_button = btn; auto sizer = new wxBoxSizer(wxHORIZONTAL); @@ -263,12 +280,17 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); - if (selection.is_single_volume() || selection.is_single_modifier()) + if (selection.is_single_volume() || selection.is_single_modifier()) { + GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); volume->set_volume_rotation(Vec3d::Zero()); - else if (selection.is_single_full_instance()) - volume->set_instance_rotation(Vec3d::Zero()); + } + else if (selection.is_single_full_instance()) { + for (unsigned int idx : selection.get_volume_idxs()){ + GLVolume* volume = const_cast(selection.get_volume(idx)); + volume->set_instance_rotation(Vec3d::Zero()); + } + } else return; @@ -534,25 +556,42 @@ void ObjectManipulation::update_mirror_buttons_visibility() { GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); - std::array show = {false, false, false}; + std::array new_states = {mbHidden, mbHidden, mbHidden}; - if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - Vec3d mirror; + if (!m_world_coordinates) { + if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Vec3d mirror; - if (selection.is_single_full_instance()) - mirror = volume->get_instance_mirror(); - else - mirror = volume->get_volume_mirror(); + if (selection.is_single_full_instance()) + mirror = volume->get_instance_mirror(); + else + mirror = volume->get_volume_mirror(); - for (unsigned char i=0; i<3; ++i) - show[i] = mirror[i] < 0.; + for (unsigned char i=0; i<3; ++i) + new_states[i] = (mirror[i] < 0. ? mbActive : mbShown); + } + } + else { + // the mirroring buttons should be hidden in world coordinates, + // unless we make it actually mirror in world coords. } - wxGetApp().CallAfter([this, show]{ - m_mirror_buttons["x"]->SetBitmap(show[0] ? m_mirror_bitmap_on.bmp() : m_mirror_bitmap_off.bmp()); - m_mirror_buttons["y"]->SetBitmap(show[1] ? m_mirror_bitmap_on.bmp() : m_mirror_bitmap_off.bmp()); - m_mirror_buttons["z"]->SetBitmap(show[2] ? m_mirror_bitmap_on.bmp() : m_mirror_bitmap_off.bmp()); + // Hiding the buttons through Hide() always messed up the sizers. As a workaround, the button + // is assigned a transparent bitmap. We must of course remember the actual state. + wxGetApp().CallAfter([this, new_states]{ + for (int i=0; i<3; ++i) { + if (new_states[i] != m_mirror_buttons[i].second) { + const wxBitmap* bmp; + switch (new_states[i]) { + case mbHidden : bmp = &m_mirror_bitmap_hidden.bmp(); m_mirror_buttons[i].first->Enable(false); break; + case mbShown : bmp = &m_mirror_bitmap_off.bmp(); m_mirror_buttons[i].first->Enable(true); break; + case mbActive : bmp = &m_mirror_bitmap_on.bmp(); m_mirror_buttons[i].first->Enable(true); break; + } + m_mirror_buttons[i].first->SetBitmap(*bmp); + m_mirror_buttons[i].second = new_states[i]; + } + } }); } @@ -817,6 +856,7 @@ void ObjectManipulation::msw_rescale() m_mirror_bitmap_on.msw_rescale(); m_mirror_bitmap_off.msw_rescale(); + m_mirror_bitmap_hidden.msw_rescale(); m_reset_scale_button->msw_rescale(); m_reset_rotation_button->msw_rescale(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 11b07cda6f..cc2154514d 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -56,9 +56,19 @@ class ObjectManipulation : public OG_Settings // Non-owning pointers to the reset buttons, so we can hide and show them. ScalableButton* m_reset_scale_button = nullptr; ScalableButton* m_reset_rotation_button = nullptr; - std::map m_mirror_buttons; + + // Mirroring buttons and their current state + enum MirrorButtonState { + mbHidden, + mbShown, + mbActive + }; + std::array, 3> m_mirror_buttons; + + // Bitmaps for the mirroring buttons. ScalableBitmap m_mirror_bitmap_on; ScalableBitmap m_mirror_bitmap_off; + ScalableBitmap m_mirror_bitmap_hidden; // Needs to be updated from OnIdle? bool m_dirty = false; From 7b07a8da83633c5391b5b8ca8db5813ff1c367c9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 5 Jun 2019 15:27:20 +0200 Subject: [PATCH 122/627] Reset buttons: synchronization of instances/volumes --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 5 +++++ src/slic3r/GUI/Selection.hpp | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 2cbd048e55..224c053e4e 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -216,6 +216,9 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : else return; + selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); + selection.synchronize_unselected_volumes(); + canvas->do_mirror(); canvas->set_as_dirty(); UpdateAndShow(true); @@ -294,6 +297,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : else return; + selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); + selection.synchronize_unselected_volumes(); canvas->do_rotate(); UpdateAndShow(true); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 54bf52706b..5da1e477bc 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -333,6 +333,8 @@ private: void render_sidebar_rotation_hint(Axis axis) const; void render_sidebar_scale_hint(Axis axis) const; void render_sidebar_size_hint(Axis axis, double length) const; + +public: enum SyncRotationType { // Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis. SYNC_ROTATION_NONE = 0, @@ -343,6 +345,8 @@ private: }; void synchronize_unselected_instances(SyncRotationType sync_rotation_type); void synchronize_unselected_volumes(); + +private: void ensure_on_bed(); bool is_from_fully_selected_instance(unsigned int volume_idx) const; From ac8de0bcaff04b6dc038128c1544ed182f5b3f14 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 14 Jun 2019 15:37:29 +0200 Subject: [PATCH 123/627] Follow-up of 1a91add2e60f3a4d22cdd6c8a10e57dc8095e0a2 -> Improvements to tighter camera frustrum to reduce z-fighting --- src/slic3r/GUI/3DBed.cpp | 20 ++++++-------------- src/slic3r/GUI/3DBed.hpp | 1 - src/slic3r/GUI/3DScene.hpp | 1 + src/slic3r/GUI/Camera.cpp | 9 ++++++++- src/slic3r/GUI/GLCanvas3D.cpp | 4 ++-- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index c82b34f498..404fb47745 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -211,7 +211,7 @@ const double Bed3D::Axes::ArrowLength = 5.0; Bed3D::Axes::Axes() : origin(Vec3d::Zero()) -, length(Vec3d::Zero()) +, length(25.0 * Vec3d::Ones()) { m_quadric = ::gluNewQuadric(); if (m_quadric != nullptr) @@ -273,7 +273,6 @@ void Bed3D::Axes::render_axis(double length) const Bed3D::Bed3D() : m_type(Custom) - , m_extended_bounding_box_dirty(true) #if ENABLE_TEXTURES_FROM_SVG , m_vbo_id(0) #endif // ENABLE_TEXTURES_FROM_SVG @@ -314,8 +313,6 @@ bool Bed3D::set_shape(const Pointfs& shape) m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z); m_axes.length = 0.1 * m_bounding_box.max_size() * Vec3d::Ones(); - m_extended_bounding_box_dirty = true; - // Let the calee to update the UI. return true; } @@ -413,17 +410,12 @@ void Bed3D::calc_bounding_boxes() const m_extended_bounding_box = m_bounding_box; - if (m_extended_bounding_box_dirty) - { - // extend to contain Z axis - m_extended_bounding_box.merge(0.1 * m_bounding_box.max_size() * Vec3d::UnitZ()); + // extend to contain axes + m_extended_bounding_box.merge(m_axes.length + Axes::ArrowLength * Vec3d::Ones()); - if (!m_model.get_filename().empty()) - // extend to contain model - m_extended_bounding_box.merge(m_model.get_bounding_box()); - - m_extended_bounding_box_dirty = false; - } + // extend to contain model, if any + if (!m_model.get_filename().empty()) + m_extended_bounding_box.merge(m_model.get_transformed_bounding_box()); } void Bed3D::calc_triangles(const ExPolygon& poly) diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 79e96fdf01..98da035423 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -87,7 +87,6 @@ private: Pointfs m_shape; mutable BoundingBoxf3 m_bounding_box; mutable BoundingBoxf3 m_extended_bounding_box; - mutable bool m_extended_bounding_box_dirty; Polygon m_polygon; GeometryBuffer m_triangles; GeometryBuffer m_gridlines; diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 2ca11073be..c0613badd1 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -555,6 +555,7 @@ public: const std::string& get_filename() const { return m_filename; } const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box; } + const BoundingBoxf3& get_transformed_bounding_box() const { return m_volume.transformed_bounding_box(); } void reset(); diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 1fc2f6be36..f6cefc8010 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -169,6 +169,7 @@ void Camera::debug_render() const Vec3f up = get_dir_up().cast(); float nearZ = (float)m_frustrum_zs.first; float farZ = (float)m_frustrum_zs.second; + float deltaZ = farZ - nearZ; ImGui::InputText("Type", const_cast(type.data()), type.length(), ImGuiInputTextFlags_ReadOnly); ImGui::Separator(); @@ -181,6 +182,7 @@ void Camera::debug_render() const ImGui::Separator(); ImGui::InputFloat("Near Z", &nearZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat("Far Z", &farZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat("Delta Z", &deltaZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); imgui.end(); } #endif // ENABLE_CAMERA_STATISTICS @@ -230,7 +232,12 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo // ensure min size if (ret.second - ret.first < FrustrumMinZSize) - ret.second = ret.first + FrustrumMinZSize; + { + double mid_z = 0.5 * (ret.first + ret.second); + double half_size = 0.5 * FrustrumMinZSize; + ret.first = mid_z - half_size; + ret.second = mid_z + half_size; + } assert(ret.first > 0.0); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index faee2727db..fd77b46096 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1590,8 +1590,8 @@ void GLCanvas3D::render() if (m_camera.requires_zoom_to_bed) { zoom_to_bed(); - const Size& cnv_size = get_canvas_size(); - _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); +// const Size& cnv_size = get_canvas_size(); +// _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); m_camera.requires_zoom_to_bed = false; } From f8c5570155e9a36a28c2147a95c1404d1c7a9f75 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 14 Jun 2019 15:47:40 +0200 Subject: [PATCH 124/627] Removed unnecessary copy / move constructors / assignment operators. --- src/PrusaSlicer_app_msvc.cpp | 1 - src/admesh/stl.h | 8 -------- src/libslic3r/TriangleMesh.hpp | 4 ---- 3 files changed, 13 deletions(-) diff --git a/src/PrusaSlicer_app_msvc.cpp b/src/PrusaSlicer_app_msvc.cpp index ee8cdf6968..95dd4fb075 100644 --- a/src/PrusaSlicer_app_msvc.cpp +++ b/src/PrusaSlicer_app_msvc.cpp @@ -8,7 +8,6 @@ #include #ifdef SLIC3R_GUI -//Turn on high power graphics for NVidia cards on laptops (with built in graphics cards + Nvidia cards) extern "C" { // Let the NVIDIA and AMD know we want to use their graphics card diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 87210b3c93..2ac6c7fd28 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -120,10 +120,6 @@ struct stl_stats { struct stl_file { stl_file() {} - stl_file(const stl_file &rhs) : facet_start(rhs.facet_start), neighbors_start(rhs.neighbors_start), stats(rhs.stats) {} - stl_file(stl_file &&rhs) : facet_start(std::move(rhs.facet_start)), neighbors_start(std::move(rhs.neighbors_start)), stats(rhs.stats) {} - stl_file& operator=(const stl_file &rhs) { this->facet_start = rhs.facet_start; this->neighbors_start = rhs.neighbors_start; this->stats = rhs.stats; return *this; } - stl_file& operator=(stl_file &&rhs) { this->facet_start = std::move(rhs.facet_start); this->neighbors_start = std::move(rhs.neighbors_start); this->stats = rhs.stats; return *this; } void clear() { this->facet_start.clear(); @@ -140,10 +136,6 @@ struct stl_file { struct indexed_triangle_set { indexed_triangle_set() {} - indexed_triangle_set(const indexed_triangle_set &rhs) : indices(rhs.indices), vertices(rhs.vertices) {} - indexed_triangle_set(indexed_triangle_set &&rhs) : indices(std::move(rhs.indices)), vertices(std::move(rhs.vertices)) {} - indexed_triangle_set& operator=(const indexed_triangle_set &rhs) { this->indices = rhs.indices; this->vertices = rhs.vertices; return *this; } - indexed_triangle_set& operator=(indexed_triangle_set &&rhs) { this->indices = std::move(rhs.indices); this->vertices = std::move(rhs.vertices); return *this; } void clear() { indices.clear(); vertices.clear(); } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 54c6dc5d00..054a98935a 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -23,10 +23,6 @@ class TriangleMesh public: TriangleMesh() : repaired(false) {} TriangleMesh(const Pointf3s &points, const std::vector &facets); - TriangleMesh(const TriangleMesh& rhs) : stl(rhs.stl), its(rhs.its), repaired(rhs.repaired) {} - TriangleMesh(TriangleMesh&& rhs) : stl(std::move(rhs.stl)), its(std::move(rhs.its)), repaired(rhs.repaired) {} - TriangleMesh& operator=(const TriangleMesh& rhs) { this->stl = rhs.stl; this->its = rhs.its; this->repaired = rhs.repaired; return *this; } - TriangleMesh& operator=(TriangleMesh &&rhs) { this->stl = std::move(rhs.stl); this->its = std::move(rhs.its); this->repaired = rhs.repaired; return *this; } void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; } bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); } bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); } From c5037540e97be3585839736d5070420a166697a7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 14 Jun 2019 18:10:16 +0200 Subject: [PATCH 125/627] Warning hunt session. --- src/libslic3r/SLA/SLABasePool.cpp | 26 +++--- src/libslic3r/SLA/SLABoilerPlate.hpp | 4 +- src/libslic3r/SLA/SLASupportTree.cpp | 4 +- src/libslic3r/SLAPrint.cpp | 125 ++++++++++++++++----------- src/libslic3r/SLAPrint.hpp | 40 +++++---- src/libslic3r/libslic3r.h | 13 +++ 6 files changed, 126 insertions(+), 86 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 171d2b8d03..553920f536 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -53,7 +53,7 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, // Shorthand for the vertex arrays auto& upoints = upper.points, &lpoints = lower.points; - auto& rpts = ret.points; auto& rfaces = ret.indices; + auto& rpts = ret.points; auto& ind = ret.indices; // If the Z levels are flipped, or the offset difference is negative, we // will interpret that as the triangles normals should be inverted. @@ -61,7 +61,7 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, // Copy the points into the mesh, convert them from 2D to 3D rpts.reserve(upoints.size() + lpoints.size()); - rfaces.reserve(2*upoints.size() + 2*lpoints.size()); + ind.reserve(2*upoints.size() + 2*lpoints.size()); const double sf = SCALING_FACTOR; for(auto& p : upoints) rpts.emplace_back(p.x()*sf, p.y()*sf, upper_z_mm); for(auto& p : lpoints) rpts.emplace_back(p.x()*sf, p.y()*sf, lower_z_mm); @@ -121,9 +121,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, case Proceed::UPPER: if(!ustarted || uidx != uendidx) { // there are vertices remaining // Get the 3D vertices in order - const Vec3d& p_up1 = rpts[size_t(uidx)]; - const Vec3d& p_low = rpts[size_t(lidx)]; - const Vec3d& p_up2 = rpts[size_t(unextidx)]; + const Vec3d& p_up1 = rpts[uidx]; + const Vec3d& p_low = rpts[lidx]; + const Vec3d& p_up2 = rpts[unextidx]; // Calculate fitness: the average of the two connecting edges double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); @@ -133,8 +133,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, if(current_fit > prev_fit) { // fit is worse than previously proceed = Proceed::LOWER; } else { // good to go, create the triangle - inverted? rfaces.emplace_back(unextidx, lidx, uidx) : - rfaces.emplace_back(uidx, lidx, unextidx) ; + inverted + ? ind.emplace_back(int(unextidx), int(lidx), int(uidx)) + : ind.emplace_back(int(uidx), int(lidx), int(unextidx)); // Increment the iterators, rotate if necessary ++uidx; ++unextidx; @@ -150,9 +151,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, case Proceed::LOWER: // Mode with lower segment, upper vertex. Same structure: if(!lstarted || lidx != lendidx) { - const Vec3d& p_low1 = rpts[size_t(lidx)]; - const Vec3d& p_low2 = rpts[size_t(lnextidx)]; - const Vec3d& p_up = rpts[size_t(uidx)]; + const Vec3d& p_low1 = rpts[lidx]; + const Vec3d& p_low2 = rpts[lnextidx]; + const Vec3d& p_up = rpts[uidx]; double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); @@ -161,8 +162,9 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, if(current_fit > prev_fit) { proceed = Proceed::UPPER; } else { - inverted? rfaces.emplace_back(uidx, lnextidx, lidx) : - rfaces.emplace_back(lidx, lnextidx, uidx); + inverted + ? ind.emplace_back(int(uidx), int(lnextidx), int(lidx)) + : ind.emplace_back(int(lidx), int(lnextidx), int(uidx)); ++lidx; ++lnextidx; if(lnextidx == rpts.size()) lnextidx = offs; diff --git a/src/libslic3r/SLA/SLABoilerPlate.hpp b/src/libslic3r/SLA/SLABoilerPlate.hpp index 602121af9a..be900f5322 100644 --- a/src/libslic3r/SLA/SLABoilerPlate.hpp +++ b/src/libslic3r/SLA/SLABoilerPlate.hpp @@ -36,12 +36,10 @@ inline coord_t x(const Vec3crd& p) { return p(0); } inline coord_t y(const Vec3crd& p) { return p(1); } inline coord_t z(const Vec3crd& p) { return p(2); } -using Indices = std::vector; - /// Intermediate struct for a 3D mesh struct Contour3D { Pointf3s points; - Indices indices; + std::vector indices; void merge(const Contour3D& ctr) { auto s3 = coord_t(points.size()); diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index cb2001024d..ae033c62fc 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -236,13 +236,13 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d sp = {0,0,0}) // According to the slicing algorithms, we need to aid them with generating // a watertight body. So we create a triangle fan for the upper and lower // ending of the cylinder to close the geometry. - points.emplace_back(jp); size_t ci = points.size() - 1; + points.emplace_back(jp); int ci = int(points.size() - 1); for(int i = 0; i < steps - 1; ++i) indices.emplace_back(i + offs + 1, i + offs, ci); indices.emplace_back(offs, steps + offs - 1, ci); - points.emplace_back(endp); ci = points.size() - 1; + points.emplace_back(endp); ci = int(points.size() - 1); for(int i = 0; i < steps - 1; ++i) indices.emplace_back(ci, i, i + 1); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index f6a1c429e5..324008cf0d 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -28,14 +28,16 @@ namespace Slic3r { using SupportTreePtr = std::unique_ptr; -class SLAPrintObject::SupportData { +class SLAPrintObject::SupportData +{ public: - sla::EigenMesh3D emesh; // index-triangle representation - std::vector support_points; // all the support points (manual/auto) - SupportTreePtr support_tree_ptr; // the supports - SlicedSupports support_slices; // sliced supports + sla::EigenMesh3D emesh; // index-triangle representation + std::vector + support_points; // all the support points (manual/auto) + SupportTreePtr support_tree_ptr; // the supports + SlicedSupports support_slices; // sliced supports - inline SupportData(const TriangleMesh& trmesh): emesh(trmesh) {} + inline SupportData(const TriangleMesh &trmesh) : emesh(trmesh) {} }; namespace { @@ -666,11 +668,11 @@ void SLAPrint::process() double ilhd = m_material_config.initial_layer_height.getFloat(); auto ilh = float(ilhd); - auto ilhs = coord_t(ilhd / SCALING_FACTOR); + auto ilhs = scaled(ilhd); const size_t objcount = m_objects.size(); - const unsigned min_objstatus = 0; // where the per object operations start - const unsigned max_objstatus = 50; // where the per object operations end + static const unsigned min_objstatus = 0; // where the per object operations start + static const unsigned max_objstatus = 50; // where the per object operations end // the coefficient that multiplies the per object status values which // are set up for <0, 100>. They need to be scaled into the whole process @@ -687,31 +689,32 @@ void SLAPrint::process() // Slicing the model object. This method is oversimplified and needs to // be compared with the fff slicing algorithm for verification - auto slice_model = [this, ilhs, ilh, ilhd](SLAPrintObject& po) { + auto slice_model = [this, ilhs, ilh](SLAPrintObject& po) { const TriangleMesh& mesh = po.transformed_mesh(); // We need to prepare the slice index... double lhd = m_objects.front()->m_config.layer_height.getFloat(); float lh = float(lhd); - auto lhs = coord_t(lhd / SCALING_FACTOR); + auto lhs = scaled(lhd); - auto&& bb3d = mesh.bounding_box(); - double minZ = bb3d.min(Z) - po.get_elevation(); - double maxZ = bb3d.max(Z); + auto &&bb3d = mesh.bounding_box(); + double minZ = bb3d.min(Z) - po.get_elevation(); + double maxZ = bb3d.max(Z); + auto minZf = float(minZ); - auto minZs = coord_t(minZ / SCALING_FACTOR); - auto maxZs = coord_t(maxZ / SCALING_FACTOR); + auto minZs = scaled(minZ); + auto maxZs = scaled(maxZ); po.m_slice_index.clear(); size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs); po.m_slice_index.reserve(cap); - po.m_slice_index.emplace_back(minZs + ilhs, minZ + ilhd / 2.0, ilh); + po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh); - for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) - po.m_slice_index.emplace_back(h, h*SCALING_FACTOR - lhd / 2.0, lh); + for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) + po.m_slice_index.emplace_back(h, unscaled(h) - lh / 2.f, lh); // Just get the first record that is form the model: auto slindex_it = @@ -737,15 +740,15 @@ void SLAPrint::process() auto mit = slindex_it; double doffs = m_printer_config.absolute_correction.getFloat(); - coord_t clpr_offs = coord_t(doffs / SCALING_FACTOR); + coord_t clpr_offs = scaled(doffs); for(size_t id = 0; id < po.m_model_slices.size() && mit != po.m_slice_index.end(); id++) { // We apply the printer correction offset here. if(clpr_offs != 0) - po.m_model_slices[id] = - offset_ex(po.m_model_slices[id], clpr_offs); + po.m_model_slices[id] = + offset_ex(po.m_model_slices[id], float(clpr_offs)); mit->set_model_slice_idx(po, id); ++mit; } @@ -949,15 +952,15 @@ void SLAPrint::process() } double doffs = m_printer_config.absolute_correction.getFloat(); - coord_t clpr_offs = coord_t(doffs / SCALING_FACTOR); + coord_t clpr_offs = scaled(doffs); for(size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) { // We apply the printer correction offset here. if(clpr_offs != 0) - sd->support_slices[i] = - offset_ex(sd->support_slices[i], clpr_offs); + sd->support_slices[i] = + offset_ex(sd->support_slices[i], float(clpr_offs)); po.m_slice_index[i].set_support_slice_idx(po, i); } @@ -1063,8 +1066,8 @@ void SLAPrint::process() const int fade_layers_cnt = m_default_object_config.faded_layers.getInt();// 10 // [3;20] - const double width = m_printer_config.display_width.getFloat() / SCALING_FACTOR; - const double height = m_printer_config.display_height.getFloat() / SCALING_FACTOR; + const double width = scaled(m_printer_config.display_width.getFloat()); + const double height = scaled(m_printer_config.display_height.getFloat()); const double display_area = width*height; // get polygons for all instances in the object @@ -1170,13 +1173,20 @@ void SLAPrint::process() ClipperPolygons model_polygons; ClipperPolygons supports_polygons; - size_t c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) { - return a + sr.get_slice(soModel).size(); + size_t c = std::accumulate(layer.slices().begin(), + layer.slices().end(), + size_t(0), + [](size_t a, const SliceRecord &sr) { + return a + sr.get_slice(soModel) + .size(); }); model_polygons.reserve(c); - c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) { + c = std::accumulate(layer.slices().begin(), + layer.slices().end(), + size_t(0), + [](size_t a, const SliceRecord &sr) { return a + sr.get_slice(soModel).size(); }); @@ -1264,8 +1274,9 @@ void SLAPrint::process() // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i); tbb::parallel_for(0, m_printer_input.size(), printlayerfn); - m_print_statistics.support_used_material = supports_volume * SCALING_FACTOR * SCALING_FACTOR; - m_print_statistics.objects_used_material = models_volume * SCALING_FACTOR * SCALING_FACTOR; + auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; + m_print_statistics.support_used_material = supports_volume * SCALING2; + m_print_statistics.objects_used_material = models_volume * SCALING2; // Estimated printing time // A layers count o the highest object @@ -1281,7 +1292,7 @@ void SLAPrint::process() }; // Rasterizing the model objects, and their supports - auto rasterize = [this, max_objstatus]() { + auto rasterize = [this]() { if(canceled()) return; // collect all the keys @@ -1376,11 +1387,12 @@ void SLAPrint::process() tbb::parallel_for(0, lvlcnt, lvlfn); // Set statistics values to the printer - m_printer->set_statistics({(m_print_statistics.objects_used_material + m_print_statistics.support_used_material)/1000, - double(m_default_object_config.faded_layers.getInt()), - double(m_print_statistics.slow_layers_count), - double(m_print_statistics.fast_layers_count) - }); + m_printer->set_statistics( + {(m_print_statistics.objects_used_material + + m_print_statistics.support_used_material) / 1000, + double(m_default_object_config.faded_layers.getInt()), + double(m_print_statistics.slow_layers_count), + double(m_print_statistics.fast_layers_count)}); }; using slaposFn = std::function; @@ -1408,25 +1420,36 @@ void SLAPrint::process() // TODO: this loop could run in parallel but should not exhaust all the CPU // power available - // Calculate the support structures first before slicing the supports, so that the preview will get displayed ASAP for all objects. - std::vector step_ranges = { slaposObjectSlice, slaposSliceSupports, slaposCount }; - for (size_t idx_range = 0; idx_range + 1 < step_ranges.size(); ++ idx_range) { - for(SLAPrintObject * po : m_objects) { + // Calculate the support structures first before slicing the supports, + // so that the preview will get displayed ASAP for all objects. + std::vector step_ranges = {slaposObjectSlice, + slaposSliceSupports, + slaposCount}; - BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name; + for (size_t idx_range = 0; idx_range + 1 < step_ranges.size(); ++idx_range) { + for (SLAPrintObject *po : m_objects) { - for (int s = int(step_ranges[idx_range]); s < int(step_ranges[idx_range + 1]); ++s) { + BOOST_LOG_TRIVIAL(info) + << "Slicing object " << po->model_object()->name; + + for (int s = int(step_ranges[idx_range]); + s < int(step_ranges[idx_range + 1]); + ++s) { auto currentstep = static_cast(s); - // Cancellation checking. Each step will check for cancellation - // on its own and return earlier gracefully. Just after it returns - // execution gets to this point and throws the canceled signal. + // Cancellation checking. Each step will check for + // cancellation on its own and return earlier gracefully. + // Just after it returns execution gets to this point and + // throws the canceled signal. throw_if_canceled(); st += incr * ostepd; - if(po->m_stepmask[currentstep] && po->set_started(currentstep)) { - m_report_status(*this, st, OBJ_STEP_LABELS(currentstep)); + if (po->m_stepmask[currentstep] + && po->set_started(currentstep)) { + m_report_status(*this, + st, + OBJ_STEP_LABELS(currentstep)); pobj_program[currentstep](*po); throw_if_canceled(); po->set_done(currentstep); @@ -1786,8 +1809,8 @@ std::vector SLAPrintObject::transformed_support_points() cons ret.reserve(spts.size()); for(sla::SupportPoint& sp : spts) { - Vec3d transformed_pos = trafo() * Vec3d(sp.pos(0), sp.pos(1), sp.pos(2)); - ret.emplace_back(transformed_pos(0), transformed_pos(1), transformed_pos(2), sp.head_front_radius, sp.is_new_island); + Vec3f transformed_pos = trafo().cast() * sp.pos; + ret.emplace_back(transformed_pos, sp.head_front_radius, sp.is_new_island); } return ret; diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index dea468e7a0..0c7d92ff2e 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -54,15 +54,15 @@ public: bool is_left_handed() const { return m_left_handed; } struct Instance { - Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} - bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } - // ID of the corresponding ModelInstance. - ModelID instance_id; - // Slic3r::Point objects in scaled G-code coordinates - Point shift; - // Rotation along the Z axis, in radians. - float rotation; - }; + Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} + bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } + // ID of the corresponding ModelInstance. + ModelID instance_id; + // Slic3r::Point objects in scaled G-code coordinates + Point shift; + // Rotation along the Z axis, in radians. + float rotation; + }; const std::vector& instances() const { return m_instances; } bool has_mesh(SLAPrintObjectStep step) const; @@ -142,15 +142,19 @@ public: }; private: - - template inline static T level(const SliceRecord& sr) { + template inline static T level(const SliceRecord &sr) + { static_assert(std::is_arithmetic::value, "Arithmetic only!"); - return std::is_integral::value ? T(sr.print_level()) : T(sr.slice_level()); + return std::is_integral::value ? T(sr.print_level()) + : T(sr.slice_level()); } - template inline static SliceRecord create_slice_record(T val) { + template inline static SliceRecord create_slice_record(T val) + { static_assert(std::is_arithmetic::value, "Arithmetic only!"); - return std::is_integral::value ? SliceRecord{ coord_t(val), 0.f, 0.f } : SliceRecord{ 0, float(val), 0.f }; + return std::is_integral::value + ? SliceRecord{coord_t(val), 0.f, 0.f} + : SliceRecord{0, float(val), 0.f}; } // This is a template method for searching the slice index either by @@ -241,11 +245,11 @@ protected: ~SLAPrintObject(); void config_apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->m_config.apply(other, ignore_nonexistent); } - void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) - { this->m_config.apply_only(other, keys, ignore_nonexistent); } + void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) + { this->m_config.apply_only(other, keys, ignore_nonexistent); } void set_trafo(const Transform3d& trafo, bool left_handed) { - m_transformed_rmesh.invalidate([this, &trafo, left_handed](){ m_trafo = trafo; m_left_handed = left_handed; }); + m_transformed_rmesh.invalidate([this, &trafo, left_handed](){ m_trafo = trafo; m_left_handed = left_handed; }); } template inline void set_instances(InstVec&& instances) { m_instances = std::forward(instances); } @@ -380,7 +384,7 @@ public: void set_task(const TaskParams ¶ms) override; void process() override; void finalize() override; - // Returns true if an object step is done on all objects and there's at least one object. + // Returns true if an object step is done on all objects and there's at least one object. bool is_step_done(SLAPrintObjectStep step) const; // Returns true if the last step was finished with success. bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 560d746962..e1d247894a 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -48,6 +48,19 @@ typedef double coordf_t; //FIXME Better to use an inline function with an explicit return type. //inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); } #define scale_(val) ((val) / SCALING_FACTOR) + +template inline constexpr coord_t scaled(Tf val) +{ + static_assert (std::is_floating_point::value, "Floating point only"); + return coord_t(val / Tf(SCALING_FACTOR)); +} + +template inline constexpr Tf unscaled(coord_t val) +{ + static_assert (std::is_floating_point::value, "Floating point only"); + return Tf(val * Tf(SCALING_FACTOR)); +} + #define SCALED_EPSILON scale_(EPSILON) #define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/" From 9ffd294f072c55e8afa04fc78e44354abf262544 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 17 Jun 2019 09:28:41 +0200 Subject: [PATCH 126/627] Fixed functions declaration template inline constexpr coord_t scaled(Tf val) and template inline constexpr Tf unscaled(coord_t val) to use constexpr on versions of Visual Studio which support it --- src/libslic3r/libslic3r.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index e1d247894a..c95c95c1ab 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -49,13 +49,21 @@ typedef double coordf_t; //inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); } #define scale_(val) ((val) / SCALING_FACTOR) +#if defined(_MSC_VER) && (_MSC_VER < 1910) +template inline coord_t scaled(Tf val) +#else template inline constexpr coord_t scaled(Tf val) +#endif // _MSC_VER { static_assert (std::is_floating_point::value, "Floating point only"); return coord_t(val / Tf(SCALING_FACTOR)); } +#if defined(_MSC_VER) && (_MSC_VER < 1910) +template inline Tf unscaled(coord_t val) +#else template inline constexpr Tf unscaled(coord_t val) +#endif // _MSC_VER { static_assert (std::is_floating_point::value, "Floating point only"); return Tf(val * Tf(SCALING_FACTOR)); From ce222517075c7da14af59f056fc8890b61e0987d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 17 Jun 2019 10:05:46 +0200 Subject: [PATCH 127/627] Warning hunt session followup --- src/libslic3r/SLA/SLABasePool.cpp | 85 +++++++--------------------- src/libslic3r/SLA/SLABoilerPlate.hpp | 5 -- src/libslic3r/libslic3r.h | 26 +++++---- src/slic3r/GUI/MainFrame.cpp | 3 +- 4 files changed, 37 insertions(+), 82 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 553920f536..3b199c4ebb 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -61,10 +61,11 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, // Copy the points into the mesh, convert them from 2D to 3D rpts.reserve(upoints.size() + lpoints.size()); - ind.reserve(2*upoints.size() + 2*lpoints.size()); - const double sf = SCALING_FACTOR; - for(auto& p : upoints) rpts.emplace_back(p.x()*sf, p.y()*sf, upper_z_mm); - for(auto& p : lpoints) rpts.emplace_back(p.x()*sf, p.y()*sf, lower_z_mm); + ind.reserve(2 * upoints.size() + 2 * lpoints.size()); + for (auto &p : upoints) + rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); + for (auto &p : lpoints) + rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); // Create pointing indices into vertex arrays. u-upper, l-lower size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; @@ -202,7 +203,7 @@ void offset(ExPolygon& sh, coord_t distance) { } ClipperOffset offs; - offs.ArcTolerance = 0.01*mm(1); + offs.ArcTolerance = 0.01*scaled(1.0); Paths result; offs.AddPath(ctour, jtRound, etClosedPolygon); offs.AddPaths(holes, jtRound, etClosedPolygon); @@ -305,16 +306,6 @@ ExPolygons unify(const ExPolygons& shapes) { return retv; } -/// Only a debug function to generate top and bottom plates from a 2D shape. -/// It is not used in the algorithm directly. -inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) { - auto lower = triangulate_expolygon_3d(poly); - auto upper = triangulate_expolygon_3d(poly, z_distance*SCALING_FACTOR, true); - Contour3D ret; - ret.merge(lower); ret.merge(upper); - return ret; -} - /// This method will create a rounded edge around a flat polygon in 3d space. /// 'base_plate' parameter is the target plate. /// 'radius' is the radius of the edges. @@ -360,7 +351,7 @@ Contour3D round_edges(const ExPolygon& base_plate, double x2 = xx*xx; double stepy = std::sqrt(r2 - x2); - offset(ob, s*mm(xx)); + offset(ob, s*scaled(xx)); wh = ceilheight_mm - radius_mm + stepy; Contour3D pwalls; @@ -384,7 +375,7 @@ Contour3D round_edges(const ExPolygon& base_plate, double xx = radius_mm - i*stepx; double x2 = xx*xx; double stepy = std::sqrt(r2 - x2); - offset(ob, s*mm(xx)); + offset(ob, s*scaled(xx)); wh = ceilheight_mm - radius_mm - stepy; Contour3D pwalls; @@ -404,41 +395,6 @@ Contour3D round_edges(const ExPolygon& base_plate, return curvedwalls; } -/// Generating the concave part of the 3D pool with the bottom plate and the -/// side walls. -Contour3D inner_bed(const ExPolygon& poly, - double depth_mm, - double begin_h_mm = 0) -{ - Contour3D bottom; - Pointf3s triangles = triangulate_expolygon_3d(poly, -depth_mm + begin_h_mm); - bottom.merge(triangles); - - coord_t depth = mm(depth_mm); - coord_t begin_h = mm(begin_h_mm); - - auto lines = poly.lines(); - - // Generate outer walls - auto fp = [](const Point& p, Point::coord_type z) { - return unscale(x(p), y(p), z); - }; - - for(auto& l : lines) { - auto s = coord_t(bottom.points.size()); - - bottom.points.emplace_back(fp(l.a, -depth + begin_h)); - bottom.points.emplace_back(fp(l.b, -depth + begin_h)); - bottom.points.emplace_back(fp(l.a, begin_h)); - bottom.points.emplace_back(fp(l.b, begin_h)); - - bottom.indices.emplace_back(s + 3, s + 1, s); - bottom.indices.emplace_back(s + 2, s + 3, s); - } - - return bottom; -} - inline Point centroid(Points& pp) { Point c; switch(pp.size()) { @@ -520,7 +476,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, double dx = x(c) - x(cc), dy = y(c) - y(cc); double l = std::sqrt(dx * dx + dy * dy); double nx = dx / l, ny = dy / l; - double max_dist = mm(max_dist_mm); + double max_dist = scaled(max_dist_mm); ExPolygon& expo = punion[idx++]; BoundingBox querybb(expo); @@ -536,10 +492,10 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, ctour.reserve(3); ctour.emplace_back(cc); - Point d(coord_t(mm(1)*nx), coord_t(mm(1)*ny)); + Point d(coord_t(scaled(1.)*nx), coord_t(scaled(1.)*ny)); ctour.emplace_back(c + Point( -y(d), x(d) )); ctour.emplace_back(c + Point( y(d), -x(d) )); - offset(r, mm(1)); + offset(r, scaled(1.)); return r; }); @@ -571,15 +527,16 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, // Now we have to unify all slice layers which can be an expensive operation // so we will try to simplify the polygons ExPolygons tmp; tmp.reserve(count); - for(ExPolygons& o : out) for(ExPolygon& e : o) { - auto&& exss = e.simplify(0.1/SCALING_FACTOR); - for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); - } + for(ExPolygons& o : out) + for(ExPolygon& e : o) { + auto&& exss = e.simplify(scaled(0.1)); + for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); + } ExPolygons utmp = unify(tmp); for(auto& o : utmp) { - auto&& smp = o.simplify(0.1/SCALING_FACTOR); + auto&& smp = o.simplify(scaled(0.1)); output.insert(output.end(), smp.begin(), smp.end()); } } @@ -609,11 +566,11 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, const double bottom_offs = (thickness + wingheight) / std::tan(slope); // scaled values - const coord_t s_thickness = mm(thickness); - const coord_t s_eradius = mm(cfg.edge_radius_mm); + const coord_t s_thickness = scaled(thickness); + const coord_t s_eradius = scaled(cfg.edge_radius_mm); const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness); - const coord_t s_wingdist = mm(wingdist); - const coord_t s_bottom_offs = mm(bottom_offs); + const coord_t s_wingdist = scaled(wingdist); + const coord_t s_bottom_offs = scaled(bottom_offs); auto& thrcl = cfg.throw_on_cancel; diff --git a/src/libslic3r/SLA/SLABoilerPlate.hpp b/src/libslic3r/SLA/SLABoilerPlate.hpp index be900f5322..86e90f3b7f 100644 --- a/src/libslic3r/SLA/SLABoilerPlate.hpp +++ b/src/libslic3r/SLA/SLABoilerPlate.hpp @@ -11,11 +11,6 @@ namespace Slic3r { namespace sla { -using coord_t = Point::coord_type; - -/// get the scaled clipper units for a millimeter value -inline coord_t mm(double v) { return coord_t(v/SCALING_FACTOR); } - /// Get x and y coordinates (because we are eigenizing...) inline coord_t x(const Point& p) { return p(0); } inline coord_t y(const Point& p) { return p(1); } diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index c95c95c1ab..8cafae17cf 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -49,29 +49,31 @@ typedef double coordf_t; //inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); } #define scale_(val) ((val) / SCALING_FACTOR) -#if defined(_MSC_VER) && (_MSC_VER < 1910) -template inline coord_t scaled(Tf val) +#define SCALED_EPSILON scale_(EPSILON) + +#define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/" + +#if defined(_MSC_VER) && _MSC_VER < 1900 +# define SLIC3R_CONSTEXPR +# define SLIC3R_NOEXCEPT #else -template inline constexpr coord_t scaled(Tf val) -#endif // _MSC_VER +#define SLIC3R_CONSTEXPR constexpr +#define SLIC3R_NOEXCEPT noexcept +#endif + +template inline SLIC3R_CONSTEXPR coord_t scaled(Tf val) { static_assert (std::is_floating_point::value, "Floating point only"); return coord_t(val / Tf(SCALING_FACTOR)); } -#if defined(_MSC_VER) && (_MSC_VER < 1910) -template inline Tf unscaled(coord_t val) -#else -template inline constexpr Tf unscaled(coord_t val) -#endif // _MSC_VER +template inline SLIC3R_CONSTEXPR Tf unscaled(coord_t val) { static_assert (std::is_floating_point::value, "Floating point only"); return Tf(val * Tf(SCALING_FACTOR)); } -#define SCALED_EPSILON scale_(EPSILON) - -#define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/" +inline SLIC3R_CONSTEXPR float unscaledf(coord_t val) { return unscaled(val); } inline std::string debug_out_path(const char *name, ...) { diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 6091f10a14..7e5b3ec05a 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -976,7 +976,8 @@ void MainFrame::load_config(const DynamicPrintConfig& config) if (! boost::algorithm::ends_with(opt_key, "_settings_id")) tab->get_config()->option(opt_key)->set(config.option(opt_key)); } - wxGetApp().load_current_presets(); + + wxGetApp().load_current_presets(); #endif } From 05e6dbbe4be46295533a36672718c5eef57847d6 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 17 Jun 2019 10:16:07 +0200 Subject: [PATCH 128/627] Wipe tower - refactoring (removed the abstract WipeTower class) - abstract class WipeTower and its descendant WipeTowerPrusaMM were merged into a single (non-abstract) WipeTower class - all uses of WipeTower::xy struct were replaced by Eigen Vec2f (it is no longer necessary to be independent on libraries that PrusaSlicer uses) - the WipeTowerPrusaMM.hpp/.cpp will be renamed in the next commit (hopefully it will retain its git history that way) --- src/libslic3r/GCode.cpp | 48 ++-- src/libslic3r/GCode.hpp | 4 +- src/libslic3r/GCode/PrintExtents.cpp | 8 +- src/libslic3r/GCode/WipeTower.hpp | 174 ------------- src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 304 ++++++++++++----------- src/libslic3r/GCode/WipeTowerPrusaMM.hpp | 143 +++++++---- src/libslic3r/Print.cpp | 4 +- src/slic3r/GUI/GLCanvas3D.cpp | 14 +- 8 files changed, 293 insertions(+), 406 deletions(-) delete mode 100644 src/libslic3r/GCode/WipeTower.hpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index a606f56662..dadf9f26e1 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -4,7 +4,7 @@ #include "EdgeGrid.hpp" #include "Geometry.hpp" #include "GCode/PrintExtents.hpp" -#include "GCode/WipeTowerPrusaMM.hpp" +#include "GCode/WipeTower.hpp" #include "Utils.hpp" #include @@ -162,9 +162,9 @@ std::string Wipe::wipe(GCode &gcodegen, bool toolchange) return gcode; } -static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const WipeTower::xy &wipe_tower_pt) +static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2f &wipe_tower_pt) { - return Point(scale_(wipe_tower_pt.x - gcodegen.origin()(0)), scale_(wipe_tower_pt.y - gcodegen.origin()(1))); + return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1))); } std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const @@ -174,13 +174,13 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T // Toolchangeresult.gcode assumes the wipe tower corner is at the origin // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position float alpha = m_wipe_tower_rotation/180.f * float(M_PI); - WipeTower::xy start_pos = tcr.start_pos; - WipeTower::xy end_pos = tcr.end_pos; + Vec2f start_pos = tcr.start_pos; + Vec2f end_pos = tcr.end_pos; if (!tcr.priming) { - start_pos.rotate(alpha); - start_pos.translate(m_wipe_tower_pos); - end_pos.rotate(alpha); - end_pos.translate(m_wipe_tower_pos); + start_pos = Eigen::Rotation2Df(alpha) * start_pos; + start_pos += m_wipe_tower_pos; + end_pos = Eigen::Rotation2Df(alpha) * end_pos; + end_pos += m_wipe_tower_pos; } std::string tcr_rotated_gcode = tcr.priming ? tcr.gcode : rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha); @@ -264,7 +264,7 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T // A phony move to the end position at the wipe tower. - gcodegen.writer().travel_to_xy(Vec2d(end_pos.x, end_pos.y)); + gcodegen.writer().travel_to_xy(end_pos.cast()); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); // Prepare a future wipe. @@ -274,8 +274,8 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos)); // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - WipeTower::xy((std::abs(m_left - end_pos.x) < std::abs(m_right - end_pos.x)) ? m_right : m_left, - end_pos.y))); + Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left, + end_pos.y()))); } // Let the planner know we are traveling between objects. @@ -285,14 +285,14 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T // This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode // Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate) -std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const +std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const Vec2f& start_pos, const Vec2f& translation, float angle) const { std::istringstream gcode_str(gcode_original); std::string gcode_out; std::string line; - WipeTower::xy pos = start_pos; - WipeTower::xy transformed_pos; - WipeTower::xy old_pos(-1000.1f, -1000.1f); + Vec2f pos = start_pos; + Vec2f transformed_pos; + Vec2f old_pos(-1000.1f, -1000.1f); while (gcode_str) { std::getline(gcode_str, line); // we read the gcode line by line @@ -303,25 +303,25 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco char ch = 0; while (line_str >> ch) { if (ch == 'X') - line_str >> pos.x; + line_str >> pos.x(); else if (ch == 'Y') - line_str >> pos.y; + line_str >> pos.y(); else line_out << ch; } transformed_pos = pos; - transformed_pos.rotate(angle); - transformed_pos.translate(translation); + transformed_pos = Eigen::Rotation2Df(angle) * transformed_pos; + transformed_pos += translation; if (transformed_pos != old_pos) { line = line_out.str(); char buf[2048] = "G1"; - if (transformed_pos.x != old_pos.x) - sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x); - if (transformed_pos.y != old_pos.y) - sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y); + if (transformed_pos.x() != old_pos.x()) + sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x()); + if (transformed_pos.y() != old_pos.y()) + sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y()); line.replace(line.find("G1 "), 3, buf); old_pos = transformed_pos; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 639c83aa4b..4b81b42aac 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -108,12 +108,12 @@ private: std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const; // Postprocesses gcode: rotates and moves all G1 extrusions and returns result - std::string rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const; + std::string rotate_wipe_tower_moves(const std::string& gcode_original, const Vec2f& start_pos, const Vec2f& translation, float angle) const; // Left / right edges of the wipe tower, for the planning of wipe moves. const float m_left; const float m_right; - const WipeTower::xy m_wipe_tower_pos; + const Vec2f m_wipe_tower_pos; const float m_wipe_tower_rotation; // Reference to cached values at the Printer class. const std::vector &m_priming; diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index 4ff7c1cbd5..07a71a0ea1 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -149,8 +149,8 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_ const WipeTower::Extrusion &e = tcr.extrusions[i]; if (e.width > 0) { Vec2d delta = 0.5 * Vec2d(e.width, e.width); - Vec2d p1 = trafo * Vec2d((&e - 1)->pos.x, (&e - 1)->pos.y); - Vec2d p2 = trafo * Vec2d(e.pos.x, e.pos.y); + Vec2d p1 = trafo * (&e - 1)->pos.cast(); + Vec2d p2 = trafo * e.pos.cast(); bbox.merge(p1.cwiseMin(p2) - delta); bbox.merge(p1.cwiseMax(p2) + delta); } @@ -169,8 +169,8 @@ BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print) for (size_t i = 1; i < tcr.extrusions.size(); ++ i) { const WipeTower::Extrusion &e = tcr.extrusions[i]; if (e.width > 0) { - Vec2d p1((&e - 1)->pos.x, (&e - 1)->pos.y); - Vec2d p2(e.pos.x, e.pos.y); + const Vec2d& p1 = (&e - 1)->pos.cast(); + const Vec2d& p2 = e.pos.cast(); bbox.merge(p1); coordf_t radius = 0.5 * e.width; bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius); diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp deleted file mode 100644 index ba841fdd79..0000000000 --- a/src/libslic3r/GCode/WipeTower.hpp +++ /dev/null @@ -1,174 +0,0 @@ -#ifndef slic3r_WipeTower_hpp_ -#define slic3r_WipeTower_hpp_ - -#include -#include -#include -#include - -namespace Slic3r -{ - -// A pure virtual WipeTower definition. -class WipeTower -{ -public: - // Internal point class, to make the wipe tower independent from other slic3r modules. - // This is important for Prusa Research as we want to build the wipe tower post-processor independently from slic3r. - struct xy - { - xy(float x = 0.f, float y = 0.f) : x(x), y(y) {} - xy(const xy& pos,float xp,float yp) : x(pos.x+xp), y(pos.y+yp) {} - xy operator+(const xy &rhs) const { xy out(*this); out.x += rhs.x; out.y += rhs.y; return out; } - xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; } - xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; } - xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; } - bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; } - bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; } - - // Rotate the point around center of the wipe tower about given angle (in degrees) - xy rotate(float width, float depth, float angle) const { - xy out(0,0); - float temp_x = x - width / 2.f; - float temp_y = y - depth / 2.f; - angle *= float(M_PI/180.); - out.x += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f; - out.y += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f; - return out; - } - - // Rotate the point around origin about given angle in degrees - void rotate(float angle) { - float temp_x = x * cos(angle) - y * sin(angle); - y = x * sin(angle) + y * cos(angle); - x = temp_x; - } - - void translate(const xy& vect) { - x += vect.x; - y += vect.y; - } - - float x; - float y; - }; - - WipeTower() {} - virtual ~WipeTower() {} - - // Return the wipe tower position. - virtual const xy& position() const = 0; - - // Return the wipe tower width. - virtual float width() const = 0; - - // The wipe tower is finished, there should be no more tool changes or wipe tower prints. - virtual bool finished() const = 0; - - // Switch to a next layer. - virtual void set_layer( - // Print height of this layer. - float print_z, - // Layer height, used to calculate extrusion the rate. - float layer_height, - // Maximum number of tool changes on this layer or the layers below. - size_t max_tool_changes, - // Is this the first layer of the print? In that case print the brim first. - bool is_first_layer, - // Is this the last layer of the wipe tower? - bool is_last_layer) = 0; - - enum Purpose { - PURPOSE_MOVE_TO_TOWER, - PURPOSE_EXTRUDE, - PURPOSE_MOVE_TO_TOWER_AND_EXTRUDE, - }; - - // Extrusion path of the wipe tower, for 3D preview of the generated tool paths. - struct Extrusion - { - Extrusion(const xy &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {} - // End position of this extrusion. - xy pos; - // Width of a squished extrusion, corrected for the roundings of the squished extrusions. - // This is left zero if it is a travel move. - float width; - // Current extruder index. - unsigned int tool; - }; - - struct ToolChangeResult - { - // Print heigh of this tool change. - float print_z; - float layer_height; - // G-code section to be directly included into the output G-code. - std::string gcode; - // For path preview. - std::vector extrusions; - // Initial position, at which the wipe tower starts its action. - // At this position the extruder is loaded and there is no Z-hop applied. - xy start_pos; - // Last point, at which the normal G-code generator of Slic3r shall continue. - // At this position the extruder is loaded and there is no Z-hop applied. - xy end_pos; - // Time elapsed over this tool change. - // This is useful not only for the print time estimation, but also for the control of layer cooling. - float elapsed_time; - - // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later) - bool priming; - - // Initial tool - int initial_tool; - - // New tool - int new_tool; - - // Sum the total length of the extrusion. - float total_extrusion_length_in_plane() { - float e_length = 0.f; - for (size_t i = 1; i < this->extrusions.size(); ++ i) { - const Extrusion &e = this->extrusions[i]; - if (e.width > 0) { - xy v = e.pos - (&e - 1)->pos; - e_length += sqrt(v.x*v.x+v.y*v.y); - } - } - return e_length; - } - }; - - // Returns gcode to prime the nozzles at the front edge of the print bed. - virtual std::vector prime( - // print_z of the first layer. - float first_layer_height, - // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. - const std::vector &tools, - // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. - // If false, the last priming are will be large enough to wipe the last extruder sufficiently. - bool last_wipe_inside_wipe_tower) = 0; - - // Returns gcode for toolchange and the end position. - // if new_tool == -1, just unload the current filament over the wipe tower. - virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer) = 0; - - // Close the current wipe tower layer with a perimeter and possibly fill the unfilled space with a zig-zag. - // Call this method only if layer_finished() is false. - virtual ToolChangeResult finish_layer() = 0; - - // Is the current layer finished? A layer is finished if either the wipe tower is finished, or - // the wipe tower has been completely covered by the tool change extrusions, - // or the rest of the tower has been filled by a sparse infill with the finish_layer() method. - virtual bool layer_finished() const = 0; - - // Returns used filament length per extruder: - virtual std::vector get_used_filament() const = 0; - - // Returns total number of toolchanges: - virtual int get_number_of_toolchanges() const = 0; -}; - -}; // namespace Slic3r - -#endif /* slic3r_WipeTower_hpp_ */ diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 0c32bd50c5..e901ba296f 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -13,7 +13,7 @@ TODO LIST */ -#include "WipeTowerPrusaMM.hpp" +#include "WipeTower.hpp" #include #include @@ -35,12 +35,23 @@ TODO LIST namespace Slic3r { -namespace PrusaMultiMaterial { +// Rotate the point around center of the wipe tower about given angle (in degrees) +static Vec2f rotate(const Vec2f& pt, float width, float depth, float angle) +{ + Vec2f out(0,0); + float temp_x = pt(0) - width / 2.f; + float temp_y = pt(1) - depth / 2.f; + angle *= float(M_PI/180.); + out.x() += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f; + out.y() += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f; + return out; +} -class Writer + +class WipeTowerWriter { public: - Writer(float layer_height, float line_width, GCodeFlavor flavor, const std::vector& filament_parameters) : + WipeTowerWriter(float layer_height, float line_width, GCodeFlavor flavor, const std::vector& filament_parameters) : m_current_pos(std::numeric_limits::max(), std::numeric_limits::max()), m_current_z(0.f), m_current_feedrate(0.f), @@ -61,7 +72,7 @@ public: change_analyzer_line_width(line_width); } - Writer& change_analyzer_line_width(float line_width) { + WipeTowerWriter& change_analyzer_line_width(float line_width) { // adds tag for analyzer: char buf[64]; sprintf(buf, ";%s%f\n", GCodeAnalyzer::Width_Tag.c_str(), line_width); @@ -69,7 +80,7 @@ public: return *this; } - Writer& change_analyzer_mm3_per_mm(float len, float e) { + WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) { static const float area = M_PI * 1.75f * 1.75f / 4.f; float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); // adds tag for analyzer: @@ -79,25 +90,25 @@ public: return *this; } - Writer& set_initial_position(const WipeTower::xy &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { + WipeTowerWriter& set_initial_position(const Vec2f &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { m_wipe_tower_width = width; m_wipe_tower_depth = depth; m_internal_angle = internal_angle; - m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); + m_start_pos = rotate(pos + Vec2f(0.f,m_y_shift), m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); m_current_pos = pos; return *this; } - Writer& set_initial_tool(const unsigned int tool) { m_current_tool = tool; return *this; } + WipeTowerWriter& set_initial_tool(const unsigned int tool) { m_current_tool = tool; return *this; } - Writer& set_z(float z) + WipeTowerWriter& set_z(float z) { m_current_z = z; return *this; } - Writer& set_extrusion_flow(float flow) + WipeTowerWriter& set_extrusion_flow(float flow) { m_extrusion_flow = flow; return *this; } - Writer& set_y_shift(float shift) { - m_current_pos.y -= shift-m_y_shift; + WipeTowerWriter& set_y_shift(float shift) { + m_current_pos.y() -= shift-m_y_shift; m_y_shift = shift; return (*this); } @@ -105,10 +116,10 @@ public: // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various // filament loading and cooling moves from normal extrusion moves. Therefore the writer // is asked to suppres output of some lines, which look like extrusions. - Writer& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; } - Writer& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; } + WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; } + WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; } - Writer& feedrate(float f) + WipeTowerWriter& feedrate(float f) { if (f != m_current_feedrate) m_gcode += "G1" + set_format_F(f) + "\n"; @@ -117,30 +128,30 @@ public: const std::string& gcode() const { return m_gcode; } const std::vector& extrusions() const { return m_extrusions; } - float x() const { return m_current_pos.x; } - float y() const { return m_current_pos.y; } - const WipeTower::xy& pos() const { return m_current_pos; } - const WipeTower::xy start_pos_rotated() const { return m_start_pos; } - const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); } + float x() const { return m_current_pos.x(); } + float y() const { return m_current_pos.y(); } + const Vec2f& pos() const { return m_current_pos; } + const Vec2f start_pos_rotated() const { return m_start_pos; } + const Vec2f pos_rotated() const { return rotate(m_current_pos + Vec2f(0.f, m_y_shift), m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); } float elapsed_time() const { return m_elapsed_time; } float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; } // Extrude with an explicitely provided amount of extrusion. - Writer& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) + WipeTowerWriter& extrude_explicit(float x, float y, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) { - if (x == m_current_pos.x && y == m_current_pos.y && e == 0.f && (f == 0.f || f == m_current_feedrate)) + if (x == m_current_pos.x() && y == m_current_pos.y() && e == 0.f && (f == 0.f || f == m_current_feedrate)) // Neither extrusion nor a travel move. return *this; - float dx = x - m_current_pos.x; - float dy = y - m_current_pos.y; + float dx = x - m_current_pos.x(); + float dy = y - m_current_pos.y(); double len = sqrt(dx*dx+dy*dy); if (record_length) m_used_filament_length += e; // Now do the "internal rotation" with respect to the wipe tower center - WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are - WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go + Vec2f rotated_current_pos(rotate(m_current_pos + Vec2f(0.f,m_y_shift), m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are + Vec2f rot(rotate(Vec2f(x,y+m_y_shift), m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go if (! m_preview_suppressed && e > 0.f && len > 0.) { change_analyzer_mm3_per_mm(len, e); @@ -151,15 +162,15 @@ public: width += m_layer_height * float(1. - M_PI / 4.); if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos) m_extrusions.emplace_back(WipeTower::Extrusion(rotated_current_pos, 0, m_current_tool)); - m_extrusions.emplace_back(WipeTower::Extrusion(WipeTower::xy(rot.x, rot.y), width, m_current_tool)); + m_extrusions.emplace_back(WipeTower::Extrusion(rot, width, m_current_tool)); } m_gcode += "G1"; - if (std::abs(rot.x - rotated_current_pos.x) > EPSILON) - m_gcode += set_format_X(rot.x); + if (std::abs(rot.x() - rotated_current_pos.x()) > EPSILON) + m_gcode += set_format_X(rot.x()); - if (std::abs(rot.y - rotated_current_pos.y) > EPSILON) - m_gcode += set_format_Y(rot.y); + if (std::abs(rot.y() - rotated_current_pos.y()) > EPSILON) + m_gcode += set_format_Y(rot.y()); if (e != 0.f) @@ -173,8 +184,8 @@ public: m_gcode += set_format_F(f); } - m_current_pos.x = x; - m_current_pos.y = y; + m_current_pos.x() = x; + m_current_pos.y() = y; // Update the elapsed time with a rough estimate. m_elapsed_time += ((len == 0) ? std::abs(e) : len) / m_current_feedrate * 60.f; @@ -182,42 +193,42 @@ public: return *this; } - Writer& extrude_explicit(const WipeTower::xy &dest, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) - { return extrude_explicit(dest.x, dest.y, e, f, record_length); } + WipeTowerWriter& extrude_explicit(const Vec2f &dest, float e, float f = 0.f, bool record_length = false, bool limit_volumetric_flow = true) + { return extrude_explicit(dest.x(), dest.y(), e, f, record_length); } // Travel to a new XY position. f=0 means use the current value. - Writer& travel(float x, float y, float f = 0.f) + WipeTowerWriter& travel(float x, float y, float f = 0.f) { return extrude_explicit(x, y, 0.f, f); } - Writer& travel(const WipeTower::xy &dest, float f = 0.f) - { return extrude_explicit(dest.x, dest.y, 0.f, f); } + WipeTowerWriter& travel(const Vec2f &dest, float f = 0.f) + { return extrude_explicit(dest.x(), dest.y(), 0.f, f); } // Extrude a line from current position to x, y with the extrusion amount given by m_extrusion_flow. - Writer& extrude(float x, float y, float f = 0.f) + WipeTowerWriter& extrude(float x, float y, float f = 0.f) { - float dx = x - m_current_pos.x; - float dy = y - m_current_pos.y; + float dx = x - m_current_pos.x(); + float dy = y - m_current_pos.y(); return extrude_explicit(x, y, sqrt(dx*dx+dy*dy) * m_extrusion_flow, f, true); } - Writer& extrude(const WipeTower::xy &dest, const float f = 0.f) - { return extrude(dest.x, dest.y, f); } + WipeTowerWriter& extrude(const Vec2f &dest, const float f = 0.f) + { return extrude(dest.x(), dest.y(), f); } - Writer& rectangle(const WipeTower::xy& ld,float width,float height,const float f = 0.f) + WipeTowerWriter& rectangle(const Vec2f& ld,float width,float height,const float f = 0.f) { - WipeTower::xy corners[4]; + Vec2f corners[4]; corners[0] = ld; - corners[1] = WipeTower::xy(ld,width,0.f); - corners[2] = WipeTower::xy(ld,width,height); - corners[3] = WipeTower::xy(ld,0.f,height); + corners[1] = ld + Vec2f(width,0.f); + corners[2] = ld + Vec2f(width,height); + corners[3] = ld + Vec2f(0.f,height); int index_of_closest = 0; - if (x()-ld.x > ld.x+width-x()) // closer to the right + if (x()-ld.x() > ld.x()+width-x()) // closer to the right index_of_closest = 1; - if (y()-ld.y > ld.y+height-y()) // closer to the top + if (y()-ld.y() > ld.y()+height-y()) // closer to the top index_of_closest = (index_of_closest==0 ? 3 : 2); - travel(corners[index_of_closest].x, y()); // travel to the closest corner - travel(x(),corners[index_of_closest].y); + travel(corners[index_of_closest].x(), y()); // travel to the closest corner + travel(x(),corners[index_of_closest].y()); int i = index_of_closest; do { @@ -228,7 +239,7 @@ public: return (*this); } - Writer& load(float e, float f = 0.f) + WipeTowerWriter& load(float e, float f = 0.f) { if (e == 0.f && (f == 0.f || f == m_current_feedrate)) return *this; @@ -244,14 +255,14 @@ public: // Derectract while moving in the X direction. // If |x| > 0, the feed rate relates to the x distance, // otherwise the feed rate relates to the e distance. - Writer& load_move_x(float x, float e, float f = 0.f) - { return extrude_explicit(x, m_current_pos.y, e, f); } + WipeTowerWriter& load_move_x(float x, float e, float f = 0.f) + { return extrude_explicit(x, m_current_pos.y(), e, f); } - Writer& retract(float e, float f = 0.f) + WipeTowerWriter& retract(float e, float f = 0.f) { return load(-e, f); } // Loads filament while also moving towards given points in x-axis (x feedrate is limited by cutting the distance short if necessary) - Writer& load_move_x_advanced(float farthest_x, float loading_dist, float loading_speed, float max_x_speed = 50.f) + WipeTowerWriter& load_move_x_advanced(float farthest_x, float loading_dist, float loading_speed, float max_x_speed = 50.f) { float time = std::abs(loading_dist / loading_speed); float x_speed = std::min(max_x_speed, std::abs(farthest_x - x()) / time); @@ -262,7 +273,7 @@ public: } // Elevate the extruder head above the current print_z position. - Writer& z_hop(float hop, float f = 0.f) + WipeTowerWriter& z_hop(float hop, float f = 0.f) { m_gcode += std::string("G1") + set_format_Z(m_current_z + hop); if (f != 0 && f != m_current_feedrate) @@ -272,29 +283,29 @@ public: } // Lower the extruder head back to the current print_z position. - Writer& z_hop_reset(float f = 0.f) + WipeTowerWriter& z_hop_reset(float f = 0.f) { return z_hop(0, f); } // Move to x1, +y_increment, // extrude quickly amount e to x2 with feed f. - Writer& ram(float x1, float x2, float dy, float e0, float e, float f) + WipeTowerWriter& ram(float x1, float x2, float dy, float e0, float e, float f) { - extrude_explicit(x1, m_current_pos.y + dy, e0, f, true, false); - extrude_explicit(x2, m_current_pos.y, e, 0.f, true, false); + extrude_explicit(x1, m_current_pos.y() + dy, e0, f, true, false); + extrude_explicit(x2, m_current_pos.y(), e, 0.f, true, false); return *this; } // Let the end of the pulled out filament cool down in the cooling tube // by moving up and down and moving the print head left / right // at the current Y position to spread the leaking material. - Writer& cool(float x1, float x2, float e1, float e2, float f) + WipeTowerWriter& cool(float x1, float x2, float e1, float e2, float f) { - extrude_explicit(x1, m_current_pos.y, e1, f); - extrude_explicit(x2, m_current_pos.y, e2); + extrude_explicit(x1, m_current_pos.y(), e1, f); + extrude_explicit(x2, m_current_pos.y(), e2); return *this; } - Writer& set_tool(int tool) + WipeTowerWriter& set_tool(int tool) { char buf[64]; sprintf(buf, "T%d\n", tool); @@ -304,7 +315,7 @@ public: } // Set extruder temperature, don't wait by default. - Writer& set_extruder_temp(int temperature, bool wait = false) + WipeTowerWriter& set_extruder_temp(int temperature, bool wait = false) { char buf[128]; sprintf(buf, "M%d S%d\n", wait ? 109 : 104, temperature); @@ -313,7 +324,7 @@ public: }; // Wait for a period of time (seconds). - Writer& wait(float time) + WipeTowerWriter& wait(float time) { if (time==0) return *this; @@ -324,7 +335,7 @@ public: }; // Set speed factor override percentage. - Writer& speed_override(int speed) + WipeTowerWriter& speed_override(int speed) { char buf[128]; sprintf(buf, "M220 S%d\n", speed); @@ -333,21 +344,21 @@ public: }; // Let the firmware back up the active speed override value. - Writer& speed_override_backup() + WipeTowerWriter& speed_override_backup() { m_gcode += "M220 B\n"; return *this; }; // Let the firmware restore the active speed override value. - Writer& speed_override_restore() + WipeTowerWriter& speed_override_restore() { m_gcode += "M220 R\n"; return *this; }; // Set digital trimpot motor - Writer& set_extruder_trimpot(int current) + WipeTowerWriter& set_extruder_trimpot(int current) { char buf[128]; if (m_gcode_flavor == gcfRepRap) @@ -358,20 +369,20 @@ public: return *this; }; - Writer& flush_planner_queue() + WipeTowerWriter& flush_planner_queue() { m_gcode += "G4 S0\n"; return *this; } // Reset internal extruder counter. - Writer& reset_extruder() + WipeTowerWriter& reset_extruder() { m_gcode += "G92 E0\n"; return *this; } - Writer& comment_with_value(const char *comment, int value) + WipeTowerWriter& comment_with_value(const char *comment, int value) { char strvalue[64]; sprintf(strvalue, "%d", value); @@ -380,7 +391,7 @@ public: }; - Writer& set_fan(unsigned int speed) + WipeTowerWriter& set_fan(unsigned int speed) { if (speed == m_last_fan_speed) return *this; @@ -398,11 +409,11 @@ public: return *this; } - Writer& append(const char *text) { m_gcode += text; return *this; } + WipeTowerWriter& append(const char *text) { m_gcode += text; return *this; } private: - WipeTower::xy m_start_pos; - WipeTower::xy m_current_pos; + Vec2f m_start_pos; + Vec2f m_current_pos; float m_current_z; float m_current_feedrate; unsigned int m_current_tool; @@ -421,20 +432,20 @@ private: const float m_default_analyzer_line_width; float m_used_filament_length = 0.f; GCodeFlavor m_gcode_flavor; - const std::vector& m_filpar; + const std::vector& m_filpar; std::string set_format_X(float x) { char buf[64]; sprintf(buf, " X%.3f", x); - m_current_pos.x = x; + m_current_pos.x() = x; return buf; } std::string set_format_Y(float y) { char buf[64]; sprintf(buf, " Y%.3f", y); - m_current_pos.y = y; + m_current_pos.y() = y; return buf; } @@ -457,14 +468,13 @@ private: return buf; } - Writer& operator=(const Writer &rhs); -}; // class Writer + WipeTowerWriter& operator=(const WipeTowerWriter &rhs); +}; // class WipeTowerWriter -}; // namespace PrusaMultiMaterial // Returns gcode to prime the nozzles at the front edge of the print bed. -std::vector WipeTowerPrusaMM::prime( +std::vector WipeTower::prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. @@ -482,7 +492,7 @@ std::vector WipeTowerPrusaMM::prime( // box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area); const float prime_section_width = std::min(240.f / tools.size(), 60.f); - box_coordinates cleaning_box(xy(5.f, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); + box_coordinates cleaning_box(Vec2f(5.f, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); std::vector results; @@ -491,7 +501,7 @@ std::vector WipeTowerPrusaMM::prime( for (size_t idx_tool = 0; idx_tool < tools.size(); ++ idx_tool) { int old_tool = m_current_tool; - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool); @@ -503,7 +513,7 @@ std::vector WipeTowerPrusaMM::prime( .append(";--------------------\n") .speed_override_backup() .speed_override(100) - .set_initial_position(xy(0.f, 0.f)) // Always move to the starting position + .set_initial_position(Vec2f::Zero()) // Always move to the starting position .travel(cleaning_box.ld, 7200); if (m_set_extruder_trimpot) writer.set_extruder_trimpot(750); // Increase the extruder driver current to allow fast ramming. @@ -524,7 +534,7 @@ std::vector WipeTowerPrusaMM::prime( //writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200); toolchange_Wipe(writer, cleaning_box , 20.f); box_coordinates box = cleaning_box; - box.translate(0.f, writer.y() - cleaning_box.ld.y + m_perimeter_width); + box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width); toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[tools[idx_tool + 1]].first_layer_temperature); cleaning_box.translate(prime_section_width, 0.f); writer.travel(cleaning_box.ld, 7200); @@ -574,7 +584,7 @@ std::vector WipeTowerPrusaMM::prime( return results; } -WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, bool last_in_layer) +WipeTower::ToolChangeResult WipeTower::tool_change(unsigned int tool, bool last_in_layer) { if ( m_print_brim ) return toolchange_Brim(); @@ -602,12 +612,12 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo } box_coordinates cleaning_box( - xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f), + Vec2f(m_perimeter_width / 2.f, m_perimeter_width / 2.f), m_wipe_tower_width - m_perimeter_width, (tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width : m_wipe_tower_depth-m_perimeter_width)); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) @@ -623,7 +633,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo writer.speed_override_backup(); writer.speed_override(100); - xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed); + Vec2f initial_position = cleaning_box.ld + Vec2f(0.f, m_depth_traversed); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); // Increase the extruder driver current to allow fast ramming. @@ -647,9 +657,9 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo if (last_change_in_layer) {// draw perimeter line writer.set_y_shift(m_y_shift); if (m_peters_wipe_tower) - writer.rectangle(WipeTower::xy(0.f, 0.f),m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth); + writer.rectangle(Vec2f::Zero(), m_layer_info->depth + 3*m_perimeter_width, m_wipe_tower_depth); else { - writer.rectangle(WipeTower::xy(0.f, 0.f),m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); + writer.rectangle(Vec2f::Zero(), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); } @@ -684,27 +694,27 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo return result; } -WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset) +WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_offset) { int old_tool = m_current_tool; const box_coordinates wipeTower_box( - WipeTower::xy(0.f, 0.f), + Vec2f::Zero(), m_wipe_tower_width, m_wipe_tower_depth); - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow * 1.1f) .set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop. .set_initial_tool(m_current_tool) .append(";-------------------------------------\n" "; CP WIPE TOWER FIRST LAYER BRIM START\n"); - xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0); + Vec2f initial_position = wipeTower_box.lu - Vec2f(m_perimeter_width * 6.f, 0); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); - writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower. - 1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400); + writer.extrude_explicit(wipeTower_box.ld - Vec2f(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower. + 1.5f * m_extrusion_flow * (wipeTower_box.lu.y() - wipeTower_box.ld.y()), 2400); // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded. // Extrude 4 rounds of a brim around the future wipe tower. @@ -745,14 +755,14 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. -void WipeTowerPrusaMM::toolchange_Unload( - PrusaMultiMaterial::Writer &writer, +void WipeTower::toolchange_Unload( + WipeTowerWriter &writer, const box_coordinates &cleaning_box, const std::string& current_material, const int new_temperature) { - float xl = cleaning_box.ld.x + 1.f * m_perimeter_width; - float xr = cleaning_box.rd.x - 1.f * m_perimeter_width; + float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; + float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; const float line_width = m_perimeter_width * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing; // spacing between lines in mm @@ -765,7 +775,7 @@ void WipeTowerPrusaMM::toolchange_Unload( float remaining = xr - xl ; // keeps track of distance to the next turnaround float e_done = 0; // measures E move done from each segment - writer.travel(xl, cleaning_box.ld.y + m_depth_traversed + y_step/2.f ); // move to starting position + writer.travel(xl, cleaning_box.ld.y() + m_depth_traversed + y_step/2.f ); // move to starting position // if the ending point of the ram would end up in mid air, align it with the end of the wipe tower: if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) { @@ -832,7 +842,7 @@ void WipeTowerPrusaMM::toolchange_Unload( e_done = 0; } } - WipeTower::xy end_of_ramming(writer.x(),writer.y()); + Vec2f end_of_ramming(writer.x(),writer.y()); writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier // Retraction: @@ -887,15 +897,15 @@ void WipeTowerPrusaMM::toolchange_Unload( // this is to align ramming and future wiping extrusions, so the future y-steps can be uniform from the start: // the perimeter_width will later be subtracted, it is there to not load while moving over just extruded material - writer.travel(end_of_ramming.x, end_of_ramming.y + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width, 2400.f); + writer.travel(end_of_ramming.x(), end_of_ramming.y() + (y_step/m_extra_spacing-m_perimeter_width) / 2.f + m_perimeter_width, 2400.f); writer.resume_preview() .flush_planner_queue(); } // Change the tool, set a speed override for soluble and flex materials. -void WipeTowerPrusaMM::toolchange_Change( - PrusaMultiMaterial::Writer &writer, +void WipeTower::toolchange_Change( + WipeTowerWriter &writer, const unsigned int new_tool, const std::string& new_material) { @@ -917,13 +927,13 @@ void WipeTowerPrusaMM::toolchange_Change( m_current_tool = new_tool; } -void WipeTowerPrusaMM::toolchange_Load( - PrusaMultiMaterial::Writer &writer, +void WipeTower::toolchange_Load( + WipeTowerWriter &writer, const box_coordinates &cleaning_box) { if (m_semm && (m_parking_pos_retraction != 0 || m_extra_loading_move != 0)) { - float xl = cleaning_box.ld.x + m_perimeter_width * 0.75f; - float xr = cleaning_box.rd.x - m_perimeter_width * 0.75f; + float xl = cleaning_box.ld.x() + m_perimeter_width * 0.75f; + float xr = cleaning_box.rd.x() - m_perimeter_width * 0.75f; float oldx = writer.x(); // the nozzle is in place to do the first wiping moves, we will remember the position // Load the filament while moving left / right, so the excess material will not create a blob at a single position. @@ -951,8 +961,8 @@ void WipeTowerPrusaMM::toolchange_Load( } // Wipe the newly loaded filament until the end of the assigned wipe area. -void WipeTowerPrusaMM::toolchange_Wipe( - PrusaMultiMaterial::Writer &writer, +void WipeTower::toolchange_Wipe( + WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_volume) { @@ -960,8 +970,8 @@ void WipeTowerPrusaMM::toolchange_Wipe( writer.set_extrusion_flow(m_extrusion_flow * (m_is_first_layer ? 1.18f : 1.f)) .append("; CP TOOLCHANGE WIPE\n"); float wipe_coeff = m_is_first_layer ? 0.5f : 1.f; - const float& xl = cleaning_box.ld.x; - const float& xr = cleaning_box.rd.x; + const float& xl = cleaning_box.ld.x(); + const float& xr = cleaning_box.rd.x(); // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least // the ordered volume, even if it means violating the box. This can later be removed and simply @@ -992,7 +1002,7 @@ void WipeTowerPrusaMM::toolchange_Wipe( else writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5*m_perimeter_width), writer.y(), wipe_speed * wipe_coeff); - if (writer.y()+EPSILON > cleaning_box.lu.y-0.5f*m_perimeter_width) + if (writer.y()+EPSILON > cleaning_box.lu.y()-0.5f*m_perimeter_width) break; // in case next line would not fit traversed_x -= writer.x(); @@ -1019,7 +1029,7 @@ void WipeTowerPrusaMM::toolchange_Wipe( -WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() +WipeTower::ToolChangeResult WipeTower::finish_layer() { // This should only be called if the layer is not finished yet. // Otherwise the caller would likely travel to the wipe tower in vain. @@ -1027,7 +1037,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() int old_tool = m_current_tool; - PrusaMultiMaterial::Writer writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); + WipeTowerWriter writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) @@ -1039,7 +1049,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() // Slow down on the 1st layer. float speed_factor = m_is_first_layer ? 0.5f : 1.f; float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); - box_coordinates fill_box(xy(m_perimeter_width, m_depth_traversed + m_perimeter_width), + box_coordinates fill_box(Vec2f(m_perimeter_width, m_depth_traversed + m_perimeter_width), m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); @@ -1053,44 +1063,44 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() else box.expand(-m_perimeter_width); } else i=2; // only draw the inner perimeter, outer has been already drawn by tool_change(...) - writer.rectangle(box.ld,box.rd.x-box.ld.x,box.ru.y-box.rd.y,2900*speed_factor); + writer.rectangle(box.ld, box.rd.x()-box.ld.x(), box.ru.y()-box.rd.y(), 2900*speed_factor); } // we are in one of the corners, travel to ld along the perimeter: - if (writer.x() > fill_box.ld.x+EPSILON) writer.travel(fill_box.ld.x,writer.y()); - if (writer.y() > fill_box.ld.y+EPSILON) writer.travel(writer.x(),fill_box.ld.y); + if (writer.x() > fill_box.ld.x()+EPSILON) writer.travel(fill_box.ld.x(),writer.y()); + if (writer.y() > fill_box.ld.y()+EPSILON) writer.travel(writer.x(),fill_box.ld.y()); if (m_is_first_layer && m_adhesion) { // Extrude a dense infill at the 1st layer to improve 1st layer adhesion of the wipe tower. box.expand(-m_perimeter_width/2.f); - int nsteps = int(floor((box.lu.y - box.ld.y) / (2*m_perimeter_width))); - float step = (box.lu.y - box.ld.y) / nsteps; - writer.travel(box.ld-xy(m_perimeter_width/2.f,m_perimeter_width/2.f)); + int nsteps = int(floor((box.lu.y() - box.ld.y()) / (2*m_perimeter_width))); + float step = (box.lu.y() - box.ld.y()) / nsteps; + writer.travel(box.ld - Vec2f(m_perimeter_width/2.f, m_perimeter_width/2.f)); if (nsteps >= 0) for (int i = 0; i < nsteps; ++i) { - writer.extrude(box.ld.x+m_perimeter_width/2.f, writer.y() + 0.5f * step); - writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y()); - writer.extrude(box.rd.x - m_perimeter_width / 2.f, writer.y() + 0.5f * step); - writer.extrude(box.ld.x + m_perimeter_width / 2.f, writer.y()); + writer.extrude(box.ld.x()+m_perimeter_width/2.f, writer.y() + 0.5f * step); + writer.extrude(box.rd.x() - m_perimeter_width / 2.f, writer.y()); + writer.extrude(box.rd.x() - m_perimeter_width / 2.f, writer.y() + 0.5f * step); + writer.extrude(box.ld.x() + m_perimeter_width / 2.f, writer.y()); } - writer.travel(box.rd.x-m_perimeter_width/2.f,writer.y()); // wipe the nozzle + writer.travel(box.rd.x()-m_perimeter_width/2.f,writer.y()); // wipe the nozzle } else { // Extrude a sparse infill to support the material to be printed above. - const float dy = (fill_box.lu.y - fill_box.ld.y - m_perimeter_width); - const float left = fill_box.lu.x+2*m_perimeter_width; - const float right = fill_box.ru.x - 2 * m_perimeter_width; + const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); + const float left = fill_box.lu.x() + 2*m_perimeter_width; + const float right = fill_box.ru.x() - 2 * m_perimeter_width; if (dy > m_perimeter_width) { // Extrude an inverse U at the left of the region. - writer.travel(fill_box.ld + xy(m_perimeter_width * 2, 0.f)) - .extrude(fill_box.lu + xy(m_perimeter_width * 2, 0.f), 2900 * speed_factor); + writer.travel(fill_box.ld + Vec2f(m_perimeter_width * 2, 0.f)) + .extrude(fill_box.lu + Vec2f(m_perimeter_width * 2, 0.f), 2900 * speed_factor); const int n = 1+(right-left)/(m_bridging); const float dx = (right-left)/n; for (int i=1;i<=n;++i) { float x=left+dx*i; writer.travel(x,writer.y()); - writer.extrude(x,i%2 ? fill_box.rd.y : fill_box.ru.y); + writer.extrude(x,i%2 ? fill_box.rd.y() : fill_box.ru.y()); } writer.travel(left,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower } @@ -1121,7 +1131,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer() } // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box -void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume) +void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume) { assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON); // refuses to add a layer below the last one @@ -1157,7 +1167,7 @@ void WipeTowerPrusaMM::plan_toolchange(float z_par, float layer_height_par, unsi -void WipeTowerPrusaMM::plan_tower() +void WipeTower::plan_tower() { // Calculate m_wipe_tower_depth (maximum depth for all the layers) and propagate depths downwards m_wipe_tower_depth = 0.f; @@ -1180,7 +1190,7 @@ void WipeTowerPrusaMM::plan_tower() } } -void WipeTowerPrusaMM::save_on_last_wipe() +void WipeTower::save_on_last_wipe() { for (m_layer_info=m_plan.begin();m_layer_infoz, m_layer_info->height, 0, m_layer_info->z == m_plan.front().z, m_layer_info->z == m_plan.back().z); @@ -1205,7 +1215,7 @@ void WipeTowerPrusaMM::save_on_last_wipe() // Processes vector m_plan and calls respective functions to generate G-code for the wipe tower // Resulting ToolChangeResults are appended into vector "result" -void WipeTowerPrusaMM::generate(std::vector> &result) +void WipeTower::generate(std::vector> &result) { if (m_plan.empty()) @@ -1251,7 +1261,7 @@ void WipeTowerPrusaMM::generate(std::vector #include @@ -7,30 +7,81 @@ #include #include -#include "WipeTower.hpp" -#include "PrintConfig.hpp" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { -namespace PrusaMultiMaterial { - class Writer; -}; +class WipeTowerWriter; -class WipeTowerPrusaMM : public WipeTower +class WipeTower { public: + struct Extrusion + { + Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {} + // End position of this extrusion. + Vec2f pos; + // Width of a squished extrusion, corrected for the roundings of the squished extrusions. + // This is left zero if it is a travel move. + float width; + // Current extruder index. + unsigned int tool; + }; + + struct ToolChangeResult + { + // Print heigh of this tool change. + float print_z; + float layer_height; + // G-code section to be directly included into the output G-code. + std::string gcode; + // For path preview. + std::vector extrusions; + // Initial position, at which the wipe tower starts its action. + // At this position the extruder is loaded and there is no Z-hop applied. + Vec2f start_pos; + // Last point, at which the normal G-code generator of Slic3r shall continue. + // At this position the extruder is loaded and there is no Z-hop applied. + Vec2f end_pos; + // Time elapsed over this tool change. + // This is useful not only for the print time estimation, but also for the control of layer cooling. + float elapsed_time; + + // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later) + bool priming; + + // Initial tool + int initial_tool; + + // New tool + int new_tool; + + // Sum the total length of the extrusion. + float total_extrusion_length_in_plane() { + float e_length = 0.f; + for (size_t i = 1; i < this->extrusions.size(); ++ i) { + const Extrusion &e = this->extrusions[i]; + if (e.width > 0) { + Vec2f v = e.pos - (&e - 1)->pos; + e_length += v.norm(); + } + } + return e_length; + } + }; + // x -- x coordinates of wipe tower in mm ( left bottom corner ) // y -- y coordinates of wipe tower in mm ( left bottom corner ) // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) // wipe_area -- space available for one toolchange in mm - WipeTowerPrusaMM(bool semm, float x, float y, float width, float rotation_angle, float cooling_tube_retraction, - float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, - float bridging, bool set_extruder_trimpot, GCodeFlavor flavor, - const std::vector>& wiping_matrix, unsigned int initial_tool) : + WipeTower(bool semm, float x, float y, float width, float rotation_angle, float cooling_tube_retraction, + float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, + float bridging, bool set_extruder_trimpot, GCodeFlavor flavor, + const std::vector>& wiping_matrix, unsigned int initial_tool) : m_semm(semm), m_wipe_tower_pos(x, y), m_wipe_tower_width(width), @@ -54,7 +105,7 @@ public: } } - virtual ~WipeTowerPrusaMM() {} + virtual ~WipeTower() {} // Set the extruder properties. @@ -105,14 +156,14 @@ public: void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, bool brim, float wipe_volume = 0.f); // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" - void generate(std::vector> &result); + void generate(std::vector> &result); float get_depth() const { return m_wipe_tower_depth; } // Switch to a next layer. - virtual void set_layer( + void set_layer( // Print height of this layer. float print_z, // Layer height, used to calculate extrusion the rate. @@ -146,14 +197,14 @@ public: } // Return the wipe tower position. - virtual const xy& position() const { return m_wipe_tower_pos; } + const Vec2f& position() const { return m_wipe_tower_pos; } // Return the wipe tower width. - virtual float width() const { return m_wipe_tower_width; } + float width() const { return m_wipe_tower_width; } // The wipe tower is finished, there should be no more tool changes or wipe tower prints. - virtual bool finished() const { return m_max_color_changes == 0; } + bool finished() const { return m_max_color_changes == 0; } // Returns gcode to prime the nozzles at the front edge of the print bed. - virtual std::vector prime( + std::vector prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. @@ -164,19 +215,19 @@ public: // Returns gcode for a toolchange and a final print head position. // On the first layer, extrude a brim around the future wipe tower first. - virtual ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer); + ToolChangeResult tool_change(unsigned int new_tool, bool last_in_layer); // Fill the unfilled space with a sparse infill. // Call this method only if layer_finished() is false. - virtual ToolChangeResult finish_layer(); + ToolChangeResult finish_layer(); // Is the current layer finished? - virtual bool layer_finished() const { + bool layer_finished() const { return ( (m_is_first_layer ? m_wipe_tower_depth - m_perimeter_width : m_layer_info->depth) - WT_EPSILON < m_depth_traversed); } - virtual std::vector get_used_filament() const override { return m_used_filament_length; } - virtual int get_number_of_toolchanges() const override { return m_num_tool_changes; } + std::vector get_used_filament() const { return m_used_filament_length; } + int get_number_of_toolchanges() const { return m_num_tool_changes; } struct FilamentParameters { std::string material = "PLA"; @@ -198,7 +249,7 @@ public: }; private: - WipeTowerPrusaMM(); + WipeTower(); enum wipe_shape // A fill-in direction { @@ -214,7 +265,7 @@ private: bool m_semm = true; // Are we using a single extruder multimaterial printer? - xy m_wipe_tower_pos; // Left front corner of the wipe tower in mm. + Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm. float m_wipe_tower_width; // Width of the wipe tower. float m_wipe_tower_depth = 0.f; // Depth of the wipe tower float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) @@ -287,28 +338,28 @@ private: lu(left , bottom + height), rd(left + width, bottom ), ru(left + width, bottom + height) {} - box_coordinates(const xy &pos, float width, float height) : box_coordinates(pos.x, pos.y, width, height) {} - void translate(const xy &shift) { + box_coordinates(const Vec2f &pos, float width, float height) : box_coordinates(pos(0), pos(1), width, height) {} + void translate(const Vec2f &shift) { ld += shift; lu += shift; rd += shift; ru += shift; } - void translate(const float dx, const float dy) { translate(xy(dx, dy)); } + void translate(const float dx, const float dy) { translate(Vec2f(dx, dy)); } void expand(const float offset) { - ld += xy(- offset, - offset); - lu += xy(- offset, offset); - rd += xy( offset, - offset); - ru += xy( offset, offset); + ld += Vec2f(- offset, - offset); + lu += Vec2f(- offset, offset); + rd += Vec2f( offset, - offset); + ru += Vec2f( offset, offset); } void expand(const float offset_x, const float offset_y) { - ld += xy(- offset_x, - offset_y); - lu += xy(- offset_x, offset_y); - rd += xy( offset_x, - offset_y); - ru += xy( offset_x, offset_y); + ld += Vec2f(- offset_x, - offset_y); + lu += Vec2f(- offset_x, offset_y); + rd += Vec2f( offset_x, - offset_y); + ru += Vec2f( offset_x, offset_y); } - xy ld; // left down - xy lu; // left upper - xy rd; // right lower - xy ru; // right upper + Vec2f ld; // left down + Vec2f lu; // left upper + Vec2f rd; // right lower + Vec2f ru; // right upper }; @@ -349,22 +400,22 @@ private: ToolChangeResult toolchange_Brim(bool sideOnly = false, float y_offset = 0.f); void toolchange_Unload( - PrusaMultiMaterial::Writer &writer, + WipeTowerWriter &writer, const box_coordinates &cleaning_box, const std::string& current_material, const int new_temperature); void toolchange_Change( - PrusaMultiMaterial::Writer &writer, + WipeTowerWriter &writer, const unsigned int new_tool, const std::string& new_material); void toolchange_Load( - PrusaMultiMaterial::Writer &writer, + WipeTowerWriter &writer, const box_coordinates &cleaning_box); void toolchange_Wipe( - PrusaMultiMaterial::Writer &writer, + WipeTowerWriter &writer, const box_coordinates &cleaning_box, float wipe_volume); }; @@ -374,4 +425,4 @@ private: }; // namespace Slic3r -#endif /* WipeTowerPrusaMM_hpp_ */ +#endif // WipeTowerPrusaMM_hpp_ diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 7a9bb785f8..6615bff725 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -7,7 +7,7 @@ #include "I18N.hpp" #include "SupportMaterial.hpp" #include "GCode.hpp" -#include "GCode/WipeTowerPrusaMM.hpp" +#include "GCode/WipeTower.hpp" #include "Utils.hpp" //#include "PrintExport.hpp" @@ -1791,7 +1791,7 @@ void Print::_make_wipe_tower() this->throw_if_canceled(); // Initialize the wipe tower. - WipeTowerPrusaMM wipe_tower( + WipeTower wipe_tower( m_config.single_extruder_multi_material.value, float(m_config.wipe_tower_x.value), float(m_config.wipe_tower_y.value), float(m_config.wipe_tower_width.value), diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 65e06e4323..6b55bd751b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4773,7 +4773,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ { const Print *print; const std::vector *tool_colors; - WipeTower::xy wipe_tower_pos; + Vec2f wipe_tower_pos; float wipe_tower_angle; // Number of vertices (each vertex is 6x4=24 bytes long) @@ -4810,7 +4810,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ ctxt.final.emplace_back(*print->wipe_tower_data().final_purge.get()); ctxt.wipe_tower_angle = ctxt.print->config().wipe_tower_rotation_angle.value/180.f * PI; - ctxt.wipe_tower_pos = WipeTower::xy(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value); + ctxt.wipe_tower_pos = Vec2f(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value); BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; @@ -4872,19 +4872,19 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ WipeTower::Extrusion e_prev = extrusions.extrusions[i-1]; if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation - e_prev.pos.rotate(ctxt.wipe_tower_angle); - e_prev.pos.translate(ctxt.wipe_tower_pos); + e_prev.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e_prev.pos; + e_prev.pos += ctxt.wipe_tower_pos; } for (; i < j; ++i) { WipeTower::Extrusion e = extrusions.extrusions[i]; assert(e.width > 0.f); if (!extrusions.priming) { - e.pos.rotate(ctxt.wipe_tower_angle); - e.pos.translate(ctxt.wipe_tower_pos); + e.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e.pos; + e.pos += ctxt.wipe_tower_pos; } - lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y)); + lines.emplace_back(Point::new_scale(e_prev.pos.x(), e_prev.pos.y()), Point::new_scale(e.pos.x(), e.pos.y())); widths.emplace_back(e.width); e_prev = e; From a643a221511098350ffe3032e885b2216a1d43c1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 17 Jun 2019 10:26:33 +0200 Subject: [PATCH 129/627] Wipe tower - renaming files (to conclude work from previous commit and not lose history of those files) --- src/libslic3r/CMakeLists.txt | 3 +-- src/libslic3r/GCode/{WipeTowerPrusaMM.cpp => WipeTower.cpp} | 0 src/libslic3r/GCode/{WipeTowerPrusaMM.hpp => WipeTower.hpp} | 0 3 files changed, 1 insertion(+), 2 deletions(-) rename src/libslic3r/GCode/{WipeTowerPrusaMM.cpp => WipeTower.cpp} (100%) rename src/libslic3r/GCode/{WipeTowerPrusaMM.hpp => WipeTower.hpp} (100%) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 312a82c4c8..9c1e82b7a0 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -81,9 +81,8 @@ add_library(libslic3r STATIC GCode/SpiralVase.hpp GCode/ToolOrdering.cpp GCode/ToolOrdering.hpp + GCode/WipeTower.cpp GCode/WipeTower.hpp - GCode/WipeTowerPrusaMM.cpp - GCode/WipeTowerPrusaMM.hpp GCode.cpp GCode.hpp GCodeReader.cpp diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTower.cpp similarity index 100% rename from src/libslic3r/GCode/WipeTowerPrusaMM.cpp rename to src/libslic3r/GCode/WipeTower.cpp diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/src/libslic3r/GCode/WipeTower.hpp similarity index 100% rename from src/libslic3r/GCode/WipeTowerPrusaMM.hpp rename to src/libslic3r/GCode/WipeTower.hpp From 1152bd5a09bb329caa7d8f3657fa33472b1ccaa9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 17 Jun 2019 11:22:17 +0200 Subject: [PATCH 130/627] Fixup of 41164a9 The WipeTowerWriter did not now which tool is being used, so it limited the volumetric flow based on different filament settings --- src/libslic3r/GCode/WipeTower.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index e901ba296f..52601886cf 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -307,9 +307,6 @@ public: WipeTowerWriter& set_tool(int tool) { - char buf[64]; - sprintf(buf, "T%d\n", tool); - m_gcode += buf; m_current_tool = tool; return *this; } @@ -920,7 +917,7 @@ void WipeTower::toolchange_Change( // The toolchange Tn command will be inserted later, only in case that the user does // not provide a custom toolchange gcode. - //writer.set_tool(new_tool); + writer.set_tool(new_tool); // This outputs nothing, the writer just needs to know the tool has changed. writer.append("[start_filament_gcode]\n"); writer.flush_planner_queue(); From 95ad76a0dc45a380478bd4bdb3cc04e552950a5a Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 17 Jun 2019 12:59:30 +0200 Subject: [PATCH 131/627] Wipe tower - fixed a long existent bug that sometimes resulted in inexact feedrate on the loading moves --- src/libslic3r/GCode/WipeTower.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 52601886cf..8f7f9f26c3 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -251,12 +251,6 @@ public: m_gcode += "\n"; return *this; } - - // Derectract while moving in the X direction. - // If |x| > 0, the feed rate relates to the x distance, - // otherwise the feed rate relates to the e distance. - WipeTowerWriter& load_move_x(float x, float e, float f = 0.f) - { return extrude_explicit(x, m_current_pos.y(), e, f); } WipeTowerWriter& retract(float e, float f = 0.f) { return load(-e, f); } @@ -264,12 +258,18 @@ public: // Loads filament while also moving towards given points in x-axis (x feedrate is limited by cutting the distance short if necessary) WipeTowerWriter& load_move_x_advanced(float farthest_x, float loading_dist, float loading_speed, float max_x_speed = 50.f) { - float time = std::abs(loading_dist / loading_speed); - float x_speed = std::min(max_x_speed, std::abs(farthest_x - x()) / time); - float feedrate = 60.f * std::hypot(x_speed, loading_speed); + float time = std::abs(loading_dist / loading_speed); // time that the move must take + float x_distance = std::abs(farthest_x - x()); // max x-distance that we can travel + float x_speed = x_distance / time; // x-speed to do it in that time - float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_speed * time; - return extrude_explicit(end_point, y(), loading_dist, feedrate); + if (x_speed > max_x_speed) { + // Necessary x_speed is too high - we must shorten the distance to achieve max_x_speed and still respect the time. + x_distance = max_x_speed * time; + x_speed = max_x_speed; + } + + float end_point = x() + (farthest_x > x() ? 1.f : -1.f) * x_distance; + return extrude_explicit(end_point, y(), loading_dist, x_speed * 60.f, false, false); } // Elevate the extruder head above the current print_z position. @@ -300,8 +300,8 @@ public: // at the current Y position to spread the leaking material. WipeTowerWriter& cool(float x1, float x2, float e1, float e2, float f) { - extrude_explicit(x1, m_current_pos.y(), e1, f); - extrude_explicit(x2, m_current_pos.y(), e2); + extrude_explicit(x1, m_current_pos.y(), e1, f, false, false); + extrude_explicit(x2, m_current_pos.y(), e2, false, false); return *this; } @@ -406,7 +406,7 @@ public: return *this; } - WipeTowerWriter& append(const char *text) { m_gcode += text; return *this; } + WipeTowerWriter& append(const std::string& text) { m_gcode += text; return *this; } private: Vec2f m_start_pos; @@ -825,7 +825,7 @@ void WipeTower::toolchange_Unload( const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / Filament_Area; // transform volume per sec to E move; const float dist = std::min(x - e_done, remaining); // distance to travel for either the next 0.25s, or to the next turnaround const float actual_time = dist/x * 0.25; - writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0, 0, e * (dist / x), std::hypot(dist, e * (dist / x)) / (actual_time / 60.)); + writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0, 0, e * (dist / x), dist / (actual_time / 60.)); remaining -= dist; if (remaining < WT_EPSILON) { // we reached a turning point From 90a854f7045b4b4295d2e0a31a5007175c7267bc Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 17 Jun 2019 13:02:49 +0200 Subject: [PATCH 132/627] Fix levitation when supports are disabled. --- src/libslic3r/SLAPrint.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index d3c931d346..9fe0687b53 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -604,8 +604,8 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { sla::PoolConfig::EmbedObject ret; - ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && - c.pad_enable.getBool(); + ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && + c.pad_enable.getBool() && c.supports_enable.getBool(); if(ret.enabled) { ret.object_gap_mm = c.pad_object_gap.getFloat(); @@ -1737,7 +1737,8 @@ bool SLAPrintObject::invalidate_all_steps() } double SLAPrintObject::get_elevation() const { - double ret = m_config.support_object_elevation.getFloat(); + bool en = m_config.supports_enable.getBool(); + double ret = en ? m_config.support_object_elevation.getFloat() : 0.; if(m_config.pad_enable.getBool()) { // Normally the elevation for the pad itself would be the thickness of From f394f84d51b8654b9008a11e773bdd04d75b979d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 17 Jun 2019 13:09:11 +0200 Subject: [PATCH 133/627] Fixed selection after layers deleting --- src/slic3r/GUI/GUI_ObjectList.cpp | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 31396b630c..1d7d95b56b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2317,32 +2317,28 @@ void ObjectList::remove() wxDataViewItemArray sels; GetSelections(sels); + wxDataViewItem parent = wxDataViewItem(0); + for (auto& item : sels) { if (m_objects_model->GetParent(item) == wxDataViewItem(0)) delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); else { -// if (sels.size() == 1) -// select_item(m_objects_model->GetParent(item)); - wxDataViewItem parent = m_objects_model->GetParent(item); - if (sels.size() == 1) { - if (!(m_objects_model->GetItemType(item) & itLayer)) { - select_item(parent); - parent = wxDataViewItem(0); - } - else { - wxDataViewItemArray children; - if (m_objects_model->GetChildren(parent, children) == 1) - parent = m_objects_model->GetTopParent(item); - } + if (m_objects_model->GetItemType(item) & itLayer) { + parent = m_objects_model->GetParent(item); + wxDataViewItemArray children; + if (m_objects_model->GetChildren(parent, children) == 1) + parent = m_objects_model->GetTopParent(item); } + else if (sels.size() == 1) + select_item(m_objects_model->GetParent(item)); del_subobject_item(item); - - if (sels.size() == 1 && parent) - select_item(parent); } } + + if (parent) + select_item(parent); } void ObjectList::del_layer_range(const t_layer_height_range& range) From 5fd3cc267686c74e422a013e62bbae6aea588678 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 17 Jun 2019 13:46:56 +0200 Subject: [PATCH 134/627] Select edited layer after changing instead of "Layers" selection, if editing was in "Layer" mode --- src/slic3r/GUI/GUI_ObjectList.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 1d7d95b56b..5539ee29af 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2486,6 +2486,8 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return false; + const ItemType sel_type = m_objects_model->GetItemType(GetSelection()); + t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; const DynamicPrintConfig config = ranges[range]; @@ -2501,8 +2503,7 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay for (const auto r : ranges) add_layer_item(r.first, root_item); - // To update(recreate) layers sizer call select_item for LayerRoot item expand - select_item(root_item); + select_item(sel_type&itLayer ? m_objects_model->GetItemByLayerRange(obj_idx, new_range) : root_item); Expand(root_item); return true; From f70b25d2f8321e016a7f72a229177f38c756e730 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 17 Jun 2019 15:33:58 +0200 Subject: [PATCH 135/627] Fixed SPE 957 - Impossible to add a part by "Load..." to an object, if the object has some instances --- src/slic3r/GUI/GUI_ObjectList.cpp | 11 ++++++++--- src/slic3r/GUI/wxExtensions.cpp | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d8d99fdf46..c8e6568886 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1415,13 +1415,18 @@ void ObjectList::update_opt_keys(t_config_option_keys& opt_keys) void ObjectList::load_subobject(ModelVolumeType type) { - auto item = GetSelection(); - if (!item || m_objects_model->GetParent(item) != wxDataViewItem(0)) + wxDataViewItem item = GetSelection(); + // we can add volumes for Object or Instance + if (!item || !(m_objects_model->GetItemType(item)&(itObject|itInstance))) return; - int obj_idx = m_objects_model->GetIdByItem(item); + const int obj_idx = m_objects_model->GetObjectIdByItem(item); if (obj_idx < 0) return; + // Get object item, if Instance is selected + if (m_objects_model->GetItemType(item)&itInstance) + item = m_objects_model->GetItemById(obj_idx); + std::vector> volumes_info; load_part((*m_objects)[obj_idx], volumes_info, type); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 76ba853dc3..06cf9b7d58 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -586,7 +586,7 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent ItemAdded(parent_item, child); root->m_volumes_cnt++; - if (insert_position > 0) insert_position++; + if (insert_position >= 0) insert_position++; } const auto node = new ObjectDataViewModelNode(root, name, GetVolumeIcon(volume_type, has_errors), extruder_str, root->m_volumes_cnt); From 90beadb65f0841d2773d2def866adfd542fee6b2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 17 Jun 2019 17:04:19 +0200 Subject: [PATCH 136/627] Check a possibility to load SLA project if there is at least one multi-part object on the bed --- src/slic3r/GUI/Plater.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bdf2498770..557b123777 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1671,6 +1671,22 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (load_config && !config_loaded.empty()) { // Based on the printer technology field found in the loaded config, select the base for the config, PrinterTechnology printer_technology = Preset::printer_technology(config_loaded); + + // We can't to load SLA project if there is at least one multi-part object on the bed + if (printer_technology == ptSLA) + { + const ModelObjectPtrs& objects = q->model().objects; + for (auto object : objects) + if (object->volumes.size() > 1) + { + Slic3r::GUI::show_info(nullptr, + _(L("You can't to load SLA project if there is at least one multi-part object on the bed")) + "\n\n" + + _(L("Please check your object list before preset changing.")), + _(L("Attention!"))); + return obj_idxs; + } + } + config.apply(printer_technology == ptFFF ? static_cast(FullPrintConfig::defaults()) : static_cast(SLAFullPrintConfig::defaults())); From 778b2cf293d3082162b97a2c7d40add410f0fde7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 17 Jun 2019 18:06:52 +0200 Subject: [PATCH 137/627] WIP on removing unused parts of pad --- src/libslic3r/SLA/SLABasePool.cpp | 45 +++++--- src/libslic3r/SLA/SLABasePool.hpp | 7 +- src/libslic3r/SLA/SLASpatIndex.hpp | 65 ++++++++--- src/libslic3r/SLA/SLASupportTree.cpp | 93 +++++++++++----- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 136 ++++++++++++++++++------ 5 files changed, 254 insertions(+), 92 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 4e1e030185..48d615a29d 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -666,24 +666,19 @@ Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, return punion; } -void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, - float layerh, ThrowOnCancel thrfn) +void base_plate(const TriangleMesh & mesh, + ExPolygons & output, + const std::vector &heights, + ThrowOnCancel thrfn) { - TriangleMesh m = mesh; - m.require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer slicer(&m); - - auto bb = mesh.bounding_box(); - float gnd = float(bb.min(Z)); - std::vector heights = {float(bb.min(Z))}; - for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) - heights.emplace_back(hi); - - std::vector out; out.reserve(size_t(std::ceil(h/layerh))); + // m.require_shared_vertices(); // TriangleMeshSlicer needs this + TriangleMeshSlicer slicer(&mesh); + + std::vector out; out.reserve(heights.size()); slicer.slice(heights, 0.f, &out, thrfn); - + size_t count = 0; for(auto& o : out) count += o.size(); - + // Now we have to unify all slice layers which can be an expensive operation // so we will try to simplify the polygons ExPolygons tmp; tmp.reserve(count); @@ -692,15 +687,31 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, auto&& exss = e.simplify(scaled(0.1)); for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); } - + ExPolygons utmp = unify(tmp); - + for(auto& o : utmp) { auto&& smp = o.simplify(scaled(0.1)); output.insert(output.end(), smp.begin(), smp.end()); } } +void base_plate(const TriangleMesh &mesh, + ExPolygons & output, + float h, + float layerh, + ThrowOnCancel thrfn) +{ + auto bb = mesh.bounding_box(); + float gnd = float(bb.min(Z)); + std::vector heights = {float(bb.min(Z))}; + + for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) + heights.emplace_back(hi); + + base_plate(mesh, output, heights, thrfn); +} + Contour3D create_base_pool(const Polygons &ground_layer, const ExPolygons &obj_self_pad = {}, const PoolConfig& cfg = PoolConfig()) diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 8aa4f5f412..67b9ccdcb5 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -21,10 +21,15 @@ using ThrowOnCancel = std::function; /// Calculate the polygon representing the silhouette from the specified height void base_plate(const TriangleMesh& mesh, // input mesh ExPolygons& output, // Output will be merged with - float zlevel = 0.1f, // Plate creation level + float samplingheight = 0.1f, // The height range to sample float layerheight = 0.05f, // The sampling height ThrowOnCancel thrfn = [](){}); // Will be called frequently +void base_plate(const TriangleMesh& mesh, // input mesh + ExPolygons& output, // Output will be merged with + const std::vector&, // Exact Z levels to sample + ThrowOnCancel thrfn = [](){}); // Will be called frequently + // Function to cut tiny connector cavities for a given polygon. The input poly // will be offsetted by "padding" and small rectangle shaped cavities will be // inserted along the perimeter in every "stride" distance. The stick rectangles diff --git a/src/libslic3r/SLA/SLASpatIndex.hpp b/src/libslic3r/SLA/SLASpatIndex.hpp index e5fbfa7d4b..90dcdc3627 100644 --- a/src/libslic3r/SLA/SLASpatIndex.hpp +++ b/src/libslic3r/SLA/SLASpatIndex.hpp @@ -7,13 +7,15 @@ #include +#include + namespace Slic3r { namespace sla { typedef Eigen::Matrix Vec3d; -using SpatElement = std::pair; +using PointIndexEl = std::pair; -class SpatIndex { +class PointIndex { class Impl; // We use Pimpl because it takes a long time to compile boost headers which @@ -21,30 +23,67 @@ class SpatIndex { std::unique_ptr m_impl; public: - SpatIndex(); - ~SpatIndex(); + PointIndex(); + ~PointIndex(); - SpatIndex(const SpatIndex&); - SpatIndex(SpatIndex&&); - SpatIndex& operator=(const SpatIndex&); - SpatIndex& operator=(SpatIndex&&); + PointIndex(const PointIndex&); + PointIndex(PointIndex&&); + PointIndex& operator=(const PointIndex&); + PointIndex& operator=(PointIndex&&); - void insert(const SpatElement&); - bool remove(const SpatElement&); + void insert(const PointIndexEl&); + bool remove(const PointIndexEl&); inline void insert(const Vec3d& v, unsigned idx) { insert(std::make_pair(v, unsigned(idx))); } - std::vector query(std::function); - std::vector nearest(const Vec3d&, unsigned k); + std::vector query(std::function); + std::vector nearest(const Vec3d&, unsigned k); // For testing size_t size() const; bool empty() const { return size() == 0; } - void foreach(std::function fn); + void foreach(std::function fn); +}; + +using BoxIndexEl = std::pair; + +class BoxIndex { + class Impl; + + // We use Pimpl because it takes a long time to compile boost headers which + // is the engine of this class. We include it only in the cpp file. + std::unique_ptr m_impl; +public: + + BoxIndex(); + ~BoxIndex(); + + BoxIndex(const BoxIndex&); + BoxIndex(BoxIndex&&); + BoxIndex& operator=(const BoxIndex&); + BoxIndex& operator=(BoxIndex&&); + + void insert(const BoxIndexEl&); + inline void insert(const BoundingBox& bb, unsigned idx) + { + insert(std::make_pair(bb, unsigned(idx))); + } + + bool remove(const BoxIndexEl&); + + enum QueryType { qtIntersects, qtWithin }; + + std::vector query(const BoundingBox&, QueryType qt); + + // For testing + size_t size() const; + bool empty() const { return size() == 0; } + + void foreach(std::function fn); }; } diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 41040e89ea..c38ff34e19 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -569,37 +569,74 @@ struct Pad { sla::get_pad_elevation(pcfg)) { Polygons basep; - cfg.throw_on_cancel(); + auto &thr = cfg.throw_on_cancel; - // The 0.1f is the layer height with which the mesh is sampled and then - // the layers are unified into one vector of polygons. - ExPolygons platetmp; - base_plate(object_support_mesh, platetmp, - float(cfg.min_wall_height_mm + cfg.min_wall_thickness_mm), - 0.1f, pcfg.throw_on_cancel); + thr(); - for (const ExPolygon &bp : platetmp) basep.emplace_back(bp.contour); + // Get a sample for the pad from the support mesh + { + ExPolygons platetmp; + float plateZ = float(get_pad_fullheight(pcfg) + EPSILON); + + base_plate(object_support_mesh, platetmp, plateZ, 0.1f, thr); + + // We don't need no... holes control... + for (const ExPolygon &bp : platetmp) + basep.emplace_back(std::move(bp.contour)); + } if(pcfg.embed_object) { - ExPolygons modelbase_sticks = modelbase; + // If the zero elevation mode is ON, we need to process the model + // base silhouette. Create the offsetted version and punch the + // breaksticks across its perimeter. + + ExPolygons modelbase_sticks = modelbase; + if (pcfg.embed_object.object_gap_mm > 0.0) modelbase_sticks = offset_ex(modelbase_sticks, - coord_t(pcfg.embed_object.object_gap_mm - / SCALING_FACTOR)); + float(scaled(pcfg.embed_object.object_gap_mm))); + BoxIndex bindex; + { + unsigned idx = 0; + for(auto &bp : basep) { + auto bb = bp.bounding_box(); + bb.offset(float(scaled(pcfg.min_wall_thickness_mm))); + bindex.insert(bb, idx++); + } + } + + ExPolygons pad_stickholes; pad_stickholes.reserve(modelbase.size()); for(auto& poly : modelbase_sticks) { - basep.emplace_back(poly.contour); - sla::breakstick_holes( - poly, - pcfg.embed_object.object_gap_mm, // padding - pcfg.embed_object.stick_stride_mm, - pcfg.embed_object.stick_width_mm, - pcfg.embed_object.stick_penetration_mm); + + if (!bindex.query(poly.contour.bounding_box(), + BoxIndex::qtIntersects).empty()) { + + basep.emplace_back(poly.contour); + + auto it = poly.holes.begin(); + while(it != poly.holes.end()) { + if (bindex.query(it->bounding_box(), + BoxIndex::qtIntersects).empty()) + it = poly.holes.erase(it); + else + ++it; + } + + sla::breakstick_holes( + poly, + pcfg.embed_object.object_gap_mm, // padding + pcfg.embed_object.stick_stride_mm, + pcfg.embed_object.stick_width_mm, + pcfg.embed_object.stick_penetration_mm); + + pad_stickholes.emplace_back(poly); + } } - create_base_pool(basep, tmesh, modelbase_sticks, cfg); + create_base_pool(basep, tmesh, pad_stickholes, cfg); } else { for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); create_base_pool(basep, tmesh, {}, cfg); @@ -630,7 +667,7 @@ inline Vec2d to_vec2(const Vec3d& v3) { return {v3(X), v3(Y)}; } -bool operator==(const SpatElement& e1, const SpatElement& e2) { +bool operator==(const PointIndexEl& e1, const PointIndexEl& e2) { return e1.second == e2.second; } @@ -647,7 +684,7 @@ ClusteredPoints cluster(const PointSet& points, ClusteredPoints cluster( const std::vector& indices, std::function pointfn, - std::function predicate, + std::function predicate, unsigned max_points); // This class will hold the support tree meshes with some additional bookkeeping @@ -974,7 +1011,7 @@ class SLASupportTree::Algorithm { ThrowOnCancel m_thr; // A spatial index to easily find strong pillars to connect to. - SpatIndex m_pillar_index; + PointIndex m_pillar_index; inline double ray_mesh_intersect(const Vec3d& s, const Vec3d& dir) @@ -1367,7 +1404,7 @@ class SLASupportTree::Algorithm { } bool search_pillar_and_connect(const Head& head) { - SpatIndex spindex = m_pillar_index; + PointIndex spindex = m_pillar_index; long nearest_id = -1; @@ -1747,8 +1784,8 @@ public: return m_result.head(i).junction_point(); }; - auto predicate = [this](const SpatElement &e1, - const SpatElement &e2) { + auto predicate = [this](const PointIndexEl &e1, + const PointIndexEl &e2) { double d2d = distance(to_2d(e1.first), to_2d(e2.first)); double d3d = distance(e1.first, e2.first); return d2d < 2 * m_cfg.base_radius_mm @@ -2070,7 +2107,7 @@ public: // be connected multiple times this is ensured by the 'pairs' set which // remembers the processed pillar pairs auto cascadefn = - [this, d, &pairs, min_height_ratio, H1] (const SpatElement& el) + [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) { Vec3d qp = el.first; // endpoint of the pillar @@ -2083,13 +2120,13 @@ public: if(pillar.links >= neighbors) return; // Query all remaining points within reach - auto qres = m_pillar_index.query([qp, d](const SpatElement& e){ + auto qres = m_pillar_index.query([qp, d](const PointIndexEl& e){ return distance(e.first, qp) < d; }); // sort the result by distance (have to check if this is needed) std::sort(qres.begin(), qres.end(), - [qp](const SpatElement& e1, const SpatElement& e2){ + [qp](const PointIndexEl& e1, const PointIndexEl& e2){ return distance(e1.first, qp) < distance(e2.first, qp); }); diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index c368b8604d..04e6f79c75 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -29,69 +29,137 @@ namespace sla { using igl::PI; /* ************************************************************************** - * SpatIndex implementation + * PointIndex implementation * ************************************************************************** */ -class SpatIndex::Impl { +class PointIndex::Impl { public: - using BoostIndex = boost::geometry::index::rtree< SpatElement, + using BoostIndex = boost::geometry::index::rtree< PointIndexEl, boost::geometry::index::rstar<16, 4> /* ? */ >; BoostIndex m_store; }; -SpatIndex::SpatIndex(): m_impl(new Impl()) {} -SpatIndex::~SpatIndex() {} +PointIndex::PointIndex(): m_impl(new Impl()) {} +PointIndex::~PointIndex() {} -SpatIndex::SpatIndex(const SpatIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} -SpatIndex::SpatIndex(SpatIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} +PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} -SpatIndex& SpatIndex::operator=(const SpatIndex &cpy) +PointIndex& PointIndex::operator=(const PointIndex &cpy) { m_impl.reset(new Impl(*cpy.m_impl)); return *this; } -SpatIndex& SpatIndex::operator=(SpatIndex &&cpy) +PointIndex& PointIndex::operator=(PointIndex &&cpy) { m_impl.swap(cpy.m_impl); return *this; } -void SpatIndex::insert(const SpatElement &el) +void PointIndex::insert(const PointIndexEl &el) { m_impl->m_store.insert(el); } -bool SpatIndex::remove(const SpatElement& el) +bool PointIndex::remove(const PointIndexEl& el) { return m_impl->m_store.remove(el) == 1; } -std::vector -SpatIndex::query(std::function fn) +std::vector +PointIndex::query(std::function fn) { namespace bgi = boost::geometry::index; - std::vector ret; + std::vector ret; m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); return ret; } -std::vector SpatIndex::nearest(const Vec3d &el, unsigned k = 1) +std::vector PointIndex::nearest(const Vec3d &el, unsigned k = 1) { namespace bgi = boost::geometry::index; - std::vector ret; ret.reserve(k); + std::vector ret; ret.reserve(k); m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); return ret; } -size_t SpatIndex::size() const +size_t PointIndex::size() const { return m_impl->m_store.size(); } -void SpatIndex::foreach(std::function fn) +void PointIndex::foreach(std::function fn) +{ + for(auto& el : m_impl->m_store) fn(el); +} + +/* ************************************************************************** + * BoxIndex implementation + * ************************************************************************** */ + +class BoxIndex::Impl { +public: + using BoostIndex = boost::geometry::index:: + rtree /* ? */>; + + BoostIndex m_store; +}; + +BoxIndex::BoxIndex(): m_impl(new Impl()) {} +BoxIndex::~BoxIndex() {} + +BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} + +BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) +{ + m_impl.reset(new Impl(*cpy.m_impl)); + return *this; +} + +BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) +{ + m_impl.swap(cpy.m_impl); + return *this; +} + +void BoxIndex::insert(const BoxIndexEl &el) +{ + m_impl->m_store.insert(el); +} + +bool BoxIndex::remove(const BoxIndexEl& el) +{ + return m_impl->m_store.remove(el) == 1; +} + +std::vector BoxIndex::query(const BoundingBox &qrbb, + BoxIndex::QueryType qt) +{ + namespace bgi = boost::geometry::index; + + std::vector ret; ret.reserve(m_impl->m_store.size()); + + switch (qt) { + case qtIntersects: + m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); + break; + case qtWithin: + m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); + } + + return ret; +} + +size_t BoxIndex::size() const +{ + return m_impl->m_store.size(); +} + +void BoxIndex::foreach(std::function fn) { for(auto& el : m_impl->m_store) fn(el); } @@ -352,12 +420,14 @@ PointSet normals(const PointSet& points, return ret; } namespace bgi = boost::geometry::index; -using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; +using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; -ClusteredPoints cluster(Index3D& sindex, unsigned max_points, - std::function(const Index3D&, const SpatElement&)> qfn) +ClusteredPoints cluster(Index3D &sindex, + unsigned max_points, + std::function( + const Index3D &, const PointIndexEl &)> qfn) { - using Elems = std::vector; + using Elems = std::vector; // Recursive function for visiting all the points in a given distance to // each other @@ -365,8 +435,8 @@ ClusteredPoints cluster(Index3D& sindex, unsigned max_points, [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) { for(auto& p : pts) { - std::vector tmp = qfn(sindex, p); - auto cmp = [](const SpatElement& e1, const SpatElement& e2){ + std::vector tmp = qfn(sindex, p); + auto cmp = [](const PointIndexEl& e1, const PointIndexEl& e2){ return e1.second < e2.second; }; @@ -410,12 +480,12 @@ ClusteredPoints cluster(Index3D& sindex, unsigned max_points, } namespace { -std::vector distance_queryfn(const Index3D& sindex, - const SpatElement& p, +std::vector distance_queryfn(const Index3D& sindex, + const PointIndexEl& p, double dist, unsigned max_points) { - std::vector tmp; tmp.reserve(max_points); + std::vector tmp; tmp.reserve(max_points); sindex.query( bgi::nearest(p.first, max_points), std::back_inserter(tmp) @@ -442,7 +512,7 @@ ClusteredPoints cluster( for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); return cluster(sindex, max_points, - [dist, max_points](const Index3D& sidx, const SpatElement& p) + [dist, max_points](const Index3D& sidx, const PointIndexEl& p) { return distance_queryfn(sidx, p, dist, max_points); }); @@ -452,7 +522,7 @@ ClusteredPoints cluster( ClusteredPoints cluster( const std::vector& indices, std::function pointfn, - std::function predicate, + std::function predicate, unsigned max_points) { // A spatial index for querying the nearest points @@ -462,10 +532,10 @@ ClusteredPoints cluster( for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); return cluster(sindex, max_points, - [max_points, predicate](const Index3D& sidx, const SpatElement& p) + [max_points, predicate](const Index3D& sidx, const PointIndexEl& p) { - std::vector tmp; tmp.reserve(max_points); - sidx.query(bgi::satisfies([p, predicate](const SpatElement& e){ + std::vector tmp; tmp.reserve(max_points); + sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){ return predicate(p, e); }), std::back_inserter(tmp)); return tmp; @@ -482,7 +552,7 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); return cluster(sindex, max_points, - [dist, max_points](const Index3D& sidx, const SpatElement& p) + [dist, max_points](const Index3D& sidx, const PointIndexEl& p) { return distance_queryfn(sidx, p, dist, max_points); }); From 5ee695b6291ffe7cce6153f509de0f78f22239aa Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 18 Jun 2019 08:53:13 +0200 Subject: [PATCH 138/627] #2506 - Fixed crash while manually editing SLA supports --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index fb758d2404..afd0087aa1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -131,6 +131,11 @@ private: protected: void on_set_state() override; + virtual void on_set_hover_id() + { + if ((int)m_editing_mode_cache.size() <= m_hover_id) + m_hover_id = -1; + } void on_start_dragging(const Selection& selection) override; virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) override; From 630883ad0f4d7f602e06773a8f333e65c1385e77 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 18 Jun 2019 08:54:28 +0200 Subject: [PATCH 139/627] Extended the error message when the G-code cannot be copied to the SD card --- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 94fb6481b7..b77a272e2e 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -89,7 +89,7 @@ void BackgroundSlicingProcess::process_fff() // Perform the final post-processing of the export path by applying the print statistics over the file name. std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path); if (copy_file(m_temp_output_path, export_path) != 0) - throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); + throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?"))); m_print->set_status(95, _utf8(L("Running post-processing scripts"))); run_post_process_scripts(export_path, m_fff_print->config()); m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str()); From f72f55dc08b3526e311bb4b92f127427e57a5b3d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 18 Jun 2019 09:57:07 +0200 Subject: [PATCH 140/627] A bit of documentation of the mirroring and reset buttons at the side panel. --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 224c053e4e..310000ecc3 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -216,9 +216,10 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : else return; + // Update mirroring at the GLVolumes. selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); - + // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. canvas->do_mirror(); canvas->set_as_dirty(); UpdateAndShow(true); @@ -297,8 +298,10 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : else return; + // Update rotation at the GLVolumes. selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); + // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. canvas->do_rotate(); UpdateAndShow(true); From d7684188f90c8eb2ae1f7bc881aa918d3177ecd6 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Jun 2019 11:24:50 +0200 Subject: [PATCH 141/627] Removing unused pad parts working --- src/libslic3r/MTUtils.hpp | 54 ++++++++++++++++++++++++++++ src/libslic3r/SLA/SLASupportTree.cpp | 9 +++-- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 7e91ace328..ee70f535d4 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -5,6 +5,9 @@ #include // for std::lock_guard #include // for std::function #include // for std::forward +#include +#include +#include namespace Slic3r { @@ -182,6 +185,57 @@ public: inline bool empty() const { return size() == 0; } }; +template +struct remove_cvref +{ + using type = + typename std::remove_cv::type>::type; +}; + +template +using remove_cvref_t = typename remove_cvref::type; + +template class C, class T> +class Container: public C> { +public: + explicit Container(size_t count, T&& initval): + C>(count, initval) {} +}; + +template using DefaultContainer = std::vector; + +/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html +template class C = DefaultContainer> +inline C> linspace(const T &start, const T &stop, const I &n) +{ + Container vals(n, T()); + T stride = (stop - start) / n; + + size_t i = 0; + std::generate(vals.begin(), vals.end(), [&i, start, stride] { + return start + i++ * stride; + }); + + return vals; +} + +/// A set of equidistant values starting from 'start' (inclusive), ending +/// in the closest multiple of 'stride' less than or equal to 'end' and +/// leaving 'stride' space between each value. +/// Very similar to Matlab [start:stride:end] notation. +template class C = DefaultContainer> +inline C> grid(const T &start, const T &stop, const T &stride) +{ + Container vals(size_t(std::ceil((stop - start) / stride)), T()); + + int i = 0; + std::generate(vals.begin(), vals.end(), [&i, start, stride] { + return start + i++ * stride; + }); + + return vals; +} + } #endif // MTUTILS_HPP diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index c38ff34e19..cfb5c2e743 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -9,6 +9,7 @@ #include "SLASpatIndex.hpp" #include "SLABasePool.hpp" +#include #include #include @@ -559,7 +560,7 @@ struct Pad { Pad() = default; - Pad(const TriangleMesh& object_support_mesh, + Pad(const TriangleMesh& support_mesh, const ExPolygons& modelbase, double ground_level, const PoolConfig& pcfg) : @@ -576,9 +577,11 @@ struct Pad { // Get a sample for the pad from the support mesh { ExPolygons platetmp; - float plateZ = float(get_pad_fullheight(pcfg) + EPSILON); - base_plate(object_support_mesh, platetmp, plateZ, 0.1f, thr); + float zstart = float(zlevel); + float zend = zstart + float(get_pad_fullheight(pcfg) + EPSILON); + + base_plate(support_mesh, platetmp, grid(zstart, zend, 0.1f), thr); // We don't need no... holes control... for (const ExPolygon &bp : platetmp) From 917702f252126f909c1d5d556a307042c0a6c07c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 18 Jun 2019 11:40:26 +0200 Subject: [PATCH 142/627] Fixed DoubleSlider manipulation from Preview scene --- src/slic3r/GUI/GLCanvas3D.cpp | 27 +++++++++++++++++---------- src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/GUI_Preview.cpp | 6 ++++++ src/slic3r/GUI/GUI_Preview.hpp | 1 + src/slic3r/GUI/Plater.cpp | 1 + src/slic3r/GUI/wxExtensions.cpp | 13 +++++++++++-- src/slic3r/GUI/wxExtensions.hpp | 1 + 7 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ca05f1695d..d7b62cbf7b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -17,6 +17,7 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/Tab.hpp" +#include "slic3r/GUI/GUI_Preview.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" @@ -1209,6 +1210,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) : m_canvas(canvas) @@ -2387,8 +2389,18 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case '4': { select_view("rear"); break; } case '5': { select_view("left"); break; } case '6': { select_view("right"); break; } - case '+': { post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); break; } - case '-': { post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break; } + case '+': { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); + else + post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); + break; } + case '-': { + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt)); + else + post_event(Event(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); + break; } case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; } case 'A': case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } @@ -2472,15 +2484,10 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) else if (keyCode == WXK_LEFT || keyCode == WXK_RIGHT || keyCode == WXK_UP || - keyCode == WXK_DOWN || - keyCode == '+' || - keyCode == WXK_NUMPAD_ADD || - keyCode == '-' || - keyCode == 390 || - keyCode == WXK_DELETE || - keyCode == WXK_BACK ) + keyCode == WXK_DOWN ) { - post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, evt)); + if (dynamic_cast(m_canvas->GetParent()) != nullptr) + post_event(wxKeyEvent(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, evt)); } } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 96bb56bd93..fc7dddd464 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -125,6 +125,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); class GLCanvas3D { diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index ec7308382a..2f5e10962e 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -420,6 +420,12 @@ void Preview::move_double_slider(wxKeyEvent& evt) m_slider->OnKeyDown(evt); } +void Preview::edit_double_slider(wxKeyEvent& evt) +{ + if (m_slider) + m_slider->OnChar(evt); +} + void Preview::bind_event_handlers() { this->Bind(wxEVT_SIZE, &Preview::on_size, this); diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index ed4555f5cc..93038b0e56 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -123,6 +123,7 @@ public: void msw_rescale(); void move_double_slider(wxKeyEvent& evt); + void edit_double_slider(wxKeyEvent& evt); private: bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 557b123777..da638fa9f9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1518,6 +1518,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); }); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); }); q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this); q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 06cf9b7d58..fc34497606 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -2366,9 +2366,9 @@ void DoubleSlider::OnWheel(wxMouseEvent& event) void DoubleSlider::OnKeyDown(wxKeyEvent &event) { const int key = event.GetKeyCode(); - if (key == '+' || key == WXK_NUMPAD_ADD) + if (key == WXK_NUMPAD_ADD) action_tick(taAdd); - else if (key == '-' || key == 390 || key == WXK_DELETE || key == WXK_BACK) + else if (key == 390 || key == WXK_DELETE || key == WXK_BACK) action_tick(taDel); else if (is_horizontal()) { @@ -2398,6 +2398,15 @@ void DoubleSlider::OnKeyUp(wxKeyEvent &event) event.Skip(); } +void DoubleSlider::OnChar(wxKeyEvent& event) +{ + const int key = event.GetKeyCode(); + if (key == '+') + action_tick(taAdd); + else if (key == '-') + action_tick(taDel); +} + void DoubleSlider::OnRightDown(wxMouseEvent& event) { this->CaptureMouse(); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 78fb7be55b..c496c28a0c 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -727,6 +727,7 @@ public: void OnWheel(wxMouseEvent& event); void OnKeyDown(wxKeyEvent &event); void OnKeyUp(wxKeyEvent &event); + void OnChar(wxKeyEvent &event); void OnRightDown(wxMouseEvent& event); void OnRightUp(wxMouseEvent& event); From b001eca21f21b00af1fcc68e0a54570e1510bc07 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 18 Jun 2019 14:45:10 +0200 Subject: [PATCH 143/627] #2528 - New project command automatically switches to 3D editor view --- src/slic3r/GUI/Plater.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 297cc9f2c7..942b77725a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3322,6 +3322,7 @@ SLAPrint& Plater::sla_print() { return p->sla_print; } void Plater::new_project() { + p->select_view_3D("3D"); wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); } From 468516aa315d16d384b5927d130b4bc16aa4a8ae Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 18 Jun 2019 16:24:30 +0200 Subject: [PATCH 144/627] Apply fixes for the ui jobs. - Localization - Mutual exclusion (ExclusiveJobGroup), only one UI job can run at a time, and background processing is stopped - m_range not used for finalization anymore - stop_jobs called before Window is closed --- src/libslic3r/MTUtils.hpp | 9 + src/slic3r/GUI/MainFrame.cpp | 4 +- src/slic3r/GUI/Plater.cpp | 344 ++++++++++++++++++++++------------- 3 files changed, 224 insertions(+), 133 deletions(-) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 7e91ace328..0afe3563f9 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -5,6 +5,7 @@ #include // for std::lock_guard #include // for std::function #include // for std::forward +#include namespace Slic3r { @@ -182,6 +183,14 @@ public: inline bool empty() const { return size() == 0; } }; +template bool all_of(const C &container) { + return std::all_of(container.begin(), + container.end(), + [](const typename C::value_type &v) { + return static_cast(v); + }); +} + } #endif // MTUTILS_HPP diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 2dfc1b747c..667dcd899d 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -103,14 +103,14 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S event.Veto(); return; } + + if(m_plater) m_plater->stop_jobs(); // Weird things happen as the Paint messages are floating around the windows being destructed. // Avoid the Paint messages by hiding the main window. // Also the application closes much faster without these unnecessary screen refreshes. // In addition, there were some crashes due to the Paint events sent to already destructed windows. this->Show(false); - - if(m_plater) m_plater->stop_jobs(); // Save the slic3r.ini.Usually the ini file is saved from "on idle" callback, // but in rare cases it may not have been called yet. diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 643cb0c1b3..42b864ef2d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1270,7 +1270,7 @@ struct Plater::priv std::future m_ftr; priv *m_plater = nullptr; std::atomic m_running {false}, m_canceled {false}; - bool m_stop_slicing = false; + bool m_finalized = false; void run() { m_running.store(true); process(); m_running.store(false); @@ -1293,16 +1293,27 @@ struct Plater::priv priv& plater() { return *m_plater; } bool was_canceled() const { return m_canceled.load(); } + // Launched just before start(), a job can use it to prepare internals + virtual void prepare() {} + + // Launched when the job is finished. It refreshes the 3dscene by def. + virtual void finalize() { + // Do a full refresh of scene tree, including regenerating + // all the GLVolumes. FIXME The update function shall just + // reload the modified matrices. + if(! was_canceled()) + plater().update(true); + } + public: - Job(priv *_plater, bool stop_slicing = false): - m_plater(_plater), m_stop_slicing(stop_slicing) + Job(priv *_plater): m_plater(_plater) { Bind(wxEVT_THREAD, [this](const wxThreadEvent& evt){ auto msg = evt.GetString(); if(! msg.empty()) plater().statusbar()->set_status_text(msg); - if(! m_range) return; + if(m_finalized) return; plater().statusbar()->set_progress(evt.GetInt()); if(evt.GetInt() == status_range()) { @@ -1312,23 +1323,25 @@ struct Plater::priv plater().statusbar()->set_cancel_callback(); wxEndBusyCursor(); - // Do a full refresh of scene tree, including regenerating - // all the GLVolumes. FIXME The update function shall just - // reload the modified matrices. - if(! was_canceled()) plater().update(true); + finalize(); // dont do finalization again for the same process - m_range = 0; + m_finalized = true; } }); } + Job(const Job&) = delete; + Job(Job&&) = default; + Job& operator=(const Job&) = delete; + Job& operator=(Job&&) = default; + virtual void process() = 0; - void start() { // Start the job. No effect if the job is already running + void start() { // Start the job. No effect if the job is already running if(! m_running.load()) { - if(m_stop_slicing) plater().background_process.stop(); + prepare(); // Save the current status indicatior range and push the new one m_range = plater().statusbar()->get_range(); @@ -1340,6 +1353,8 @@ struct Plater::priv m_canceled.store(true); }); + m_finalized = false; + // Changing cursor to busy wxBeginBusyCursor(); @@ -1358,7 +1373,7 @@ struct Plater::priv // 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 join(int timeout_ms = 0) const { if(!m_ftr.valid()) return true; if(timeout_ms <= 0) @@ -1374,21 +1389,85 @@ struct Plater::priv void cancel() { m_canceled.store(true); } }; - class ArrangeJob: public Job { - int count = 0; - public: - using Job::Job; - int status_range() const override { return count; } - void set_count(int c) { count = c; } - void process() override; - } arrange_job {this}; + enum class Jobs : size_t { + Arrange, + Rotoptimize + }; - class RotoptimizeJob: public Job { + // Jobs defined inside the group class will be managed so that only one can + // run at a time. Also, the background process will be stopped if a job is + // started. + class ExclusiveJobGroup { + + static const int ABORT_WAIT_MAX_MS = 10000; + + priv * m_plater; + + class ArrangeJob : public Job + { + int count = 0; + + protected: + void prepare() override + { + count = 0; + for (auto obj : plater().model.objects) + count += int(obj->instances.size()); + } + + public: + using Job::Job; + int status_range() const override { return count; } + void set_count(int c) { count = c; } + void process() override; + } arrange_job{m_plater}; + + class RotoptimizeJob : public Job + { + public: + using Job::Job; + void process() override; + } rotoptimize_job{m_plater}; + + std::vector> m_jobs{arrange_job, + rotoptimize_job}; + public: - using Job::Job; - void process() override; - } rotoptimize_job {this}; - + + ExclusiveJobGroup(priv *_plater): m_plater(_plater) {} + + void start(Jobs jid) { + m_plater->background_process.stop(); + stop_all(); + m_jobs[size_t(jid)].get().start(); + } + + void cancel_all() { for (Job& j : m_jobs) j.cancel(); } + + void join_all(int wait_ms = 0) + { + std::vector aborted(m_jobs.size(), false); + + for (size_t jid = 0; jid < m_jobs.size(); ++jid) + aborted[jid] = m_jobs[jid].get().join(wait_ms); + + if (!all_of(aborted)) + BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; + } + + void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); } + + const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; } + + bool is_any_running() const + { + return std::any_of(m_jobs.begin(), + m_jobs.end(), + [](const Job &j) { return j.is_running(); }); + } + + } m_ui_jobs{this}; + bool delayed_scene_refresh; std::string delayed_error_message; @@ -2274,48 +2353,43 @@ void Plater::priv::mirror(Axis axis) void Plater::priv::arrange() { - if(!arrange_job.is_running()) { - int count = 0; - for(auto obj : model.objects) count += int(obj->instances.size()); - arrange_job.set_count(count); - arrange_job.start(); - } + 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() { - - rotoptimize_job.start(); + m_ui_jobs.start(Jobs::Rotoptimize); } -void Plater::priv::ArrangeJob::process() { - +void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { // TODO: we should decide whether to allow arrange when the search is // running we should probably disable explicit slicing and background // processing - static const std::string arrangestr = L("Arranging"); - - auto& config = plater().config; - auto& view3D = plater().view3D; - auto& model = plater().model; + static const auto arrangestr = _(L("Arranging")); + + auto &config = plater().config; + auto &view3D = plater().view3D; + auto &model = plater().model; // 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) { + double dist = 6; // PrintConfig::min_object_distance(config); + if (plater().printer_technology == ptFFF) { dist = PrintConfig::min_object_distance(config); } - auto min_obj_distance = coord_t(dist/SCALING_FACTOR); + auto min_obj_distance = coord_t(dist / SCALING_FACTOR); - const auto *bed_shape_opt = config->opt("bed_shape"); + const auto *bed_shape_opt = config->opt( + "bed_shape"); assert(bed_shape_opt); - auto& bedpoints = bed_shape_opt->values; - Polyline bed; bed.points.reserve(bedpoints.size()); - for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); + auto & bedpoints = bed_shape_opt->values; + Polyline bed; + bed.points.reserve(bedpoints.size()); + for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); update_status(0, arrangestr); @@ -2333,94 +2407,115 @@ void Plater::priv::ArrangeJob::process() { bed, hint, false, // create many piles not just one pile - [this](unsigned st) { if(st > 0) update_status(count - int(st), arrangestr); }, - [this] () { return was_canceled(); }); - } catch(std::exception& /*e*/) { - GUI::show_error(plater().q, L("Could not arrange model objects! " - "Some geometries may be invalid.")); + [this](unsigned st) { + if (st > 0) + update_status(count - int(st), arrangestr); + }, + [this]() { return was_canceled(); }); + } catch (std::exception & /*e*/) { + GUI::show_error(plater().q, + L("Could not arrange model objects! " + "Some geometries may be invalid.")); } - - update_status(count, was_canceled() ? L("Arranging canceled.") : L("Arranging done.")); + + update_status(count, + was_canceled() ? _(L("Arranging canceled.")) + : _(L("Arranging done."))); // it remains to move the wipe tower: - view3D->get_canvas3d()->arrange_wipe_tower(wti); + view3D->get_canvas3d()->arrange_wipe_tower(wti); } -void Plater::priv::RotoptimizeJob::process() +void Plater::priv::ExclusiveJobGroup::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(); } - ); + ModelObject *o = plater().model.objects[size_t(obj_idx)]; - const auto *bed_shape_opt = plater().config->opt("bed_shape"); + 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(); }); + + const auto *bed_shape_opt = plater().config->opt( + "bed_shape"); assert(bed_shape_opt); - auto& bedpoints = bed_shape_opt->values; - Polyline bed; bed.points.reserve(bedpoints.size()); - for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); + auto & bedpoints = bed_shape_opt->values; + Polyline bed; + bed.points.reserve(bedpoints.size()); + for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); double mindist = 6.0; // FIXME - double offs = mindist / 2.0 - EPSILON; + double offs = mindist / 2.0 - EPSILON; - if(! was_canceled()) // wasn't canceled - for(ModelInstance * oi : o->instances) { - oi->set_rotation({r[X], r[Y], r[Z]}); + if (!was_canceled()) // wasn't canceled + for (ModelInstance *oi : o->instances) { + oi->set_rotation({r[X], r[Y], r[Z]}); - auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix()); + auto trchull = o->convex_hull_2d( + oi->get_transformation().get_matrix()); - namespace opt = libnest2d::opt; - opt::StopCriteria stopcr; - stopcr.relative_score_difference = 0.01; - stopcr.max_iterations = 10000; - stopcr.stop_score = 0.0; - opt::GeneticOptimizer solver(stopcr); - Polygon pbed(bed); + namespace opt = libnest2d::opt; + opt::StopCriteria stopcr; + stopcr.relative_score_difference = 0.01; + stopcr.max_iterations = 10000; + stopcr.stop_score = 0.0; + opt::GeneticOptimizer solver(stopcr); + Polygon pbed(bed); - auto bin = pbed.bounding_box(); - double binw = bin.size()(X) * SCALING_FACTOR - offs; - double binh = bin.size()(Y) * SCALING_FACTOR - offs; + auto bin = pbed.bounding_box(); + double binw = bin.size()(X) * SCALING_FACTOR - offs; + double binh = bin.size()(Y) * SCALING_FACTOR - offs; - auto result = solver.optimize_min([&trchull, binw, binh](double rot){ - auto chull = trchull; - chull.rotate(rot); + auto result = solver.optimize_min( + [&trchull, binw, binh](double rot) { + auto chull = trchull; + chull.rotate(rot); - auto bb = chull.bounding_box(); - double bbw = bb.size()(X) * SCALING_FACTOR; - double bbh = bb.size()(Y) * SCALING_FACTOR; + auto bb = chull.bounding_box(); + double bbw = bb.size()(X) * SCALING_FACTOR; + double bbh = bb.size()(Y) * SCALING_FACTOR; - auto wdiff = bbw - binw; - auto hdiff = bbh - binh; - double diff = 0; - if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff; - if(wdiff > 0) diff += wdiff; - if(hdiff > 0) diff += hdiff; + auto wdiff = bbw - binw; + auto hdiff = bbh - binh; + double diff = 0; + if (wdiff < 0 && hdiff < 0) diff = wdiff + hdiff; + if (wdiff > 0) diff += wdiff; + if (hdiff > 0) diff += hdiff; - return diff; - }, opt::initvals(0.0), opt::bound(-PI/2, PI/2)); + return diff; + }, + opt::initvals(0.0), + opt::bound(-PI / 2, PI / 2)); - double r = std::get<0>(result.optimum); + double r = std::get<0>(result.optimum); - Vec3d rt = oi->get_rotation(); rt(Z) += r; - oi->set_rotation(rt); + Vec3d rt = oi->get_rotation(); + rt(Z) += r; + oi->set_rotation(rt); - arr::WipeTowerInfo wti; // useless in SLA context - arr::find_new_position(plater().model, o->instances, - coord_t(mindist/SCALING_FACTOR), bed, wti); - - // Correct the z offset of the object which was corrupted be the rotation - o->ensure_on_bed(); - - update_status(100, L("Orientation found.")); - } else { - update_status(100, L("Orientation search canceled.")); + arr::WipeTowerInfo wti; // useless in SLA context + arr::find_new_position(plater().model, + o->instances, + coord_t(mindist / SCALING_FACTOR), + bed, + wti); + + // Correct the z offset of the object which was corrupted be + // the rotation + o->ensure_on_bed(); + + update_status(100, _(L("Orientation found."))); + } + else { + update_status(100, _(L("Orientation search canceled."))); } } @@ -2602,7 +2697,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation) // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState. bool Plater::priv::restart_background_process(unsigned int state) { - if (arrange_job.is_running() || rotoptimize_job.is_running()) { + if (m_ui_jobs.is_any_running()) { // Avoid a race condition return false; } @@ -2833,7 +2928,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) { if (evt.status.percent >= -1) { - if (arrange_job.is_running() || rotoptimize_job.is_running()) { + if (m_ui_jobs.is_any_running()) { // Avoid a race condition return; } @@ -3314,7 +3409,7 @@ bool Plater::priv::can_fix_through_netfabb() const bool Plater::priv::can_increase_instances() const { - if (arrange_job.is_running() || rotoptimize_job.is_running()) { + if (m_ui_jobs.is_any_running()) { return false; } @@ -3324,7 +3419,7 @@ bool Plater::priv::can_increase_instances() const bool Plater::priv::can_decrease_instances() const { - if (arrange_job.is_running() || rotoptimize_job.is_running()) { + if (m_ui_jobs.is_any_running()) { return false; } @@ -3344,7 +3439,7 @@ bool Plater::priv::can_split_to_volumes() const bool Plater::priv::can_arrange() const { - return !model.objects.empty() && !arrange_job.is_running(); + return !model.objects.empty() && !m_ui_jobs.is_any_running(); } bool Plater::priv::can_layers_editing() const @@ -3471,20 +3566,7 @@ void Plater::load_files(const std::vector& input_files, bool load_m void Plater::update() { p->update(); } -void Plater::stop_jobs() -{ - static const int ABORT_WAIT_MAX_MS = 10000; - bool aborted = false; - - p->rotoptimize_job.cancel(); - aborted = p->rotoptimize_job.join(ABORT_WAIT_MAX_MS); - - p->arrange_job.cancel(); - aborted &= p->arrange_job.join(ABORT_WAIT_MAX_MS); - - if(!aborted) - BOOST_LOG_TRIVIAL(error) << "Could not abort an optimization job!"; -} +void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); } void Plater::update_ui_from_settings() { p->update_ui_from_settings(); } @@ -3792,7 +3874,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) if (!path.Lower().EndsWith(".3mf")) return; - DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); + DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); const std::string path_u8 = into_u8(path); wxBusyCursor wait; if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) { @@ -3846,7 +3928,7 @@ void Plater::reslice_SLA_supports(const ModelObject &object) if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) this->p->view3D->reload_scene(false); - if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)) + if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)) // Nothing to do on empty input or invalid configuration. return; From 415877d79e7edf26868e71afdbc3277948dec765 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 18 Jun 2019 18:02:40 +0200 Subject: [PATCH 145/627] Experiments with msw_rescale fixing --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 33 +++++++++++++++------ src/slic3r/GUI/GUI_Utils.hpp | 36 ++++++++++++++++++++--- src/slic3r/GUI/wxExtensions.cpp | 2 ++ 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 5bf25f3fc0..925ba65bcd 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -130,8 +130,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : auto manifold_warning_icon = [this](wxWindow* parent) { m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_fix_throught_netfab_bitmap); +// auto sizer = new wxBoxSizer(wxHORIZONTAL); +// sizer->Add(m_fix_throught_netfab_bitmap); if (is_windows10()) m_fix_throught_netfab_bitmap->Bind(wxEVT_CONTEXT_MENU, [this](wxCommandEvent &e) @@ -144,17 +144,19 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors_list()); }); - return sizer; +// return sizer; + return m_fix_throught_netfab_bitmap; }; - line.append_widget(manifold_warning_icon); + // line.append_widget(manifold_warning_icon); + line.near_label_widget = manifold_warning_icon; def.label = ""; def.gui_type = "legend"; def.tooltip = L("Object name"); #ifdef __APPLE__ - def.width = 19; + def.width = 20; #else - def.width = 21; + def.width = 22; #endif def.set_default_value(new ConfigOptionString{ " " }); line.append_option(Option(def, "object_name")); @@ -233,10 +235,19 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // call back for a rescale of button "Set uniform scale" m_og->rescale_near_label_widget = [this](wxWindow* win) { + // rescale lock icon auto *ctrl = dynamic_cast(win); - if (ctrl == nullptr) + if (ctrl != nullptr) { + ctrl->msw_rescale(); return; - ctrl->msw_rescale(); + } + + if (win == m_fix_throught_netfab_bitmap) + return; + + // rescale "place" of the empty icon (to correct layout of the "Size" and "Scale") + if (dynamic_cast(win) != nullptr) + win->SetMinSize(create_scaled_bitmap(m_parent, "one_layer_lock_on.png").GetSize()); }; } @@ -431,6 +442,7 @@ void ObjectManipulation::emulate_kill_focus() void ObjectManipulation::update_warning_icon_state(const wxString& tooltip) { m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); + m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0,0) : m_manifold_warning_bmp.bmp().GetSize()); m_fix_throught_netfab_bitmap->SetToolTip(tooltip); } @@ -664,7 +676,10 @@ void ObjectManipulation::msw_rescale() { msw_rescale_word_local_combo(m_word_local_combo); m_manifold_warning_bmp.msw_rescale(); - m_fix_throught_netfab_bitmap->SetBitmap(m_manifold_warning_bmp.bmp()); + + const wxString& tooltip = m_fix_throught_netfab_bitmap->GetToolTipText(); + m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); + m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0, 0) : m_manifold_warning_bmp.bmp().GetSize()); get_og()->msw_rescale(); } diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index a17bbf6d33..39b68ea7a0 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -72,6 +72,8 @@ public: this->Bind(EVT_DPI_CHANGED, [this](const DpiChangedEvent &evt) { m_scale_factor = (float)evt.dpi / (float)DPI_DEFAULT; + m_new_font_point_size = get_default_font_for_dpi(evt.dpi).GetPointSize(); + if (!m_can_rescale) return; @@ -124,6 +126,8 @@ private: float m_prev_scale_factor; bool m_can_rescale{ true }; + int m_new_font_point_size; + // void recalc_font() // { // wxClientDC dc(this); @@ -148,18 +152,42 @@ private: window->Layout(); } + void scale_win_font(wxWindow *window, const int font_point_size) + { + wxFont new_font(window->GetFont()); + new_font.SetPointSize(font_point_size); + window->SetFont(new_font); + } + + // recursive function for scaling fonts for all controls in Window + void scale_controls_fonts(wxWindow *window, const int font_point_size) + { + auto children = window->GetChildren(); + + for (auto child : children) { + scale_controls_fonts(child, font_point_size); + scale_win_font(child, font_point_size); + } + + window->Layout(); + } + void rescale(const wxRect &suggested_rect) { this->Freeze(); - const float relative_scale_factor = m_scale_factor / m_prev_scale_factor; +// const float relative_scale_factor = m_scale_factor / m_prev_scale_factor; // rescale fonts of all controls - scale_controls_fonts(this, relative_scale_factor); - this->SetFont(this->GetFont().Scaled(relative_scale_factor)); +// scale_controls_fonts(this, relative_scale_factor); + scale_controls_fonts(this, m_new_font_point_size); + +// this->SetFont(this->GetFont().Scaled(relative_scale_factor)); + scale_win_font(this, m_new_font_point_size); // rescale normal_font value - m_normal_font = m_normal_font.Scaled(relative_scale_factor); +// m_normal_font = m_normal_font.Scaled(relative_scale_factor); + m_normal_font = this->GetFont(); // An analog of em_unit value from GUI_App. m_em_unit = std::max(10, 10 * m_scale_factor); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 76ba853dc3..546e4889f7 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -2478,6 +2478,8 @@ void LockButton::msw_rescale() m_bmp_lock_off .msw_rescale(); m_bmp_unlock_on .msw_rescale(); m_bmp_unlock_off.msw_rescale(); + + enter_button(false); } void LockButton::enter_button(const bool enter) From 78610de4730cc85baea984c91d00e776b94d7574 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 19 Jun 2019 10:18:51 +0200 Subject: [PATCH 146/627] Fix build on vs2013 Fix 2 for vs2013 --- src/slic3r/GUI/Plater.cpp | 45 +++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e729a2ff7b..0a8499f709 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1332,10 +1332,22 @@ struct Plater::priv }); } + // TODO: use this when we all migrated to VS2019 + // Job(const Job&) = delete; + // Job(Job&&) = default; + // Job& operator=(const Job&) = delete; + // Job& operator=(Job&&) = default; Job(const Job&) = delete; - Job(Job&&) = default; Job& operator=(const Job&) = delete; - Job& operator=(Job&&) = default; + Job(Job &&o) : + m_range(o.m_range), + m_ftr(std::move(o.m_ftr)), + m_plater(o.m_plater), + m_finalized(o.m_finalized) + { + m_running.store(o.m_running.load()); + m_canceled.store(o.m_canceled.load()); + } virtual void process() = 0; @@ -1417,26 +1429,37 @@ struct Plater::priv } public: - using Job::Job; + //using Job::Job; + ArrangeJob(priv * pltr): Job(pltr) {} int status_range() const override { return count; } void set_count(int c) { count = c; } void process() override; - } arrange_job{m_plater}; + } arrange_job/*{m_plater}*/; class RotoptimizeJob : public Job { public: - using Job::Job; + //using Job::Job; + RotoptimizeJob(priv * pltr): Job(pltr) {} void process() override; - } rotoptimize_job{m_plater}; + } rotoptimize_job/*{m_plater}*/; - std::vector> m_jobs{arrange_job, - rotoptimize_job}; + // 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> m_jobs/*{arrange_job, + rotoptimize_job}*/; public: - - ExclusiveJobGroup(priv *_plater): m_plater(_plater) {} - + ExclusiveJobGroup(priv *_plater) + : m_plater(_plater) + , arrange_job(m_plater) + , rotoptimize_job(m_plater) + , m_jobs({arrange_job, rotoptimize_job}) + {} + void start(Jobs jid) { m_plater->background_process.stop(); stop_all(); From 5a1e1bc10c887a3408194cd546b2a704b94d08f3 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 19 Jun 2019 10:15:01 +0200 Subject: [PATCH 147/627] GUI_ObjectManipulation.cpp - Removed implicit capture by value in some of the lambdas --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 310000ecc3..4ccaa68479 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -185,7 +185,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : unsigned int axis_idx = (axis[0] - 'x'); // 0, 1 or 2 // We will add a button to toggle mirroring to each axis: - auto mirror_button = [=](wxWindow* parent) { + auto mirror_button = [this, mirror_btn_width, axis_idx, &label](wxWindow* parent) { wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width); auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off.png", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); btn->SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), label)); @@ -195,7 +195,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent &e) { Axis axis = (Axis)(axis_idx + X); if (m_mirror_buttons[axis_idx].second == mbHidden) return; @@ -258,13 +258,13 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : return btn; }; // Add reset scale button - auto reset_scale_button = [=](wxWindow* parent) { + auto reset_scale_button = [this](wxWindow* parent) { auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); btn->SetToolTip(_(L("Reset scale"))); m_reset_scale_button = btn; auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn, wxBU_EXACTFIT); - btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { change_scale_value(0, 100.); change_scale_value(1, 100.); change_scale_value(2, 100.); @@ -275,13 +275,13 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : } else if (option_name == "Rotation") { // Add reset rotation button - auto reset_rotation_button = [=](wxWindow* parent) { + auto reset_rotation_button = [this](wxWindow* parent) { auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); btn->SetToolTip(_(L("Reset rotation"))); m_reset_rotation_button = btn; auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn, wxBU_EXACTFIT); - btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); From 3d8c3804fab810568adedce76836ea97e7e1c71d Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 19 Jun 2019 10:46:42 +0200 Subject: [PATCH 148/627] Added 'drop to bed' button into object manipulation panel --- resources/icons/drop_to_bed.png | Bin 0 -> 528 bytes src/slic3r/GUI/3DScene.hpp | 2 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 57 +++++++++++++++++++++- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 1 + 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 resources/icons/drop_to_bed.png diff --git a/resources/icons/drop_to_bed.png b/resources/icons/drop_to_bed.png new file mode 100644 index 0000000000000000000000000000000000000000..b60e1b137d9d12a1cfc177de1d51eb7c7093aa7b GIT binary patch literal 528 zcmV+r0`L8aP)pZ4MY$`uvwNR!r;okMbKJk8Mr7Sc?B_4 zw3vhk*&h#jJRY=Z9WH}5j=0iAViLGQMuMqqF{$nne8*(H`8hk~-;c&PlNg4nEF!8fo zAB{$(kqK(G+PT~9o+ZhoNVD0z(sliTyuVApvaFM$sJEMv_I2*lK8?hfruj57K`9N$uNw6@pwG@@d=4U;%B$pZI{dCsB#M&HN# z(T$${eQO-sVHtp7jYUy>bJR3V$(^0?{b#F314aL;=?9riril=m#u&2t-indexed_vertex_array.empty(); } bool indexed() const { return this->indexed_vertex_array.indexed(); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 4ccaa68479..7363b2c166 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -17,6 +17,28 @@ namespace Slic3r namespace GUI { + +// Helper function to be used by drop to bed button. Returns lowest point of this +// volume in world coordinate system. +static double get_volume_min_z(const GLVolume* volume) +{ + const Transform3f& world_matrix = volume->world_matrix().cast(); + + // need to get the ModelVolume pointer + const ModelObject* mo = wxGetApp().model_objects()->at(volume->composite_id.object_id); + const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id]; + const TriangleMesh& hull = mv->get_convex_hull(); + + float min_z = std::numeric_limits::max(); + for (const stl_facet& facet : hull.stl.facet_start) { + for (int i = 0; i < 3; ++ i) + min_z = std::min(min_z, Vec3f::UnitZ().dot(world_matrix * facet.vertex[i])); + } + return min_z; +} + + + static wxBitmapComboBox* create_word_local_combo(wxWindow *parent) { wxSize size(15 * wxGetApp().em_unit(), -1); @@ -310,6 +332,33 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : }; line.append_widget(reset_rotation_button); } + else if (option_name == "Position") { + // Add drop to bed button + auto drop_to_bed_button = [=](wxWindow* parent) { + auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed.png")); + btn->SetToolTip(_(L("Drop to bed"))); + m_drop_to_bed_button = btn; + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn, wxBU_EXACTFIT); + btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + // ??? + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + + if (selection.is_single_volume() || selection.is_single_modifier()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + + Vec3d diff = m_cache.position - Vec3d(0., 0., get_volume_min_z(volume)); + + change_position_value(0, diff.x()); + change_position_value(1, diff.y()); + change_position_value(2, diff.z()); + } + }); + return sizer; + }; + line.append_widget(drop_to_bed_button); + } // Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment else if (option_name == "Size") { line.near_label_widget = [this](wxWindow* parent) { @@ -534,11 +583,13 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_rotation = false; bool show_scale = false; + bool show_drop_to_bed = false; if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation; Vec3d scale; + double min_z = 0.; if (selection.is_single_full_instance()) { rotation = volume->get_instance_rotation(); @@ -547,14 +598,17 @@ void ObjectManipulation::update_reset_buttons_visibility() else { rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); + min_z = get_volume_min_z(volume); } show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); + show_drop_to_bed = (std::abs(min_z) > EPSILON); } - wxGetApp().CallAfter([this, show_rotation, show_scale]{ + wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed]{ m_reset_rotation_button->Show(show_rotation); m_reset_scale_button->Show(show_scale); + m_drop_to_bed_button->Show(show_drop_to_bed); }); } @@ -867,6 +921,7 @@ void ObjectManipulation::msw_rescale() m_mirror_bitmap_hidden.msw_rescale(); m_reset_scale_button->msw_rescale(); m_reset_rotation_button->msw_rescale(); + m_drop_to_bed_button->msw_rescale(); get_og()->msw_rescale(); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index cc2154514d..e4e190b5bc 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -56,6 +56,7 @@ class ObjectManipulation : public OG_Settings // Non-owning pointers to the reset buttons, so we can hide and show them. ScalableButton* m_reset_scale_button = nullptr; ScalableButton* m_reset_rotation_button = nullptr; + ScalableButton* m_drop_to_bed_button = nullptr; // Mirroring buttons and their current state enum MirrorButtonState { From b0b54ed0e6fa02a97cc8409b0b5a5c44e8dc3f8f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 19 Jun 2019 11:05:34 +0200 Subject: [PATCH 149/627] Fixed flickering of 3D view when moving objects on NVIDIA graphic cards --- src/slic3r/GUI/Plater.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0a8499f709..8e5ba2d3fa 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1770,7 +1770,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) void Plater::priv::update(bool force_full_scene_refresh) { - wxWindowUpdateLocker freeze_guard(q); + // the following line, when enabled, causes flickering on NVIDIA graphics cards +// wxWindowUpdateLocker freeze_guard(q); if (get_config("autocenter") == "1") { // auto *bed_shape_opt = config->opt("bed_shape"); // const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values); From 12133f95993ba33064b059e971f27e02fcc046d8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 19 Jun 2019 11:38:42 +0200 Subject: [PATCH 150/627] Code cleaning and last msw_rescale() improvements --- src/slic3r/GUI/GUI_Utils.hpp | 32 +++++++++++--------------------- src/slic3r/GUI/MainFrame.cpp | 4 +++- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 39b68ea7a0..c47714e464 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -64,6 +64,12 @@ public: m_prev_scale_factor = m_scale_factor; m_normal_font = get_default_font_for_dpi(dpi); + /* Because of default window font is a primary display font, + * We should set correct font for window before getting em_unit value. + */ +#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList + this->SetFont(m_normal_font); +#endif // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); @@ -139,19 +145,7 @@ private: // check if new scale is differ from previous bool is_new_scale_factor() const { return fabs(m_scale_factor - m_prev_scale_factor) > 0.001; } - // recursive function for scaling fonts for all controls in Window - void scale_controls_fonts(wxWindow *window, const float scale_f) - { - auto children = window->GetChildren(); - - for (auto child : children) { - scale_controls_fonts(child, scale_f); - child->SetFont(child->GetFont().Scaled(scale_f)); - } - - window->Layout(); - } - + // function for a font scaling of the window void scale_win_font(wxWindow *window, const int font_point_size) { wxFont new_font(window->GetFont()); @@ -175,22 +169,18 @@ private: void rescale(const wxRect &suggested_rect) { this->Freeze(); -// const float relative_scale_factor = m_scale_factor / m_prev_scale_factor; // rescale fonts of all controls -// scale_controls_fonts(this, relative_scale_factor); scale_controls_fonts(this, m_new_font_point_size); - -// this->SetFont(this->GetFont().Scaled(relative_scale_factor)); + // rescale current window font scale_win_font(this, m_new_font_point_size); - // rescale normal_font value -// m_normal_font = m_normal_font.Scaled(relative_scale_factor); + // set normal application font as a current window font m_normal_font = this->GetFont(); - // An analog of em_unit value from GUI_App. - m_em_unit = std::max(10, 10 * m_scale_factor); + // update em_unit value for new window font + m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); // rescale missed controls sizes and images on_dpi_changed(suggested_rect); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 7e5b3ec05a..47d012fdf5 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -38,10 +38,12 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S { // Fonts were created by the DPIFrame constructor for the monitor, on which the window opened. wxGetApp().update_fonts(this); +/* #ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList this->SetFont(this->normal_font()); #endif - + // Font is already set in DPIFrame constructor +*/ // Load the icon either from the exe, or from the ico file. #if _WIN32 { From 0481f33ceb4dd64098e3b0c9c324bc32a82ba5f3 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 19 Jun 2019 11:52:37 +0200 Subject: [PATCH 151/627] Drop to bed function now accounts for instance transformation --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 7363b2c166..d5445c6afe 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -348,7 +348,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : if (selection.is_single_volume() || selection.is_single_modifier()) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - Vec3d diff = m_cache.position - Vec3d(0., 0., get_volume_min_z(volume)); + const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); + Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); change_position_value(0, diff.x()); change_position_value(1, diff.y()); From 670a5632263114efeb18b0e7f8a43a8daa3ac301 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 19 Jun 2019 12:04:11 +0200 Subject: [PATCH 152/627] Fix for issue #2536 --- src/libslic3r/ModelArrange.cpp | 50 ++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index b323285ccb..fc03d0a439 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -318,7 +318,7 @@ class AutoArranger {}; // management and spatial index structures for acceleration. template class _ArrBase { -protected: +public: // Useful type shortcuts... using Placer = TPacker; @@ -327,6 +327,8 @@ protected: using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; using Pile = TMultiShape; + +protected: Packer m_pck; PConfig m_pconf; // Placement configuration @@ -564,7 +566,10 @@ public: // 2D shape from top view. using ShapeData2D = std::vector>; -ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& wti) { +ShapeData2D projectModelFromTop(const Slic3r::Model &model, + const WipeTowerInfo &wti, + double tolerance) +{ ShapeData2D ret; // Count all the items on the bin (all the object's instances) @@ -586,21 +591,32 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, const WipeTowerInfo& // Object instances should carry the same scaling and // x, y rotation that is why we use the first instance. { - ModelInstance *finst = objptr->instances.front(); - Vec3d rotation = finst->get_rotation(); - rotation.z() = 0.; - Transform3d trafo_instance = Geometry::assemble_transform(Vec3d::Zero(), rotation, finst->get_scaling_factor(), finst->get_mirror()); + ModelInstance *finst = objptr->instances.front(); + Vec3d rotation = finst->get_rotation(); + rotation.z() = 0.; + Transform3d trafo_instance = Geometry::assemble_transform( + Vec3d::Zero(), + rotation, + finst->get_scaling_factor(), + finst->get_mirror()); Polygon p = objptr->convex_hull_2d(trafo_instance); - assert(! p.points.empty()); - - // this may happen for malformed models, see: https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (p.points.empty()) - continue; + + assert(!p.points.empty()); + // this may happen for malformed models, see: + // https://github.com/prusa3d/PrusaSlicer/issues/2209 + if (p.points.empty()) continue; + + if(tolerance > EPSILON) { + Polygons pp { p }; + pp = p.simplify(double(scaled(tolerance))); + if (!pp.empty()) p = pp.front(); + } + p.reverse(); assert(!p.is_counter_clockwise()); - p.append(p.first_point()); clpath = Slic3rMultiPoint_to_ClipperPath(p); + auto firstp = clpath.front(); clpath.emplace_back(firstp); } Vec3d rotation0 = objptr->instances.front()->get_rotation(); @@ -780,9 +796,9 @@ bool arrange(Model &model, // The model with the geometries std::function stopcondition) { bool ret = true; - + // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model, wti); + auto shapemap = arr::projectModelFromTop(model, wti, 0.1); // Copy the references for the shapes only as the arranger expects a // sequence of objects convertible to Item or ClipperPolygon @@ -807,7 +823,7 @@ bool arrange(Model &model, // The model with the geometries static_cast(bbb.min(0)), static_cast(bbb.min(1)) }, - { + { static_cast(bbb.max(0)), static_cast(bbb.max(1)) }); @@ -881,9 +897,9 @@ void find_new_position(const Model &model, coord_t min_obj_distance, const Polyline &bed, WipeTowerInfo& wti) -{ +{ // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model, wti); + auto shapemap = arr::projectModelFromTop(model, wti, 0.1); // Copy the references for the shapes only as the arranger expects a // sequence of objects convertible to Item or ClipperPolygon From a3e6412113b1760ff2ab980cbecffe6505426ba2 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 19 Jun 2019 13:01:18 +0200 Subject: [PATCH 153/627] Enabled perspective camera --- src/slic3r/GUI/Camera.cpp | 77 +++++++++++++++++++---------------- src/slic3r/GUI/Camera.hpp | 14 ++++--- src/slic3r/GUI/GLCanvas3D.cpp | 2 + 3 files changed, 52 insertions(+), 41 deletions(-) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index f6cefc8010..1620548ef9 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -22,19 +22,19 @@ static const float VIEW_REAR[2] = { 180.0f, 90.0f }; namespace Slic3r { namespace GUI { -const float Camera::DefaultDistance = 1000.0f; +const double Camera::DefaultDistance = 1000.0; double Camera::FrustrumMinZSize = 50.0; double Camera::FrustrumZMargin = 10.0; Camera::Camera() - : type(Ortho) - , zoom(1.0f) + : zoom(1.0f) , phi(45.0f) - , distance(DefaultDistance) , requires_zoom_to_bed(false) , inverted_phi(false) - , m_theta(45.0f) + , m_type(Ortho) , m_target(Vec3d::Zero()) + , m_theta(45.0f) + , m_distance(DefaultDistance) , m_view_matrix(Transform3d::Identity()) , m_projection_matrix(Transform3d::Identity()) { @@ -42,18 +42,27 @@ Camera::Camera() std::string Camera::get_type_as_string() const { - switch (type) + switch (m_type) { default: case Unknown: return "unknown"; -// case Perspective: -// return "perspective"; + case Perspective: + return "perspective"; case Ortho: return "orthographic"; }; } +void Camera::select_next_type() +{ + unsigned char next = (unsigned char)m_type + 1; + if (next == (unsigned char)Num_types) + next = 1; + + m_type = (EType)next; +} + void Camera::set_target(const Vec3d& target) { m_target = target; @@ -114,7 +123,7 @@ void Camera::apply_view_matrix() const double theta_rad = Geometry::deg2rad(-(double)m_theta); double phi_rad = Geometry::deg2rad((double)phi); double sin_theta = ::sin(theta_rad); - Vec3d camera_pos = m_target + (double)distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad)); + Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad)); glsafe(::glMatrixMode(GL_MODELVIEW)); glsafe(::glLoadIdentity()); @@ -131,27 +140,36 @@ void Camera::apply_projection(const BoundingBoxf3& box) const { m_frustrum_zs = calc_tight_frustrum_zs_around(box); - switch (type) + double w = (double)m_viewport[2]; + double h = (double)m_viewport[3]; + double two_zoom = 2.0 * zoom; + if (two_zoom != 0.0) { + double inv_two_zoom = 1.0 / two_zoom; + w *= inv_two_zoom; + h *= inv_two_zoom; + } + + glsafe(::glMatrixMode(GL_PROJECTION)); + glsafe(::glLoadIdentity()); + + switch (m_type) + { + default: case Ortho: { - double w2 = (double)m_viewport[2]; - double h2 = (double)m_viewport[3]; - double two_zoom = 2.0 * zoom; - if (two_zoom != 0.0) - { - double inv_two_zoom = 1.0 / two_zoom; - w2 *= inv_two_zoom; - h2 *= inv_two_zoom; - } - - apply_ortho_projection(-w2, w2, -h2, h2, m_frustrum_zs.first, m_frustrum_zs.second); + glsafe(::glOrtho(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second)); break; } -// case Perspective: -// { -// } + case Perspective: + { + glsafe(::glFrustum(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second)); + break; } + } + + glsafe(::glGetDoublev(GL_PROJECTION_MATRIX, m_projection_matrix.data())); + glsafe(::glMatrixMode(GL_MODELVIEW)); } #if ENABLE_CAMERA_STATISTICS @@ -187,17 +205,6 @@ void Camera::debug_render() const } #endif // ENABLE_CAMERA_STATISTICS -void Camera::apply_ortho_projection(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max) const -{ - glsafe(::glMatrixMode(GL_PROJECTION)); - glsafe(::glLoadIdentity()); - - glsafe(::glOrtho(x_min, x_max, y_min, y_max, z_min, z_max)); - glsafe(::glGetDoublev(GL_PROJECTION_MATRIX, m_projection_matrix.data())); - - glsafe(::glMatrixMode(GL_MODELVIEW)); -} - std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const { std::pair ret = std::make_pair(DBL_MAX, -DBL_MAX); diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 18dec6083c..3f8d676b5b 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -9,29 +9,29 @@ namespace GUI { struct Camera { - static const float DefaultDistance; + static const double DefaultDistance; static double FrustrumMinZSize; static double FrustrumZMargin; enum EType : unsigned char { Unknown, -// Perspective, + Perspective, Ortho, Num_types }; - EType type; float zoom; float phi; - // Distance between camera position and camera target measured along the camera Z axis - float distance; bool requires_zoom_to_bed; bool inverted_phi; private: + EType m_type; Vec3d m_target; float m_theta; + // Distance between camera position and camera target measured along the camera Z axis + double m_distance; mutable std::array m_viewport; mutable Transform3d m_view_matrix; @@ -43,7 +43,10 @@ private: public: Camera(); + EType get_type() const { return m_type; } std::string get_type_as_string() const; + void set_type(EType type) { m_type = type; } + void select_next_type(); const Vec3d& get_target() const { return m_target; } void set_target(const Vec3d& target); @@ -78,7 +81,6 @@ public: #endif // ENABLE_CAMERA_STATISTICS private: - void apply_ortho_projection(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max) const; // returns tight values for nearZ and farZ plane around the given bounding box // the camera MUST be outside of the bounding box in eye coordinate of the given box std::pair calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 021a72dcb0..7f8d915836 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2365,6 +2365,8 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } case 'B': case 'b': { zoom_to_bed(); break; } + case 'C': + case 'c': { m_camera.select_next_type(); m_dirty = true; break; } case 'I': case 'i': { set_camera_zoom(1.0f); break; } case 'O': From 89e39e38955b03500e4e08388200168cb1f5bb0c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 19 Jun 2019 13:19:11 +0200 Subject: [PATCH 154/627] Eliminate magic constant --- src/libslic3r/ModelArrange.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index fc03d0a439..a440c3999d 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -782,6 +782,8 @@ BedShapeHint bedShape(const Polyline &bed) { return ret; } +static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; + // The final client function to arrange the Model. A progress indicator and // a stop predicate can be also be passed to control the process. bool arrange(Model &model, // The model with the geometries @@ -798,7 +800,7 @@ bool arrange(Model &model, // The model with the geometries bool ret = true; // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model, wti, 0.1); + auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); // Copy the references for the shapes only as the arranger expects a // sequence of objects convertible to Item or ClipperPolygon @@ -899,7 +901,7 @@ void find_new_position(const Model &model, WipeTowerInfo& wti) { // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model, wti, 0.1); + auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); // Copy the references for the shapes only as the arranger expects a // sequence of objects convertible to Item or ClipperPolygon From da8179d9c7a7f1892775a3f6d858db60a1c55501 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 19 Jun 2019 14:18:51 +0200 Subject: [PATCH 155/627] More camera related functionalities moved from GLCanvas3D to Camera --- src/slic3r/GUI/Camera.cpp | 98 +++++++++++++++++- src/slic3r/GUI/Camera.hpp | 10 +- src/slic3r/GUI/GLCanvas3D.cpp | 116 ++++------------------ src/slic3r/GUI/GLCanvas3D.hpp | 5 +- src/slic3r/GUI/GLSelectionRectangle.cpp | 2 +- src/slic3r/GUI/GLToolbar.cpp | 12 +-- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 +- 7 files changed, 131 insertions(+), 114 deletions(-) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 1620548ef9..a3ecb5e541 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -27,13 +27,13 @@ double Camera::FrustrumMinZSize = 50.0; double Camera::FrustrumZMargin = 10.0; Camera::Camera() - : zoom(1.0f) - , phi(45.0f) + : phi(45.0f) , requires_zoom_to_bed(false) , inverted_phi(false) , m_type(Ortho) , m_target(Vec3d::Zero()) , m_theta(45.0f) + , m_zoom(1.0) , m_distance(DefaultDistance) , m_view_matrix(Transform3d::Identity()) , m_projection_matrix(Transform3d::Identity()) @@ -83,6 +83,22 @@ void Camera::set_theta(float theta, bool apply_limit) } } +void Camera::set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h) +{ + zoom = std::max(std::min(zoom, 4.0), -4.0) / 10.0; + zoom = m_zoom / (1.0 - zoom); + + // Don't allow to zoom too far outside the scene. + double zoom_min = calc_zoom_to_bounding_box_factor(max_box, canvas_w, canvas_h); + if (zoom_min > 0.0) + zoom = std::max(zoom, zoom_min * 0.7); + + // Don't allow to zoom too close to the scene. + zoom = std::min(zoom, 100.0); + + m_zoom = zoom; +} + bool Camera::select_view(const std::string& direction) { const float* dir_vec = nullptr; @@ -142,7 +158,8 @@ void Camera::apply_projection(const BoundingBoxf3& box) const double w = (double)m_viewport[2]; double h = (double)m_viewport[3]; - double two_zoom = 2.0 * zoom; + + double two_zoom = 2.0 * m_zoom; if (two_zoom != 0.0) { double inv_two_zoom = 1.0 / two_zoom; @@ -172,6 +189,18 @@ void Camera::apply_projection(const BoundingBoxf3& box) const glsafe(::glMatrixMode(GL_MODELVIEW)); } +void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h) +{ + // Calculate the zoom factor needed to adjust the view around the given box. + double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h); + if (zoom > 0.0) + { + m_zoom = zoom; + // center view around box center + m_target = box.center(); + } +} + #if ENABLE_CAMERA_STATISTICS void Camera::debug_render() const { @@ -212,7 +241,7 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo Vec3d bb_min = box.min; Vec3d bb_max = box.max; - // bbox vertices in world space + // box vertices in world space std::vector vertices; vertices.reserve(8); vertices.push_back(bb_min); @@ -251,6 +280,67 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo return ret; } +double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const +{ + double max_bb_size = box.max_size(); + if (max_bb_size == 0.0) + return -1.0; + + // project the box vertices on a plane perpendicular to the camera forward axis + // then calculates the vertices coordinate on this plane along the camera xy axes + + // ensure that the view matrix is updated + apply_view_matrix(); + + Vec3d right = get_dir_right(); + Vec3d up = get_dir_up(); + Vec3d forward = get_dir_forward(); + + Vec3d bb_min = box.min; + Vec3d bb_max = box.max; + Vec3d bb_center = box.center(); + + // box vertices in world space + std::vector vertices; + vertices.reserve(8); + vertices.push_back(bb_min); + vertices.emplace_back(bb_max(0), bb_min(1), bb_min(2)); + vertices.emplace_back(bb_max(0), bb_max(1), bb_min(2)); + vertices.emplace_back(bb_min(0), bb_max(1), bb_min(2)); + vertices.emplace_back(bb_min(0), bb_min(1), bb_max(2)); + vertices.emplace_back(bb_max(0), bb_min(1), bb_max(2)); + vertices.push_back(bb_max); + vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2)); + + double max_x = 0.0; + double max_y = 0.0; + + // margin factor to give some empty space around the box + double margin_factor = 1.25; + + for (const Vec3d& v : vertices) + { + // project vertex on the plane perpendicular to camera forward axis + Vec3d pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2)); + Vec3d proj_on_plane = pos - pos.dot(forward) * forward; + + // calculates vertex coordinate along camera xy axes + double x_on_plane = proj_on_plane.dot(right); + double y_on_plane = proj_on_plane.dot(up); + + max_x = std::max(max_x, std::abs(x_on_plane)); + max_y = std::max(max_y, std::abs(y_on_plane)); + } + + if ((max_x == 0.0) || (max_y == 0.0)) + return -1.0f; + + max_x *= margin_factor; + max_y *= margin_factor; + + return std::min((double)canvas_w / (2.0 * max_x), (double)canvas_h / (2.0 * max_y)); +} + } // GUI } // Slic3r diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 3f8d676b5b..fe6571b056 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -21,7 +21,6 @@ struct Camera Num_types }; - float zoom; float phi; bool requires_zoom_to_bed; bool inverted_phi; @@ -30,6 +29,7 @@ private: EType m_type; Vec3d m_target; float m_theta; + double m_zoom; // Distance between camera position and camera target measured along the camera Z axis double m_distance; @@ -54,8 +54,11 @@ public: float get_theta() const { return m_theta; } void set_theta(float theta, bool apply_limit); + double get_zoom() const { return m_zoom; } + void set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h); + const BoundingBoxf3& get_scene_box() const { return m_scene_box; } - void set_scene_box(const BoundingBoxf3& box){ m_scene_box = box; } + void set_scene_box(const BoundingBoxf3& box) { m_scene_box = box; } bool select_view(const std::string& direction); @@ -76,6 +79,8 @@ public: void apply_view_matrix() const; void apply_projection(const BoundingBoxf3& box) const; + void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h); + #if ENABLE_CAMERA_STATISTICS void debug_render() const; #endif // ENABLE_CAMERA_STATISTICS @@ -84,6 +89,7 @@ private: // returns tight values for nearZ and farZ plane around the given bounding box // the camera MUST be outside of the bounding box in eye coordinate of the given box std::pair calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const; + double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const; }; } // GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 7f8d915836..d126c54459 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -357,7 +357,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) float half_w = 0.5f * (float)cnv_size.get_width(); float half_h = 0.5f * (float)cnv_size.get_height(); - float zoom = canvas.get_camera().zoom; + float zoom = (float)canvas.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom); @@ -369,7 +369,7 @@ Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas float half_w = 0.5f * (float)cnv_size.get_width(); float half_h = 0.5f * (float)cnv_size.get_height(); - float zoom = canvas.get_camera().zoom; + float zoom = (float)canvas.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom); @@ -400,7 +400,7 @@ void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas const float width = (float)m_tooltip_texture.get_width() * scale; const float height = (float)m_tooltip_texture.get_height() * scale; - float zoom = canvas.get_camera().zoom; + float zoom = (float)canvas.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float gap = 10.0f * inv_zoom; @@ -867,7 +867,7 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) { const Size& cnv_size = canvas.get_canvas_size(); - float zoom = canvas.get_camera().zoom; + float zoom = (float)canvas.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float left = (-0.5f * (float)m_original_width) * inv_zoom; float top = (-0.5f * (float)cnv_size.get_height() + (float)m_original_height + 2.0f) * inv_zoom; @@ -1140,7 +1140,7 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) { const Size& cnv_size = canvas.get_canvas_size(); - float zoom = canvas.get_camera().zoom; + float zoom = (float)canvas.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; float top = (0.5f * (float)cnv_size.get_height()) * inv_zoom; @@ -1523,20 +1523,20 @@ void GLCanvas3D::allow_multisample(bool allow) void GLCanvas3D::zoom_to_bed() { - _zoom_to_bounding_box(m_bed.get_bounding_box(false)); + _zoom_to_box(m_bed.get_bounding_box(false)); } void GLCanvas3D::zoom_to_volumes() { m_apply_zoom_to_volumes_filter = true; - _zoom_to_bounding_box(volumes_bounding_box()); + _zoom_to_box(volumes_bounding_box()); m_apply_zoom_to_volumes_filter = false; } void GLCanvas3D::zoom_to_selection() { if (!m_selection.is_empty()) - _zoom_to_bounding_box(m_selection.get_bounding_box()); + _zoom_to_box(m_selection.get_bounding_box()); } void GLCanvas3D::select_view(const std::string& direction) @@ -2368,9 +2368,9 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'C': case 'c': { m_camera.select_next_type(); m_dirty = true; break; } case 'I': - case 'i': { set_camera_zoom(1.0f); break; } + case 'i': { set_camera_zoom(1.0); break; } case 'O': - case 'o': { set_camera_zoom(-1.0f); break; } + case 'o': { set_camera_zoom(-1.0); break; } case 'Z': case 'z': { m_selection.is_empty() ? zoom_to_volumes() : zoom_to_selection(); break; } default: { evt.Skip(); break; } @@ -2507,7 +2507,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) return; // Calculate the zoom delta and apply it to the current zoom factor - set_camera_zoom((float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()); + set_camera_zoom((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta()); } void GLCanvas3D::on_timer(wxTimerEvent& evt) @@ -3259,20 +3259,10 @@ void GLCanvas3D::do_mirror() post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } -void GLCanvas3D::set_camera_zoom(float zoom) +void GLCanvas3D::set_camera_zoom(double zoom) { - zoom = std::max(std::min(zoom, 4.0f), -4.0f) / 10.0f; - zoom = m_camera.zoom / (1.0f - zoom); - - // Don't allow to zoom too far outside the scene. - float zoom_min = _get_zoom_to_bounding_box_factor(_max_bounding_box(false)); - if (zoom_min > 0.0f) - zoom = std::max(zoom, zoom_min * 0.7f); - - // Don't allow to zoom too close to the scene. - zoom = std::min(zoom, 100.0f); - - m_camera.zoom = zoom; + const Size& cnv_size = get_canvas_size(); + m_camera.set_zoom(zoom, _max_bounding_box(false), cnv_size.get_width(), cnv_size.get_height()); m_dirty = true; } @@ -3600,79 +3590,11 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_bed_model) const return bb; } -void GLCanvas3D::_zoom_to_bounding_box(const BoundingBoxf3& bbox) +void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box) { - // Calculate the zoom factor needed to adjust viewport to bounding box. - float zoom = _get_zoom_to_bounding_box_factor(bbox); - if (zoom > 0.0f) - { - m_camera.zoom = zoom; - // center view around bounding box center - m_camera.set_target(bbox.center()); - m_dirty = true; - } -} - -float GLCanvas3D::_get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const -{ - float max_bb_size = bbox.max_size(); - if (max_bb_size == 0.0f) - return -1.0f; - - // project the bbox vertices on a plane perpendicular to the camera forward axis - // then calculates the vertices coordinate on this plane along the camera xy axes - - // we need the view matrix, we let opengl calculate it (same as done in render()) - m_camera.apply_view_matrix(); - - Vec3d right = m_camera.get_dir_right(); - Vec3d up = m_camera.get_dir_up(); - Vec3d forward = m_camera.get_dir_forward(); - - Vec3d bb_min = bbox.min; - Vec3d bb_max = bbox.max; - Vec3d bb_center = bbox.center(); - - // bbox vertices in world space - std::vector vertices; - vertices.reserve(8); - vertices.push_back(bb_min); - vertices.emplace_back(bb_max(0), bb_min(1), bb_min(2)); - vertices.emplace_back(bb_max(0), bb_max(1), bb_min(2)); - vertices.emplace_back(bb_min(0), bb_max(1), bb_min(2)); - vertices.emplace_back(bb_min(0), bb_min(1), bb_max(2)); - vertices.emplace_back(bb_max(0), bb_min(1), bb_max(2)); - vertices.push_back(bb_max); - vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2)); - - double max_x = 0.0; - double max_y = 0.0; - - // margin factor to give some empty space around the bbox - double margin_factor = 1.25; - - for (const Vec3d& v : vertices) - { - // project vertex on the plane perpendicular to camera forward axis - Vec3d pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2)); - Vec3d proj_on_plane = pos - pos.dot(forward) * forward; - - // calculates vertex coordinate along camera xy axes - double x_on_plane = proj_on_plane.dot(right); - double y_on_plane = proj_on_plane.dot(up); - - max_x = std::max(max_x, margin_factor * std::abs(x_on_plane)); - max_y = std::max(max_y, margin_factor * std::abs(y_on_plane)); - } - - if ((max_x == 0.0) || (max_y == 0.0)) - return -1.0f; - - max_x *= 2.0; - max_y *= 2.0; - const Size& cnv_size = get_canvas_size(); - return (float)std::min((double)cnv_size.get_width() / max_x, (double)cnv_size.get_height() / max_y); + m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height()); + m_dirty = true; } void GLCanvas3D::_refresh_if_shown_on_screen() @@ -4088,7 +4010,7 @@ void GLCanvas3D::_render_toolbar() const #endif // ENABLE_RETINA_GL Size cnv_size = get_canvas_size(); - float zoom = m_camera.zoom; + float zoom = (float)m_camera.get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation(); @@ -4156,7 +4078,7 @@ void GLCanvas3D::_render_view_toolbar() const #endif // ENABLE_RETINA_GL Size cnv_size = get_canvas_size(); - float zoom = m_camera.zoom; + float zoom = (float)m_camera.get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; // places the toolbar on the bottom-left corner of the 3d scene diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index d751ecf24f..423e607764 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -598,7 +598,7 @@ public: void do_flatten(); void do_mirror(); - void set_camera_zoom(float zoom); + void set_camera_zoom(double zoom); void update_gizmos_on_off_state(); void reset_all_gizmos() { m_gizmos.reset_all_states(); } @@ -638,8 +638,7 @@ private: BoundingBoxf3 _max_bounding_box(bool include_bed_model) const; - void _zoom_to_bounding_box(const BoundingBoxf3& bbox); - float _get_zoom_to_bounding_box_factor(const BoundingBoxf3& bbox) const; + void _zoom_to_box(const BoundingBoxf3& box); void _refresh_if_shown_on_screen(); diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp index 9684bb5ec9..327cb1fde8 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.cpp +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -68,7 +68,7 @@ namespace GUI { if (!is_dragging()) return; - float zoom = canvas.get_camera().zoom; + float zoom = (float)canvas.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; Size cnv_size = canvas.get_canvas_size(); diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index e73533d37f..1b8cfc4e8a 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -607,7 +607,7 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC { // NB: mouse_pos is already scaled appropriately - float zoom = parent.get_camera().zoom; + float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; #if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; @@ -712,7 +712,7 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan { // NB: mouse_pos is already scaled appropriately - float zoom = parent.get_camera().zoom; + float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; #if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; @@ -829,7 +829,7 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3 { // NB: mouse_pos is already scaled appropriately - float zoom = parent.get_camera().zoom; + float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; #if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; @@ -912,7 +912,7 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& { // NB: mouse_pos is already scaled appropriately - float zoom = parent.get_camera().zoom; + float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; #if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; @@ -1008,7 +1008,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const return; #endif // !ENABLE_SVG_ICONS - float zoom = parent.get_camera().zoom; + float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; #if ENABLE_SVG_ICONS float factor = inv_zoom * m_layout.scale; @@ -1163,7 +1163,7 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const return; #endif // !ENABLE_SVG_ICONS - float zoom = parent.get_camera().zoom; + float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; #if ENABLE_SVG_ICONS float factor = inv_zoom * m_layout.scale; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 32809c01d3..e4b386eb40 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -930,7 +930,7 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio float cnv_w = (float)canvas.get_canvas_size().get_width(); float cnv_h = (float)canvas.get_canvas_size().get_height(); - float zoom = canvas.get_camera().zoom; + float zoom = (float)canvas.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float height = get_total_overlay_height(); From b91b94ad3cf25e77bf7357e8da458a7fa741095c Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 19 Jun 2019 14:33:09 +0200 Subject: [PATCH 156/627] Key K set as camera type toggle and updated keyboard shortcuts dialog --- src/slic3r/GUI/GLCanvas3D.cpp | 4 ++-- src/slic3r/GUI/KBShortcutsDialog.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d126c54459..efebfa4a52 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2365,10 +2365,10 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; } case 'B': case 'b': { zoom_to_bed(); break; } - case 'C': - case 'c': { m_camera.select_next_type(); m_dirty = true; break; } case 'I': case 'i': { set_camera_zoom(1.0); break; } + case 'K': + case 'k': { m_camera.select_next_type(); m_dirty = true; break; } case 'O': case 'o': { set_camera_zoom(-1.0); break; } case 'Z': diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 347dac13ea..1af658ed36 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -147,6 +147,7 @@ void KBShortcutsDialog::fill_shortcuts() plater_shortcuts.push_back(Shortcut("F", L("Press to scale selection to fit print volume\nin Gizmo scale"))); plater_shortcuts.push_back(Shortcut(alt, L("Press to activate deselection rectangle\nor to scale or rotate selected objects\naround their own center"))); plater_shortcuts.push_back(Shortcut(ctrl, L("Press to activate one direction scaling in Gizmo scale"))); + plater_shortcuts.push_back(Shortcut("K", L("Change camera type"))); plater_shortcuts.push_back(Shortcut("B", L("Zoom to Bed"))); plater_shortcuts.push_back(Shortcut("Z", L("Zoom to all objects in scene, if none selected"))); plater_shortcuts.push_back(Shortcut("Z", L("Zoom to selected object"))); From 82de7bedb12e8d8d1c1d5f69e596bb44917f9ba4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 19 Jun 2019 14:51:17 +0200 Subject: [PATCH 157/627] WipeTowerDialog.cpp - wxEXPAND conflicted with wxALIGN_CENTER_HORIZONTAL (triggered a wxWidgets assert) --- src/slic3r/GUI/WipeTowerDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/WipeTowerDialog.cpp b/src/slic3r/GUI/WipeTowerDialog.cpp index 4c2b2480e6..894b1ee62d 100644 --- a/src/slic3r/GUI/WipeTowerDialog.cpp +++ b/src/slic3r/GUI/WipeTowerDialog.cpp @@ -172,7 +172,7 @@ void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_ wxSize text_size = GetTextExtent(info); auto info_str = new wxStaticText(page, wxID_ANY, info ,wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER); info_str->Wrap(int(0.6*text_size.x)); - sizer->Add( info_str, 0, wxALIGN_CENTER_HORIZONTAL | wxEXPAND); + sizer->Add( info_str, 0, wxEXPAND); auto table_sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(table_sizer, 0, wxALIGN_CENTER | wxCENTER, table_lshift); table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 50); From 2ae2672ee92db219ad9210ddd5288f2bd5a62fbc Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 19 Jun 2019 14:52:55 +0200 Subject: [PATCH 158/627] Building igl statically and moving to the dep scripts Fixing dep build script on Windows and removing some warnings. Use bundled igl by default. Not building with the dependency scripts if not explicitly stated. This way, it will stay in Fix the libigl patch to include C source files in header only mode. --- CMakeLists.txt | 6 +- deps/CMakeLists.txt | 2 + deps/deps-unix-common.cmake | 30 ++++++- deps/deps-windows.cmake | 45 ++++++++++ deps/igl-fixes.patch | 87 +++++++++++++++++++ src/CMakeLists.txt | 1 + src/libigl/CMakeLists.txt | 14 +++ src/{ => libigl}/igl/AABB.cpp | 0 src/{ => libigl}/igl/AABB.h | 0 src/{ => libigl}/igl/ARAPEnergyType.h | 0 src/{ => libigl}/igl/AtA_cached.cpp | 0 src/{ => libigl}/igl/AtA_cached.h | 0 src/{ => libigl}/igl/C_STR.h | 0 src/{ => libigl}/igl/Camera.h | 0 src/{ => libigl}/igl/EPS.cpp | 0 src/{ => libigl}/igl/EPS.h | 0 src/{ => libigl}/igl/HalfEdgeIterator.cpp | 0 src/{ => libigl}/igl/HalfEdgeIterator.h | 0 src/{ => libigl}/igl/Hit.h | 0 src/{ => libigl}/igl/IO | 0 src/{ => libigl}/igl/IndexComparison.h | 0 src/{ => libigl}/igl/LinSpaced.h | 0 src/{ => libigl}/igl/MeshBooleanType.h | 0 src/{ => libigl}/igl/NormalType.h | 0 src/{ => libigl}/igl/ONE.h | 0 src/{ => libigl}/igl/PI.h | 0 src/{ => libigl}/igl/REDRUM.h | 0 src/{ => libigl}/igl/STR.h | 0 ...osition_Givens_QR_Factorization_Kernel.hpp | 0 ...ecomposition_Jacobi_Conjugation_Kernel.hpp | 0 ...alue_Decomposition_Kernel_Declarations.hpp | 0 ...r_Value_Decomposition_Main_Kernel_Body.hpp | 0 .../Singular_Value_Decomposition_Preamble.hpp | 0 src/{ => libigl}/igl/SolverStatus.h | 0 src/{ => libigl}/igl/SortableRow.h | 0 src/{ => libigl}/igl/Timer.h | 0 src/{ => libigl}/igl/Viewport.h | 0 src/{ => libigl}/igl/WindingNumberAABB.h | 0 src/{ => libigl}/igl/WindingNumberMethod.h | 0 src/{ => libigl}/igl/WindingNumberTree.h | 0 src/{ => libigl}/igl/ZERO.h | 0 src/{ => libigl}/igl/active_set.cpp | 0 src/{ => libigl}/igl/active_set.h | 0 src/{ => libigl}/igl/adjacency_list.cpp | 0 src/{ => libigl}/igl/adjacency_list.h | 0 src/{ => libigl}/igl/adjacency_matrix.cpp | 0 src/{ => libigl}/igl/adjacency_matrix.h | 0 src/{ => libigl}/igl/all.cpp | 0 src/{ => libigl}/igl/all.h | 0 src/{ => libigl}/igl/all_edges.cpp | 0 src/{ => libigl}/igl/all_edges.h | 0 src/{ => libigl}/igl/all_pairs_distances.cpp | 0 src/{ => libigl}/igl/all_pairs_distances.h | 0 src/{ => libigl}/igl/ambient_occlusion.cpp | 0 src/{ => libigl}/igl/ambient_occlusion.h | 0 src/{ => libigl}/igl/angular_distance.cpp | 0 src/{ => libigl}/igl/angular_distance.h | 0 .../igl/anttweakbar/ReAntTweakBar.cpp | 0 .../igl/anttweakbar/ReAntTweakBar.h | 0 .../cocoa_key_to_anttweakbar_key.cpp | 0 .../cocoa_key_to_anttweakbar_key.h | 0 src/{ => libigl}/igl/any.cpp | 0 src/{ => libigl}/igl/any.h | 0 src/{ => libigl}/igl/any_of.cpp | 0 src/{ => libigl}/igl/any_of.h | 0 src/{ => libigl}/igl/arap.cpp | 0 src/{ => libigl}/igl/arap.h | 0 src/{ => libigl}/igl/arap_dof.cpp | 0 src/{ => libigl}/igl/arap_dof.h | 0 src/{ => libigl}/igl/arap_linear_block.cpp | 0 src/{ => libigl}/igl/arap_linear_block.h | 0 src/{ => libigl}/igl/arap_rhs.cpp | 0 src/{ => libigl}/igl/arap_rhs.h | 0 src/{ => libigl}/igl/average_onto_faces.cpp | 0 src/{ => libigl}/igl/average_onto_faces.h | 0 .../igl/average_onto_vertices.cpp | 0 src/{ => libigl}/igl/average_onto_vertices.h | 0 src/{ => libigl}/igl/avg_edge_length.cpp | 0 src/{ => libigl}/igl/avg_edge_length.h | 0 src/{ => libigl}/igl/axis_angle_to_quat.cpp | 0 src/{ => libigl}/igl/axis_angle_to_quat.h | 0 src/{ => libigl}/igl/barycenter.cpp | 0 src/{ => libigl}/igl/barycenter.h | 0 .../igl/barycentric_coordinates.cpp | 0 .../igl/barycentric_coordinates.h | 0 .../igl/barycentric_to_global.cpp | 0 src/{ => libigl}/igl/barycentric_to_global.h | 0 src/{ => libigl}/igl/basename.cpp | 0 src/{ => libigl}/igl/basename.h | 0 src/{ => libigl}/igl/bbw.cpp | 0 src/{ => libigl}/igl/bbw.h | 0 src/{ => libigl}/igl/bfs.cpp | 0 src/{ => libigl}/igl/bfs.h | 0 src/{ => libigl}/igl/bfs_orient.cpp | 0 src/{ => libigl}/igl/bfs_orient.h | 0 .../igl/biharmonic_coordinates.cpp | 0 src/{ => libigl}/igl/biharmonic_coordinates.h | 0 .../bijective_composite_harmonic_mapping.cpp | 0 .../bijective_composite_harmonic_mapping.h | 0 src/{ => libigl}/igl/bone_parents.cpp | 0 src/{ => libigl}/igl/bone_parents.h | 0 src/{ => libigl}/igl/boundary_conditions.cpp | 0 src/{ => libigl}/igl/boundary_conditions.h | 0 src/{ => libigl}/igl/boundary_facets.cpp | 0 src/{ => libigl}/igl/boundary_facets.h | 0 src/{ => libigl}/igl/boundary_loop.cpp | 0 src/{ => libigl}/igl/boundary_loop.h | 0 src/{ => libigl}/igl/bounding_box.cpp | 0 src/{ => libigl}/igl/bounding_box.h | 0 .../igl/bounding_box_diagonal.cpp | 0 src/{ => libigl}/igl/bounding_box_diagonal.h | 0 .../igl/canonical_quaternions.cpp | 0 src/{ => libigl}/igl/canonical_quaternions.h | 0 src/{ => libigl}/igl/cat.cpp | 0 src/{ => libigl}/igl/cat.h | 0 src/{ => libigl}/igl/ceil.cpp | 0 src/{ => libigl}/igl/ceil.h | 0 src/{ => libigl}/igl/centroid.cpp | 0 src/{ => libigl}/igl/centroid.h | 0 src/{ => libigl}/igl/circulation.cpp | 0 src/{ => libigl}/igl/circulation.h | 0 src/{ => libigl}/igl/circumradius.cpp | 0 src/{ => libigl}/igl/circumradius.h | 0 src/{ => libigl}/igl/collapse_edge.cpp | 0 src/{ => libigl}/igl/collapse_edge.h | 0 .../igl/collapse_small_triangles.cpp | 0 .../igl/collapse_small_triangles.h | 0 src/{ => libigl}/igl/colon.cpp | 0 src/{ => libigl}/igl/colon.h | 0 src/{ => libigl}/igl/colormap.cpp | 0 src/{ => libigl}/igl/colormap.h | 0 src/{ => libigl}/igl/column_to_quats.cpp | 0 src/{ => libigl}/igl/column_to_quats.h | 0 src/{ => libigl}/igl/columnize.cpp | 0 src/{ => libigl}/igl/columnize.h | 0 src/{ => libigl}/igl/comb_cross_field.cpp | 0 src/{ => libigl}/igl/comb_cross_field.h | 0 src/{ => libigl}/igl/comb_frame_field.cpp | 0 src/{ => libigl}/igl/comb_frame_field.h | 0 src/{ => libigl}/igl/comb_line_field.cpp | 0 src/{ => libigl}/igl/comb_line_field.h | 0 src/{ => libigl}/igl/combine.cpp | 0 src/{ => libigl}/igl/combine.h | 0 src/{ => libigl}/igl/components.cpp | 0 src/{ => libigl}/igl/components.h | 0 .../igl/compute_frame_field_bisectors.cpp | 0 .../igl/compute_frame_field_bisectors.h | 0 .../igl/connect_boundary_to_infinity.cpp | 0 .../igl/connect_boundary_to_infinity.h | 0 src/{ => libigl}/igl/copyleft/README.md | 0 .../cgal/BinaryWindingNumberOperations.h | 0 .../igl/copyleft/cgal/CGAL_includes.hpp | 0 src/{ => libigl}/igl/copyleft/cgal/CSGTree.h | 0 .../cgal/RemeshSelfIntersectionsParam.h | 0 .../igl/copyleft/cgal/SelfIntersectMesh.h | 0 src/{ => libigl}/igl/copyleft/cgal/assign.cpp | 0 src/{ => libigl}/igl/copyleft/cgal/assign.h | 0 .../igl/copyleft/cgal/assign_scalar.cpp | 0 .../igl/copyleft/cgal/assign_scalar.h | 0 .../igl/copyleft/cgal/barycenter.cpp | 0 .../igl/copyleft/cgal/cell_adjacency.cpp | 0 .../igl/copyleft/cgal/cell_adjacency.h | 0 .../igl/copyleft/cgal/closest_facet.cpp | 0 .../igl/copyleft/cgal/closest_facet.h | 0 .../igl/copyleft/cgal/complex_to_mesh.cpp | 0 .../igl/copyleft/cgal/complex_to_mesh.h | 0 .../cgal/component_inside_component.cpp | 0 .../cgal/component_inside_component.h | 0 .../igl/copyleft/cgal/convex_hull.cpp | 0 .../igl/copyleft/cgal/convex_hull.h | 0 .../copyleft/cgal/delaunay_triangulation.cpp | 0 .../copyleft/cgal/delaunay_triangulation.h | 0 .../igl/copyleft/cgal/extract_cells.cpp | 0 .../igl/copyleft/cgal/extract_cells.h | 0 .../igl/copyleft/cgal/extract_feature.cpp | 0 .../igl/copyleft/cgal/extract_feature.h | 0 .../igl/copyleft/cgal/fast_winding_number.cpp | 0 .../igl/copyleft/cgal/fast_winding_number.h | 0 .../igl/copyleft/cgal/half_space_box.cpp | 0 .../igl/copyleft/cgal/half_space_box.h | 0 .../igl/copyleft/cgal/hausdorff.cpp | 0 .../igl/copyleft/cgal/hausdorff.h | 0 .../igl/copyleft/cgal/incircle.cpp | 0 src/{ => libigl}/igl/copyleft/cgal/incircle.h | 0 .../igl/copyleft/cgal/insert_into_cdt.cpp | 0 .../igl/copyleft/cgal/insert_into_cdt.h | 0 .../igl/copyleft/cgal/insphere.cpp | 0 src/{ => libigl}/igl/copyleft/cgal/insphere.h | 0 .../igl/copyleft/cgal/intersect_other.cpp | 0 .../igl/copyleft/cgal/intersect_other.h | 0 .../cgal/intersect_with_half_space.cpp | 0 .../copyleft/cgal/intersect_with_half_space.h | 0 .../cgal/lexicographic_triangulation.cpp | 0 .../cgal/lexicographic_triangulation.h | 0 .../igl/copyleft/cgal/list_to_matrix.cpp | 0 .../igl/copyleft/cgal/mesh_boolean.cpp | 0 .../igl/copyleft/cgal/mesh_boolean.h | 0 .../cgal/mesh_boolean_type_to_funcs.cpp | 0 .../cgal/mesh_boolean_type_to_funcs.h | 0 .../cgal/mesh_to_cgal_triangle_list.cpp | 0 .../cgal/mesh_to_cgal_triangle_list.h | 0 .../igl/copyleft/cgal/mesh_to_polyhedron.cpp | 0 .../igl/copyleft/cgal/mesh_to_polyhedron.h | 0 .../igl/copyleft/cgal/minkowski_sum.cpp | 0 .../igl/copyleft/cgal/minkowski_sum.h | 0 .../cgal/order_facets_around_edge.cpp | 0 .../copyleft/cgal/order_facets_around_edge.h | 0 .../cgal/order_facets_around_edges.cpp | 0 .../copyleft/cgal/order_facets_around_edges.h | 0 .../igl/copyleft/cgal/orient2D.cpp | 0 src/{ => libigl}/igl/copyleft/cgal/orient2D.h | 0 .../igl/copyleft/cgal/orient3D.cpp | 0 src/{ => libigl}/igl/copyleft/cgal/orient3D.h | 0 .../igl/copyleft/cgal/outer_element.cpp | 0 .../igl/copyleft/cgal/outer_element.h | 0 .../igl/copyleft/cgal/outer_facet.cpp | 0 .../igl/copyleft/cgal/outer_facet.h | 0 .../igl/copyleft/cgal/outer_hull.cpp | 0 .../igl/copyleft/cgal/outer_hull.h | 0 .../copyleft/cgal/peel_outer_hull_layers.cpp | 0 .../copyleft/cgal/peel_outer_hull_layers.h | 0 .../cgal/peel_winding_number_layers.cpp | 0 .../cgal/peel_winding_number_layers.h | 0 .../piecewise_constant_winding_number.cpp | 0 .../cgal/piecewise_constant_winding_number.h | 0 .../igl/copyleft/cgal/point_areas.cpp | 0 .../igl/copyleft/cgal/point_areas.h | 0 .../cgal/point_mesh_squared_distance.cpp | 0 .../cgal/point_mesh_squared_distance.h | 0 .../cgal/point_segment_squared_distance.cpp | 0 .../cgal/point_segment_squared_distance.h | 0 .../point_solid_signed_squared_distance.cpp | 0 .../point_solid_signed_squared_distance.h | 0 .../cgal/point_triangle_squared_distance.cpp | 0 .../cgal/point_triangle_squared_distance.h | 0 .../copyleft/cgal/points_inside_component.cpp | 0 .../copyleft/cgal/points_inside_component.h | 0 .../igl/copyleft/cgal/polyhedron_to_mesh.cpp | 0 .../igl/copyleft/cgal/polyhedron_to_mesh.h | 0 .../igl/copyleft/cgal/projected_cdt.cpp | 0 .../igl/copyleft/cgal/projected_cdt.h | 0 .../igl/copyleft/cgal/projected_delaunay.cpp | 0 .../igl/copyleft/cgal/projected_delaunay.h | 0 .../cgal/propagate_winding_numbers.cpp | 0 .../copyleft/cgal/propagate_winding_numbers.h | 0 .../igl/copyleft/cgal/read_triangle_mesh.cpp | 0 .../igl/copyleft/cgal/read_triangle_mesh.h | 0 .../cgal/relabel_small_immersed_cells.cpp | 0 .../cgal/relabel_small_immersed_cells.h | 0 .../copyleft/cgal/remesh_intersections.cpp | 0 .../igl/copyleft/cgal/remesh_intersections.h | 0 .../cgal/remesh_self_intersections.cpp | 0 .../copyleft/cgal/remesh_self_intersections.h | 0 .../igl/copyleft/cgal/remove_unreferenced.cpp | 0 .../copyleft/cgal/resolve_intersections.cpp | 0 .../igl/copyleft/cgal/resolve_intersections.h | 0 .../igl/copyleft/cgal/row_to_point.cpp | 0 .../igl/copyleft/cgal/row_to_point.h | 0 .../cgal/segment_segment_squared_distance.cpp | 0 .../cgal/segment_segment_squared_distance.h | 0 .../cgal/signed_distance_isosurface.cpp | 0 .../cgal/signed_distance_isosurface.h | 0 src/{ => libigl}/igl/copyleft/cgal/slice.cpp | 0 .../igl/copyleft/cgal/slice_mask.cpp | 0 .../igl/copyleft/cgal/snap_rounding.cpp | 0 .../igl/copyleft/cgal/snap_rounding.h | 0 .../cgal/string_to_mesh_boolean_type.cpp | 0 .../cgal/string_to_mesh_boolean_type.h | 0 .../igl/copyleft/cgal/subdivide_segments.cpp | 0 .../igl/copyleft/cgal/subdivide_segments.h | 0 .../igl/copyleft/cgal/submesh_aabb_tree.cpp | 0 .../igl/copyleft/cgal/submesh_aabb_tree.h | 0 .../triangle_triangle_squared_distance.cpp | 0 .../cgal/triangle_triangle_squared_distance.h | 0 .../igl/copyleft/cgal/trim_with_solid.cpp | 0 .../igl/copyleft/cgal/trim_with_solid.h | 0 src/{ => libigl}/igl/copyleft/cgal/unique.cpp | 0 .../igl/copyleft/cgal/unique_rows.cpp | 0 .../igl/copyleft/cgal/wire_mesh.cpp | 0 .../igl/copyleft/cgal/wire_mesh.h | 0 .../igl/copyleft/comiso/frame_field.cpp | 0 .../igl/copyleft/comiso/frame_field.h | 0 src/{ => libigl}/igl/copyleft/comiso/miq.cpp | 0 src/{ => libigl}/igl/copyleft/comiso/miq.h | 0 .../igl/copyleft/comiso/nrosy.cpp | 0 src/{ => libigl}/igl/copyleft/comiso/nrosy.h | 0 .../igl/copyleft/cork/from_cork_mesh.cpp | 0 .../igl/copyleft/cork/from_cork_mesh.h | 0 .../igl/copyleft/cork/mesh_boolean.cpp | 0 .../igl/copyleft/cork/mesh_boolean.h | 0 .../igl/copyleft/cork/to_cork_mesh.cpp | 0 .../igl/copyleft/cork/to_cork_mesh.h | 0 .../igl/copyleft/marching_cubes.cpp | 0 .../igl/copyleft/marching_cubes.h | 0 .../igl/copyleft/marching_cubes_tables.h | 0 .../igl/copyleft/offset_surface.cpp | 0 .../igl/copyleft/offset_surface.h | 0 .../igl/copyleft/opengl2/render_to_tga.cpp | 0 .../igl/copyleft/opengl2/render_to_tga.h | 0 .../igl/copyleft/opengl2/texture_from_tga.cpp | 0 .../igl/copyleft/opengl2/texture_from_tga.h | 0 src/{ => libigl}/igl/copyleft/opengl2/tga.cpp | 0 src/{ => libigl}/igl/copyleft/opengl2/tga.h | 0 .../igl/copyleft/progressive_hulls.cpp | 0 .../igl/copyleft/progressive_hulls.h | 0 .../progressive_hulls_cost_and_placement.cpp | 0 .../progressive_hulls_cost_and_placement.h | 0 src/{ => libigl}/igl/copyleft/quadprog.cpp | 0 src/{ => libigl}/igl/copyleft/quadprog.h | 0 .../igl/copyleft/swept_volume.cpp | 0 src/{ => libigl}/igl/copyleft/swept_volume.h | 0 src/{ => libigl}/igl/copyleft/tetgen/README | 0 src/{ => libigl}/igl/copyleft/tetgen/cdt.cpp | 0 src/{ => libigl}/igl/copyleft/tetgen/cdt.h | 0 .../igl/copyleft/tetgen/mesh_to_tetgenio.cpp | 0 .../igl/copyleft/tetgen/mesh_to_tetgenio.h | 0 .../copyleft/tetgen/mesh_with_skeleton.cpp | 0 .../igl/copyleft/tetgen/mesh_with_skeleton.h | 0 .../copyleft/tetgen/read_into_tetgenio.cpp | 0 .../igl/copyleft/tetgen/read_into_tetgenio.h | 0 .../copyleft/tetgen/tetgenio_to_tetmesh.cpp | 0 .../igl/copyleft/tetgen/tetgenio_to_tetmesh.h | 0 .../igl/copyleft/tetgen/tetrahedralize.cpp | 0 .../igl/copyleft/tetgen/tetrahedralize.h | 0 src/{ => libigl}/igl/cotmatrix.cpp | 0 src/{ => libigl}/igl/cotmatrix.h | 0 src/{ => libigl}/igl/cotmatrix_entries.cpp | 0 src/{ => libigl}/igl/cotmatrix_entries.h | 0 src/{ => libigl}/igl/count.cpp | 0 src/{ => libigl}/igl/count.h | 0 .../igl/covariance_scatter_matrix.cpp | 0 .../igl/covariance_scatter_matrix.h | 0 src/{ => libigl}/igl/cross.cpp | 0 src/{ => libigl}/igl/cross.h | 0 .../igl/cross_field_missmatch.cpp | 0 src/{ => libigl}/igl/cross_field_missmatch.h | 0 .../igl/crouzeix_raviart_cotmatrix.cpp | 0 .../igl/crouzeix_raviart_cotmatrix.h | 0 .../igl/crouzeix_raviart_massmatrix.cpp | 0 .../igl/crouzeix_raviart_massmatrix.h | 0 src/{ => libigl}/igl/cumsum.cpp | 0 src/{ => libigl}/igl/cumsum.h | 0 src/{ => libigl}/igl/cut_mesh.cpp | 0 src/{ => libigl}/igl/cut_mesh.h | 0 .../igl/cut_mesh_from_singularities.cpp | 0 .../igl/cut_mesh_from_singularities.h | 0 src/{ => libigl}/igl/cylinder.cpp | 0 src/{ => libigl}/igl/cylinder.h | 0 src/{ => libigl}/igl/dated_copy.cpp | 0 src/{ => libigl}/igl/dated_copy.h | 0 src/{ => libigl}/igl/decimate.cpp | 0 src/{ => libigl}/igl/decimate.h | 0 src/{ => libigl}/igl/deform_skeleton.cpp | 0 src/{ => libigl}/igl/deform_skeleton.h | 0 .../igl/delaunay_triangulation.cpp | 0 src/{ => libigl}/igl/delaunay_triangulation.h | 0 src/{ => libigl}/igl/deprecated.h | 0 src/{ => libigl}/igl/dfs.cpp | 0 src/{ => libigl}/igl/dfs.h | 0 src/{ => libigl}/igl/diag.cpp | 0 src/{ => libigl}/igl/diag.h | 0 src/{ => libigl}/igl/dihedral_angles.cpp | 0 src/{ => libigl}/igl/dihedral_angles.h | 0 src/{ => libigl}/igl/dijkstra.cpp | 0 src/{ => libigl}/igl/dijkstra.h | 0 .../igl/directed_edge_orientations.cpp | 0 .../igl/directed_edge_orientations.h | 0 .../igl/directed_edge_parents.cpp | 0 src/{ => libigl}/igl/directed_edge_parents.h | 0 src/{ => libigl}/igl/dirname.cpp | 0 src/{ => libigl}/igl/dirname.h | 0 src/{ => libigl}/igl/dot.cpp | 0 src/{ => libigl}/igl/dot.h | 0 src/{ => libigl}/igl/dot_row.cpp | 0 src/{ => libigl}/igl/dot_row.h | 0 src/{ => libigl}/igl/doublearea.cpp | 0 src/{ => libigl}/igl/doublearea.h | 0 src/{ => libigl}/igl/dqs.cpp | 0 src/{ => libigl}/igl/dqs.h | 0 src/{ => libigl}/igl/ears.cpp | 0 src/{ => libigl}/igl/ears.h | 0 .../igl/edge_collapse_is_valid.cpp | 0 src/{ => libigl}/igl/edge_collapse_is_valid.h | 0 src/{ => libigl}/igl/edge_flaps.cpp | 0 src/{ => libigl}/igl/edge_flaps.h | 0 src/{ => libigl}/igl/edge_lengths.cpp | 0 src/{ => libigl}/igl/edge_lengths.h | 0 src/{ => libigl}/igl/edge_topology.cpp | 0 src/{ => libigl}/igl/edge_topology.h | 0 src/{ => libigl}/igl/edges.cpp | 0 src/{ => libigl}/igl/edges.h | 0 src/{ => libigl}/igl/edges_to_path.cpp | 0 src/{ => libigl}/igl/edges_to_path.h | 0 src/{ => libigl}/igl/eigs.cpp | 0 src/{ => libigl}/igl/eigs.h | 0 .../igl/embree/EmbreeIntersector.h | 0 .../igl/embree/Embree_convenience.h | 0 .../igl/embree/ambient_occlusion.cpp | 0 .../igl/embree/ambient_occlusion.h | 0 src/{ => libigl}/igl/embree/bone_heat.cpp | 0 src/{ => libigl}/igl/embree/bone_heat.h | 0 src/{ => libigl}/igl/embree/bone_visible.cpp | 0 src/{ => libigl}/igl/embree/bone_visible.h | 0 src/{ => libigl}/igl/embree/embree2/rtcore.h | 0 .../igl/embree/embree2/rtcore.isph | 0 .../igl/embree/embree2/rtcore_geometry.h | 0 .../igl/embree/embree2/rtcore_geometry.isph | 0 .../igl/embree/embree2/rtcore_geometry_user.h | 0 .../embree/embree2/rtcore_geometry_user.isph | 0 .../igl/embree/embree2/rtcore_ray.h | 0 .../igl/embree/embree2/rtcore_ray.isph | 0 .../igl/embree/embree2/rtcore_scene.h | 0 .../igl/embree/embree2/rtcore_scene.isph | 0 .../igl/embree/line_mesh_intersection.cpp | 0 .../igl/embree/line_mesh_intersection.h | 0 .../igl/embree/reorient_facets_raycast.cpp | 0 .../igl/embree/reorient_facets_raycast.h | 0 .../igl/embree/shape_diameter_function.cpp | 0 .../igl/embree/shape_diameter_function.h | 0 .../igl/embree/unproject_in_mesh.cpp | 0 .../igl/embree/unproject_in_mesh.h | 0 .../igl/embree/unproject_onto_mesh.cpp | 0 .../igl/embree/unproject_onto_mesh.h | 0 src/{ => libigl}/igl/euler_characteristic.cpp | 0 src/{ => libigl}/igl/euler_characteristic.h | 0 src/{ => libigl}/igl/exact_geodesic.cpp | 0 src/{ => libigl}/igl/exact_geodesic.h | 0 src/{ => libigl}/igl/example_fun.cpp | 0 src/{ => libigl}/igl/example_fun.h | 0 src/{ => libigl}/igl/exterior_edges.cpp | 0 src/{ => libigl}/igl/exterior_edges.h | 0 .../igl/extract_manifold_patches.cpp | 0 .../igl/extract_manifold_patches.h | 0 .../igl/extract_non_manifold_edge_curves.cpp | 0 .../igl/extract_non_manifold_edge_curves.h | 0 src/{ => libigl}/igl/face_areas.cpp | 0 src/{ => libigl}/igl/face_areas.h | 0 src/{ => libigl}/igl/face_occurrences.cpp | 0 src/{ => libigl}/igl/face_occurrences.h | 0 src/{ => libigl}/igl/faces_first.cpp | 0 src/{ => libigl}/igl/faces_first.h | 0 src/{ => libigl}/igl/facet_components.cpp | 0 src/{ => libigl}/igl/facet_components.h | 0 .../igl/false_barycentric_subdivision.cpp | 0 .../igl/false_barycentric_subdivision.h | 0 src/{ => libigl}/igl/fast_winding_number.cpp | 0 src/{ => libigl}/igl/fast_winding_number.h | 0 .../igl/file_contents_as_string.cpp | 0 .../igl/file_contents_as_string.h | 0 src/{ => libigl}/igl/file_dialog_open.cpp | 0 src/{ => libigl}/igl/file_dialog_open.h | 0 src/{ => libigl}/igl/file_dialog_save.cpp | 0 src/{ => libigl}/igl/file_dialog_save.h | 0 src/{ => libigl}/igl/file_exists.cpp | 0 src/{ => libigl}/igl/file_exists.h | 0 src/{ => libigl}/igl/find.cpp | 0 src/{ => libigl}/igl/find.h | 0 .../igl/find_cross_field_singularities.cpp | 0 .../igl/find_cross_field_singularities.h | 0 src/{ => libigl}/igl/find_zero.cpp | 0 src/{ => libigl}/igl/find_zero.h | 0 src/{ => libigl}/igl/fit_plane.cpp | 0 src/{ => libigl}/igl/fit_plane.h | 0 src/{ => libigl}/igl/fit_rotations.cpp | 0 src/{ => libigl}/igl/fit_rotations.h | 0 .../igl/flip_avoiding_line_search.cpp | 0 .../igl/flip_avoiding_line_search.h | 0 src/{ => libigl}/igl/flip_edge.cpp | 0 src/{ => libigl}/igl/flip_edge.h | 0 src/{ => libigl}/igl/flipped_triangles.cpp | 0 src/{ => libigl}/igl/flipped_triangles.h | 0 src/{ => libigl}/igl/flood_fill.cpp | 0 src/{ => libigl}/igl/flood_fill.h | 0 src/{ => libigl}/igl/floor.cpp | 0 src/{ => libigl}/igl/floor.h | 0 src/{ => libigl}/igl/for_each.h | 0 src/{ => libigl}/igl/forward_kinematics.cpp | 0 src/{ => libigl}/igl/forward_kinematics.h | 0 src/{ => libigl}/igl/frame_field_deformer.cpp | 0 src/{ => libigl}/igl/frame_field_deformer.h | 0 src/{ => libigl}/igl/frame_to_cross_field.cpp | 0 src/{ => libigl}/igl/frame_to_cross_field.h | 0 src/{ => libigl}/igl/frustum.cpp | 0 src/{ => libigl}/igl/frustum.h | 0 src/{ => libigl}/igl/gaussian_curvature.cpp | 0 src/{ => libigl}/igl/gaussian_curvature.h | 0 src/{ => libigl}/igl/get_seconds.cpp | 0 src/{ => libigl}/igl/get_seconds.h | 0 src/{ => libigl}/igl/get_seconds_hires.cpp | 0 src/{ => libigl}/igl/get_seconds_hires.h | 0 src/{ => libigl}/igl/grad.cpp | 0 src/{ => libigl}/igl/grad.h | 0 src/{ => libigl}/igl/grid.cpp | 0 src/{ => libigl}/igl/grid.h | 0 src/{ => libigl}/igl/grid_search.cpp | 0 src/{ => libigl}/igl/grid_search.h | 0 src/{ => libigl}/igl/group_sum_matrix.cpp | 0 src/{ => libigl}/igl/group_sum_matrix.h | 0 src/{ => libigl}/igl/guess_extension.cpp | 0 src/{ => libigl}/igl/guess_extension.h | 0 src/{ => libigl}/igl/harmonic.cpp | 0 src/{ => libigl}/igl/harmonic.h | 0 src/{ => libigl}/igl/harwell_boeing.cpp | 0 src/{ => libigl}/igl/harwell_boeing.h | 0 src/{ => libigl}/igl/hausdorff.cpp | 0 src/{ => libigl}/igl/hausdorff.h | 0 src/{ => libigl}/igl/hessian.cpp | 0 src/{ => libigl}/igl/hessian.h | 0 src/{ => libigl}/igl/hessian_energy.cpp | 0 src/{ => libigl}/igl/hessian_energy.h | 0 src/{ => libigl}/igl/histc.cpp | 0 src/{ => libigl}/igl/histc.h | 0 src/{ => libigl}/igl/hsv_to_rgb.cpp | 0 src/{ => libigl}/igl/hsv_to_rgb.h | 0 src/{ => libigl}/igl/igl_inline.h | 0 src/{ => libigl}/igl/in_element.cpp | 0 src/{ => libigl}/igl/in_element.h | 0 .../igl/infinite_cost_stopping_condition.cpp | 0 .../igl/infinite_cost_stopping_condition.h | 0 src/{ => libigl}/igl/inradius.cpp | 0 src/{ => libigl}/igl/inradius.h | 0 src/{ => libigl}/igl/internal_angles.cpp | 0 src/{ => libigl}/igl/internal_angles.h | 0 src/{ => libigl}/igl/intersect.cpp | 0 src/{ => libigl}/igl/intersect.h | 0 src/{ => libigl}/igl/invert_diag.cpp | 0 src/{ => libigl}/igl/invert_diag.h | 0 src/{ => libigl}/igl/is_border_vertex.cpp | 0 src/{ => libigl}/igl/is_border_vertex.h | 0 src/{ => libigl}/igl/is_boundary_edge.cpp | 0 src/{ => libigl}/igl/is_boundary_edge.h | 0 src/{ => libigl}/igl/is_dir.cpp | 0 src/{ => libigl}/igl/is_dir.h | 0 src/{ => libigl}/igl/is_edge_manifold.cpp | 0 src/{ => libigl}/igl/is_edge_manifold.h | 0 src/{ => libigl}/igl/is_file.cpp | 0 src/{ => libigl}/igl/is_file.h | 0 src/{ => libigl}/igl/is_irregular_vertex.cpp | 0 src/{ => libigl}/igl/is_irregular_vertex.h | 0 src/{ => libigl}/igl/is_planar.cpp | 0 src/{ => libigl}/igl/is_planar.h | 0 src/{ => libigl}/igl/is_readable.cpp | 0 src/{ => libigl}/igl/is_readable.h | 0 src/{ => libigl}/igl/is_sparse.cpp | 0 src/{ => libigl}/igl/is_sparse.h | 0 src/{ => libigl}/igl/is_stl.cpp | 0 src/{ => libigl}/igl/is_stl.h | 0 src/{ => libigl}/igl/is_symmetric.cpp | 0 src/{ => libigl}/igl/is_symmetric.h | 0 src/{ => libigl}/igl/is_vertex_manifold.cpp | 0 src/{ => libigl}/igl/is_vertex_manifold.h | 0 src/{ => libigl}/igl/is_writable.cpp | 0 src/{ => libigl}/igl/is_writable.h | 0 src/{ => libigl}/igl/isdiag.cpp | 0 src/{ => libigl}/igl/isdiag.h | 0 src/{ => libigl}/igl/ismember.cpp | 0 src/{ => libigl}/igl/ismember.h | 0 src/{ => libigl}/igl/isolines.cpp | 0 src/{ => libigl}/igl/isolines.h | 0 src/{ => libigl}/igl/jet.cpp | 0 src/{ => libigl}/igl/jet.h | 0 src/{ => libigl}/igl/knn.cpp | 0 src/{ => libigl}/igl/knn.h | 0 src/{ => libigl}/igl/launch_medit.cpp | 0 src/{ => libigl}/igl/launch_medit.h | 0 src/{ => libigl}/igl/lbs_matrix.cpp | 0 src/{ => libigl}/igl/lbs_matrix.h | 0 .../igl/lexicographic_triangulation.cpp | 0 .../igl/lexicographic_triangulation.h | 0 src/{ => libigl}/igl/lim/lim.cpp | 0 src/{ => libigl}/igl/lim/lim.h | 0 src/{ => libigl}/igl/limit_faces.cpp | 0 src/{ => libigl}/igl/limit_faces.h | 0 src/{ => libigl}/igl/line_field_missmatch.cpp | 0 src/{ => libigl}/igl/line_field_missmatch.h | 0 src/{ => libigl}/igl/line_search.cpp | 0 src/{ => libigl}/igl/line_search.h | 0 .../igl/line_segment_in_rectangle.cpp | 0 .../igl/line_segment_in_rectangle.h | 0 src/{ => libigl}/igl/linprog.cpp | 0 src/{ => libigl}/igl/linprog.h | 0 src/{ => libigl}/igl/list_to_matrix.cpp | 0 src/{ => libigl}/igl/list_to_matrix.h | 0 src/{ => libigl}/igl/local_basis.cpp | 0 src/{ => libigl}/igl/local_basis.h | 0 src/{ => libigl}/igl/look_at.cpp | 0 src/{ => libigl}/igl/look_at.h | 0 src/{ => libigl}/igl/loop.cpp | 0 src/{ => libigl}/igl/loop.h | 0 src/{ => libigl}/igl/lscm.cpp | 0 src/{ => libigl}/igl/lscm.h | 0 .../igl/map_vertices_to_circle.cpp | 0 src/{ => libigl}/igl/map_vertices_to_circle.h | 0 src/{ => libigl}/igl/massmatrix.cpp | 0 src/{ => libigl}/igl/massmatrix.h | 0 src/{ => libigl}/igl/mat_max.cpp | 0 src/{ => libigl}/igl/mat_max.h | 0 src/{ => libigl}/igl/mat_min.cpp | 0 src/{ => libigl}/igl/mat_min.h | 0 src/{ => libigl}/igl/mat_to_quat.cpp | 0 src/{ => libigl}/igl/mat_to_quat.h | 0 src/{ => libigl}/igl/material_colors.h | 0 src/{ => libigl}/igl/matlab/MatlabWorkspace.h | 0 src/{ => libigl}/igl/matlab/MexStream.h | 0 .../igl/matlab/matlabinterface.cpp | 0 src/{ => libigl}/igl/matlab/matlabinterface.h | 0 src/{ => libigl}/igl/matlab/mexErrMsgTxt.cpp | 0 src/{ => libigl}/igl/matlab/mexErrMsgTxt.h | 0 src/{ => libigl}/igl/matlab/parse_rhs.cpp | 0 src/{ => libigl}/igl/matlab/parse_rhs.h | 0 src/{ => libigl}/igl/matlab/prepare_lhs.cpp | 0 src/{ => libigl}/igl/matlab/prepare_lhs.h | 0 src/{ => libigl}/igl/matlab/requires_arg.cpp | 0 src/{ => libigl}/igl/matlab/requires_arg.h | 0 src/{ => libigl}/igl/matlab/validate_arg.cpp | 0 src/{ => libigl}/igl/matlab/validate_arg.h | 0 src/{ => libigl}/igl/matlab_format.cpp | 0 src/{ => libigl}/igl/matlab_format.h | 0 src/{ => libigl}/igl/matrix_to_list.cpp | 0 src/{ => libigl}/igl/matrix_to_list.h | 0 src/{ => libigl}/igl/max.cpp | 0 src/{ => libigl}/igl/max.h | 0 .../igl/max_faces_stopping_condition.cpp | 0 .../igl/max_faces_stopping_condition.h | 0 src/{ => libigl}/igl/max_size.cpp | 0 src/{ => libigl}/igl/max_size.h | 0 src/{ => libigl}/igl/median.cpp | 0 src/{ => libigl}/igl/median.h | 0 src/{ => libigl}/igl/min.cpp | 0 src/{ => libigl}/igl/min.h | 0 src/{ => libigl}/igl/min_quad_dense.cpp | 0 src/{ => libigl}/igl/min_quad_dense.h | 0 src/{ => libigl}/igl/min_quad_with_fixed.cpp | 0 src/{ => libigl}/igl/min_quad_with_fixed.h | 0 src/{ => libigl}/igl/min_size.cpp | 0 src/{ => libigl}/igl/min_size.h | 0 src/{ => libigl}/igl/mod.cpp | 0 src/{ => libigl}/igl/mod.h | 0 src/{ => libigl}/igl/mode.cpp | 0 src/{ => libigl}/igl/mode.h | 0 src/{ => libigl}/igl/mosek/bbw.cpp | 0 src/{ => libigl}/igl/mosek/bbw.h | 0 src/{ => libigl}/igl/mosek/mosek_guarded.cpp | 0 src/{ => libigl}/igl/mosek/mosek_guarded.h | 0 src/{ => libigl}/igl/mosek/mosek_linprog.cpp | 0 src/{ => libigl}/igl/mosek/mosek_linprog.h | 0 src/{ => libigl}/igl/mosek/mosek_quadprog.cpp | 0 src/{ => libigl}/igl/mosek/mosek_quadprog.h | 0 src/{ => libigl}/igl/mvc.cpp | 0 src/{ => libigl}/igl/mvc.h | 0 src/{ => libigl}/igl/nchoosek.cpp | 0 src/{ => libigl}/igl/nchoosek.h | 0 src/{ => libigl}/igl/next_filename.cpp | 0 src/{ => libigl}/igl/next_filename.h | 0 src/{ => libigl}/igl/normal_derivative.cpp | 0 src/{ => libigl}/igl/normal_derivative.h | 0 src/{ => libigl}/igl/normalize_quat.cpp | 0 src/{ => libigl}/igl/normalize_quat.h | 0 .../igl/normalize_row_lengths.cpp | 0 src/{ => libigl}/igl/normalize_row_lengths.h | 0 src/{ => libigl}/igl/normalize_row_sums.cpp | 0 src/{ => libigl}/igl/normalize_row_sums.h | 0 src/{ => libigl}/igl/null.cpp | 0 src/{ => libigl}/igl/null.h | 0 src/{ => libigl}/igl/octree.cpp | 0 src/{ => libigl}/igl/octree.h | 0 src/{ => libigl}/igl/on_boundary.cpp | 0 src/{ => libigl}/igl/on_boundary.h | 0 src/{ => libigl}/igl/opengl/MeshGL.cpp | 0 src/{ => libigl}/igl/opengl/MeshGL.h | 0 src/{ => libigl}/igl/opengl/ViewerCore.cpp | 0 src/{ => libigl}/igl/opengl/ViewerCore.h | 0 src/{ => libigl}/igl/opengl/ViewerData.cpp | 0 src/{ => libigl}/igl/opengl/ViewerData.h | 0 .../igl/opengl/bind_vertex_attrib_array.cpp | 0 .../igl/opengl/bind_vertex_attrib_array.h | 0 .../igl/opengl/create_index_vbo.cpp | 0 .../igl/opengl/create_index_vbo.h | 0 .../igl/opengl/create_mesh_vbo.cpp | 0 src/{ => libigl}/igl/opengl/create_mesh_vbo.h | 0 .../igl/opengl/create_shader_program.cpp | 0 .../igl/opengl/create_shader_program.h | 0 .../igl/opengl/create_vector_vbo.cpp | 0 .../igl/opengl/create_vector_vbo.h | 0 .../igl/opengl/destroy_shader_program.cpp | 0 .../igl/opengl/destroy_shader_program.h | 0 src/{ => libigl}/igl/opengl/gl.h | 0 src/{ => libigl}/igl/opengl/gl_type_size.cpp | 0 src/{ => libigl}/igl/opengl/gl_type_size.h | 0 src/{ => libigl}/igl/opengl/glfw/Viewer.cpp | 0 src/{ => libigl}/igl/opengl/glfw/Viewer.h | 0 .../igl/opengl/glfw/ViewerPlugin.h | 0 .../igl/opengl/glfw/background_window.cpp | 0 .../igl/opengl/glfw/background_window.h | 0 .../igl/opengl/glfw/imgui/ImGuiHelpers.h | 0 .../igl/opengl/glfw/imgui/ImGuiMenu.cpp | 0 .../igl/opengl/glfw/imgui/ImGuiMenu.h | 0 .../igl/opengl/glfw/map_texture.cpp | 0 .../igl/opengl/glfw/map_texture.h | 0 .../igl/opengl/init_render_to_texture.cpp | 0 .../igl/opengl/init_render_to_texture.h | 0 src/{ => libigl}/igl/opengl/load_shader.cpp | 0 src/{ => libigl}/igl/opengl/load_shader.h | 0 .../igl/opengl/print_program_info_log.cpp | 0 .../igl/opengl/print_program_info_log.h | 0 .../igl/opengl/print_shader_info_log.cpp | 0 .../igl/opengl/print_shader_info_log.h | 0 .../igl/opengl/report_gl_error.cpp | 0 src/{ => libigl}/igl/opengl/report_gl_error.h | 0 .../igl/opengl/uniform_type_to_string.cpp | 0 .../igl/opengl/uniform_type_to_string.h | 0 src/{ => libigl}/igl/opengl/vertex_array.cpp | 0 src/{ => libigl}/igl/opengl/vertex_array.h | 0 .../igl/opengl2/MouseController.h | 0 src/{ => libigl}/igl/opengl2/RotateWidget.h | 0 .../igl/opengl2/TranslateWidget.h | 0 .../igl/opengl2/draw_beach_ball.cpp | 0 .../igl/opengl2/draw_beach_ball.h | 0 src/{ => libigl}/igl/opengl2/draw_floor.cpp | 0 src/{ => libigl}/igl/opengl2/draw_floor.h | 0 src/{ => libigl}/igl/opengl2/draw_mesh.cpp | 0 src/{ => libigl}/igl/opengl2/draw_mesh.h | 0 src/{ => libigl}/igl/opengl2/draw_point.cpp | 0 src/{ => libigl}/igl/opengl2/draw_point.h | 0 .../igl/opengl2/draw_rectangular_marquee.cpp | 0 .../igl/opengl2/draw_rectangular_marquee.h | 0 .../igl/opengl2/draw_skeleton_3d.cpp | 0 .../igl/opengl2/draw_skeleton_3d.h | 0 .../opengl2/draw_skeleton_vector_graphics.cpp | 0 .../opengl2/draw_skeleton_vector_graphics.h | 0 src/{ => libigl}/igl/opengl2/flare_textures.h | 0 src/{ => libigl}/igl/opengl2/gl.h | 0 src/{ => libigl}/igl/opengl2/glext.h | 0 src/{ => libigl}/igl/opengl2/glu.h | 0 src/{ => libigl}/igl/opengl2/lens_flare.cpp | 0 src/{ => libigl}/igl/opengl2/lens_flare.h | 0 .../igl/opengl2/model_proj_viewport.cpp | 0 .../igl/opengl2/model_proj_viewport.h | 0 src/{ => libigl}/igl/opengl2/print_gl_get.cpp | 0 src/{ => libigl}/igl/opengl2/print_gl_get.h | 0 src/{ => libigl}/igl/opengl2/project.cpp | 0 src/{ => libigl}/igl/opengl2/project.h | 0 src/{ => libigl}/igl/opengl2/right_axis.cpp | 0 src/{ => libigl}/igl/opengl2/right_axis.h | 0 src/{ => libigl}/igl/opengl2/shine_textures.h | 0 .../igl/opengl2/sort_triangles.cpp | 0 src/{ => libigl}/igl/opengl2/sort_triangles.h | 0 src/{ => libigl}/igl/opengl2/unproject.cpp | 0 src/{ => libigl}/igl/opengl2/unproject.h | 0 .../igl/opengl2/unproject_to_zero_plane.cpp | 0 .../igl/opengl2/unproject_to_zero_plane.h | 0 src/{ => libigl}/igl/opengl2/up_axis.cpp | 0 src/{ => libigl}/igl/opengl2/up_axis.h | 0 src/{ => libigl}/igl/opengl2/view_axis.cpp | 0 src/{ => libigl}/igl/opengl2/view_axis.h | 0 src/{ => libigl}/igl/orient_outward.cpp | 0 src/{ => libigl}/igl/orient_outward.h | 0 src/{ => libigl}/igl/orientable_patches.cpp | 0 src/{ => libigl}/igl/orientable_patches.h | 0 src/{ => libigl}/igl/oriented_facets.cpp | 0 src/{ => libigl}/igl/oriented_facets.h | 0 src/{ => libigl}/igl/orth.cpp | 0 src/{ => libigl}/igl/orth.h | 0 src/{ => libigl}/igl/ortho.cpp | 0 src/{ => libigl}/igl/ortho.h | 0 src/{ => libigl}/igl/outer_element.cpp | 0 src/{ => libigl}/igl/outer_element.h | 0 src/{ => libigl}/igl/parallel_for.h | 0 .../igl/parallel_transport_angles.cpp | 0 .../igl/parallel_transport_angles.h | 0 src/{ => libigl}/igl/partition.cpp | 0 src/{ => libigl}/igl/partition.h | 0 src/{ => libigl}/igl/parula.cpp | 0 src/{ => libigl}/igl/parula.h | 0 src/{ => libigl}/igl/path_to_executable.cpp | 0 src/{ => libigl}/igl/path_to_executable.h | 0 src/{ => libigl}/igl/pathinfo.cpp | 0 src/{ => libigl}/igl/pathinfo.h | 0 src/{ => libigl}/igl/per_corner_normals.cpp | 0 src/{ => libigl}/igl/per_corner_normals.h | 0 src/{ => libigl}/igl/per_edge_normals.cpp | 0 src/{ => libigl}/igl/per_edge_normals.h | 0 src/{ => libigl}/igl/per_face_normals.cpp | 0 src/{ => libigl}/igl/per_face_normals.h | 0 .../igl/per_vertex_attribute_smoothing.cpp | 0 .../igl/per_vertex_attribute_smoothing.h | 0 src/{ => libigl}/igl/per_vertex_normals.cpp | 0 src/{ => libigl}/igl/per_vertex_normals.h | 0 .../per_vertex_point_to_plane_quadrics.cpp | 0 .../igl/per_vertex_point_to_plane_quadrics.h | 0 .../igl/piecewise_constant_winding_number.cpp | 0 .../igl/piecewise_constant_winding_number.h | 0 src/{ => libigl}/igl/pinv.cpp | 0 src/{ => libigl}/igl/pinv.h | 0 src/{ => libigl}/igl/planarize_quad_mesh.cpp | 0 src/{ => libigl}/igl/planarize_quad_mesh.h | 0 src/{ => libigl}/igl/ply.h | 0 src/{ => libigl}/igl/png/readPNG.cpp | 0 src/{ => libigl}/igl/png/readPNG.h | 0 src/{ => libigl}/igl/png/render_to_png.cpp | 0 src/{ => libigl}/igl/png/render_to_png.h | 0 .../igl/png/render_to_png_async.cpp | 0 .../igl/png/render_to_png_async.h | 0 .../igl/png/texture_from_file.cpp | 0 src/{ => libigl}/igl/png/texture_from_file.h | 0 src/{ => libigl}/igl/png/texture_from_png.cpp | 0 src/{ => libigl}/igl/png/texture_from_png.h | 0 src/{ => libigl}/igl/png/writePNG.cpp | 0 src/{ => libigl}/igl/png/writePNG.h | 0 src/{ => libigl}/igl/point_in_circle.cpp | 0 src/{ => libigl}/igl/point_in_circle.h | 0 src/{ => libigl}/igl/point_in_poly.cpp | 0 src/{ => libigl}/igl/point_in_poly.h | 0 .../igl/point_mesh_squared_distance.cpp | 0 .../igl/point_mesh_squared_distance.h | 0 .../igl/point_simplex_squared_distance.cpp | 0 .../igl/point_simplex_squared_distance.h | 0 src/{ => libigl}/igl/polar_dec.cpp | 0 src/{ => libigl}/igl/polar_dec.h | 0 src/{ => libigl}/igl/polar_svd.cpp | 0 src/{ => libigl}/igl/polar_svd.h | 0 src/{ => libigl}/igl/polar_svd3x3.cpp | 0 src/{ => libigl}/igl/polar_svd3x3.h | 0 .../igl/polygon_mesh_to_triangle_mesh.cpp | 0 .../igl/polygon_mesh_to_triangle_mesh.h | 0 src/{ => libigl}/igl/principal_curvature.cpp | 0 src/{ => libigl}/igl/principal_curvature.h | 0 src/{ => libigl}/igl/print_ijv.cpp | 0 src/{ => libigl}/igl/print_ijv.h | 0 src/{ => libigl}/igl/print_vector.cpp | 0 src/{ => libigl}/igl/print_vector.h | 0 src/{ => libigl}/igl/procrustes.cpp | 0 src/{ => libigl}/igl/procrustes.h | 0 src/{ => libigl}/igl/project.cpp | 0 src/{ => libigl}/igl/project.h | 0 .../igl/project_isometrically_to_plane.cpp | 0 .../igl/project_isometrically_to_plane.h | 0 src/{ => libigl}/igl/project_to_line.cpp | 0 src/{ => libigl}/igl/project_to_line.h | 0 .../igl/project_to_line_segment.cpp | 0 .../igl/project_to_line_segment.h | 0 src/{ => libigl}/igl/pseudonormal_test.cpp | 0 src/{ => libigl}/igl/pseudonormal_test.h | 0 src/{ => libigl}/igl/pso.cpp | 0 src/{ => libigl}/igl/pso.h | 0 src/{ => libigl}/igl/qslim.cpp | 0 src/{ => libigl}/igl/qslim.h | 0 .../qslim_optimal_collapse_edge_callbacks.cpp | 0 .../qslim_optimal_collapse_edge_callbacks.h | 0 src/{ => libigl}/igl/quad_planarity.cpp | 0 src/{ => libigl}/igl/quad_planarity.h | 0 .../igl/quadric_binary_plus_operator.cpp | 0 .../igl/quadric_binary_plus_operator.h | 0 src/{ => libigl}/igl/quat_conjugate.cpp | 0 src/{ => libigl}/igl/quat_conjugate.h | 0 src/{ => libigl}/igl/quat_mult.cpp | 0 src/{ => libigl}/igl/quat_mult.h | 0 src/{ => libigl}/igl/quat_to_axis_angle.cpp | 0 src/{ => libigl}/igl/quat_to_axis_angle.h | 0 src/{ => libigl}/igl/quat_to_mat.cpp | 0 src/{ => libigl}/igl/quat_to_mat.h | 0 src/{ => libigl}/igl/quats_to_column.cpp | 0 src/{ => libigl}/igl/quats_to_column.h | 0 .../igl/ramer_douglas_peucker.cpp | 0 src/{ => libigl}/igl/ramer_douglas_peucker.h | 0 src/{ => libigl}/igl/random_dir.cpp | 0 src/{ => libigl}/igl/random_dir.h | 0 .../igl/random_points_on_mesh.cpp | 0 src/{ => libigl}/igl/random_points_on_mesh.h | 0 src/{ => libigl}/igl/random_quaternion.cpp | 0 src/{ => libigl}/igl/random_quaternion.h | 0 src/{ => libigl}/igl/random_search.cpp | 0 src/{ => libigl}/igl/random_search.h | 0 src/{ => libigl}/igl/randperm.cpp | 0 src/{ => libigl}/igl/randperm.h | 0 src/{ => libigl}/igl/ray_box_intersect.cpp | 0 src/{ => libigl}/igl/ray_box_intersect.h | 0 src/{ => libigl}/igl/ray_mesh_intersect.cpp | 0 src/{ => libigl}/igl/ray_mesh_intersect.h | 0 src/{ => libigl}/igl/ray_sphere_intersect.cpp | 0 src/{ => libigl}/igl/ray_sphere_intersect.h | 0 src/{ => libigl}/igl/raytri.c | 0 src/{ => libigl}/igl/readBF.cpp | 0 src/{ => libigl}/igl/readBF.h | 0 src/{ => libigl}/igl/readCSV.cpp | 0 src/{ => libigl}/igl/readCSV.h | 0 src/{ => libigl}/igl/readDMAT.cpp | 0 src/{ => libigl}/igl/readDMAT.h | 0 src/{ => libigl}/igl/readMESH.cpp | 0 src/{ => libigl}/igl/readMESH.h | 0 src/{ => libigl}/igl/readMSH.cpp | 0 src/{ => libigl}/igl/readMSH.h | 0 src/{ => libigl}/igl/readNODE.cpp | 0 src/{ => libigl}/igl/readNODE.h | 0 src/{ => libigl}/igl/readOBJ.cpp | 0 src/{ => libigl}/igl/readOBJ.h | 0 src/{ => libigl}/igl/readOFF.cpp | 0 src/{ => libigl}/igl/readOFF.h | 0 src/{ => libigl}/igl/readPLY.cpp | 0 src/{ => libigl}/igl/readPLY.h | 0 src/{ => libigl}/igl/readSTL.cpp | 0 src/{ => libigl}/igl/readSTL.h | 0 src/{ => libigl}/igl/readTGF.cpp | 0 src/{ => libigl}/igl/readTGF.h | 0 src/{ => libigl}/igl/readWRL.cpp | 0 src/{ => libigl}/igl/readWRL.h | 0 src/{ => libigl}/igl/read_triangle_mesh.cpp | 0 src/{ => libigl}/igl/read_triangle_mesh.h | 0 src/{ => libigl}/igl/redux.h | 0 src/{ => libigl}/igl/remesh_along_isoline.cpp | 0 src/{ => libigl}/igl/remesh_along_isoline.h | 0 .../igl/remove_duplicate_vertices.cpp | 0 .../igl/remove_duplicate_vertices.h | 0 src/{ => libigl}/igl/remove_duplicates.cpp | 0 src/{ => libigl}/igl/remove_duplicates.h | 0 src/{ => libigl}/igl/remove_unreferenced.cpp | 0 src/{ => libigl}/igl/remove_unreferenced.h | 0 src/{ => libigl}/igl/reorder.cpp | 0 src/{ => libigl}/igl/reorder.h | 0 src/{ => libigl}/igl/repdiag.cpp | 0 src/{ => libigl}/igl/repdiag.h | 0 src/{ => libigl}/igl/repmat.cpp | 0 src/{ => libigl}/igl/repmat.h | 0 .../igl/resolve_duplicated_faces.cpp | 0 .../igl/resolve_duplicated_faces.h | 0 src/{ => libigl}/igl/rgb_to_hsv.cpp | 0 src/{ => libigl}/igl/rgb_to_hsv.h | 0 src/{ => libigl}/igl/rotate_by_quat.cpp | 0 src/{ => libigl}/igl/rotate_by_quat.h | 0 src/{ => libigl}/igl/rotate_vectors.cpp | 0 src/{ => libigl}/igl/rotate_vectors.h | 0 .../igl/rotation_matrix_from_directions.cpp | 0 .../igl/rotation_matrix_from_directions.h | 0 src/{ => libigl}/igl/round.cpp | 0 src/{ => libigl}/igl/round.h | 0 src/{ => libigl}/igl/rows_to_matrix.cpp | 0 src/{ => libigl}/igl/rows_to_matrix.h | 0 src/{ => libigl}/igl/sample_edges.cpp | 0 src/{ => libigl}/igl/sample_edges.h | 0 src/{ => libigl}/igl/seam_edges.cpp | 0 src/{ => libigl}/igl/seam_edges.h | 0 .../igl/segment_segment_intersect.cpp | 0 .../igl/segment_segment_intersect.h | 0 src/{ => libigl}/igl/serialize.h | 0 src/{ => libigl}/igl/setdiff.cpp | 0 src/{ => libigl}/igl/setdiff.h | 0 src/{ => libigl}/igl/setunion.cpp | 0 src/{ => libigl}/igl/setunion.h | 0 src/{ => libigl}/igl/setxor.cpp | 0 src/{ => libigl}/igl/setxor.h | 0 .../igl/shape_diameter_function.cpp | 0 .../igl/shape_diameter_function.h | 0 src/{ => libigl}/igl/shapeup.cpp | 0 src/{ => libigl}/igl/shapeup.h | 0 .../igl/shortest_edge_and_midpoint.cpp | 0 .../igl/shortest_edge_and_midpoint.h | 0 src/{ => libigl}/igl/signed_angle.cpp | 0 src/{ => libigl}/igl/signed_angle.h | 0 src/{ => libigl}/igl/signed_distance.cpp | 0 src/{ => libigl}/igl/signed_distance.h | 0 src/{ => libigl}/igl/simplify_polyhedron.cpp | 0 src/{ => libigl}/igl/simplify_polyhedron.h | 0 src/{ => libigl}/igl/slice.cpp | 0 src/{ => libigl}/igl/slice.h | 0 src/{ => libigl}/igl/slice_cached.cpp | 0 src/{ => libigl}/igl/slice_cached.h | 0 src/{ => libigl}/igl/slice_into.cpp | 0 src/{ => libigl}/igl/slice_into.h | 0 src/{ => libigl}/igl/slice_mask.cpp | 0 src/{ => libigl}/igl/slice_mask.h | 0 src/{ => libigl}/igl/slice_tets.cpp | 0 src/{ => libigl}/igl/slice_tets.h | 0 src/{ => libigl}/igl/slim.cpp | 0 src/{ => libigl}/igl/slim.h | 0 src/{ => libigl}/igl/snap_points.cpp | 0 src/{ => libigl}/igl/snap_points.h | 0 .../igl/snap_to_canonical_view_quat.cpp | 0 .../igl/snap_to_canonical_view_quat.h | 0 src/{ => libigl}/igl/snap_to_fixed_up.cpp | 0 src/{ => libigl}/igl/snap_to_fixed_up.h | 0 src/{ => libigl}/igl/solid_angle.cpp | 0 src/{ => libigl}/igl/solid_angle.h | 0 src/{ => libigl}/igl/sort.cpp | 0 src/{ => libigl}/igl/sort.h | 0 src/{ => libigl}/igl/sort_angles.cpp | 0 src/{ => libigl}/igl/sort_angles.h | 0 src/{ => libigl}/igl/sort_triangles.cpp | 0 src/{ => libigl}/igl/sort_triangles.h | 0 src/{ => libigl}/igl/sort_vectors_ccw.cpp | 0 src/{ => libigl}/igl/sort_vectors_ccw.h | 0 src/{ => libigl}/igl/sortrows.cpp | 0 src/{ => libigl}/igl/sortrows.h | 0 src/{ => libigl}/igl/sparse.cpp | 0 src/{ => libigl}/igl/sparse.h | 0 src/{ => libigl}/igl/sparse_cached.cpp | 0 src/{ => libigl}/igl/sparse_cached.h | 0 src/{ => libigl}/igl/speye.cpp | 0 src/{ => libigl}/igl/speye.h | 0 src/{ => libigl}/igl/squared_edge_lengths.cpp | 0 src/{ => libigl}/igl/squared_edge_lengths.h | 0 src/{ => libigl}/igl/stdin_to_temp.cpp | 0 src/{ => libigl}/igl/stdin_to_temp.h | 0 src/{ => libigl}/igl/straighten_seams.cpp | 0 src/{ => libigl}/igl/straighten_seams.h | 0 src/{ => libigl}/igl/sum.cpp | 0 src/{ => libigl}/igl/sum.h | 0 src/{ => libigl}/igl/svd3x3.cpp | 0 src/{ => libigl}/igl/svd3x3.h | 0 src/{ => libigl}/igl/svd3x3_avx.cpp | 0 src/{ => libigl}/igl/svd3x3_avx.h | 0 src/{ => libigl}/igl/svd3x3_sse.cpp | 0 src/{ => libigl}/igl/svd3x3_sse.h | 0 .../igl/swept_volume_bounding_box.cpp | 0 .../igl/swept_volume_bounding_box.h | 0 .../igl/swept_volume_signed_distance.cpp | 0 .../igl/swept_volume_signed_distance.h | 0 src/{ => libigl}/igl/trackball.cpp | 0 src/{ => libigl}/igl/trackball.h | 0 src/{ => libigl}/igl/transpose_blocks.cpp | 0 src/{ => libigl}/igl/transpose_blocks.h | 0 src/{ => libigl}/igl/triangle/cdt.cpp | 0 src/{ => libigl}/igl/triangle/cdt.h | 0 src/{ => libigl}/igl/triangle/triangulate.cpp | 0 src/{ => libigl}/igl/triangle/triangulate.h | 0 src/{ => libigl}/igl/triangle_fan.cpp | 0 src/{ => libigl}/igl/triangle_fan.h | 0 .../igl/triangle_triangle_adjacency.cpp | 0 .../igl/triangle_triangle_adjacency.h | 0 src/{ => libigl}/igl/triangles_from_strip.cpp | 0 src/{ => libigl}/igl/triangles_from_strip.h | 0 .../igl/two_axis_valuator_fixed_up.cpp | 0 .../igl/two_axis_valuator_fixed_up.h | 0 .../igl/uniformly_sample_two_manifold.cpp | 0 .../igl/uniformly_sample_two_manifold.h | 0 src/{ => libigl}/igl/unique.cpp | 0 src/{ => libigl}/igl/unique.h | 0 src/{ => libigl}/igl/unique_edge_map.cpp | 0 src/{ => libigl}/igl/unique_edge_map.h | 0 src/{ => libigl}/igl/unique_rows.cpp | 0 src/{ => libigl}/igl/unique_rows.h | 0 src/{ => libigl}/igl/unique_simplices.cpp | 0 src/{ => libigl}/igl/unique_simplices.h | 0 src/{ => libigl}/igl/unproject.cpp | 0 src/{ => libigl}/igl/unproject.h | 0 src/{ => libigl}/igl/unproject_in_mesh.cpp | 0 src/{ => libigl}/igl/unproject_in_mesh.h | 0 src/{ => libigl}/igl/unproject_onto_mesh.cpp | 0 src/{ => libigl}/igl/unproject_onto_mesh.h | 0 src/{ => libigl}/igl/unproject_ray.cpp | 0 src/{ => libigl}/igl/unproject_ray.h | 0 src/{ => libigl}/igl/unzip_corners.cpp | 0 src/{ => libigl}/igl/unzip_corners.h | 0 src/{ => libigl}/igl/upsample.cpp | 0 src/{ => libigl}/igl/upsample.h | 0 src/{ => libigl}/igl/vector_area_matrix.cpp | 0 src/{ => libigl}/igl/vector_area_matrix.h | 0 src/{ => libigl}/igl/verbose.h | 0 .../igl/vertex_triangle_adjacency.cpp | 0 .../igl/vertex_triangle_adjacency.h | 0 src/{ => libigl}/igl/volume.cpp | 0 src/{ => libigl}/igl/volume.h | 0 src/{ => libigl}/igl/voxel_grid.cpp | 0 src/{ => libigl}/igl/voxel_grid.h | 0 src/{ => libigl}/igl/winding_number.cpp | 0 src/{ => libigl}/igl/winding_number.h | 0 src/{ => libigl}/igl/writeBF.cpp | 0 src/{ => libigl}/igl/writeBF.h | 0 src/{ => libigl}/igl/writeDMAT.cpp | 0 src/{ => libigl}/igl/writeDMAT.h | 0 src/{ => libigl}/igl/writeMESH.cpp | 0 src/{ => libigl}/igl/writeMESH.h | 0 src/{ => libigl}/igl/writeOBJ.cpp | 0 src/{ => libigl}/igl/writeOBJ.h | 0 src/{ => libigl}/igl/writeOFF.cpp | 0 src/{ => libigl}/igl/writeOFF.h | 0 src/{ => libigl}/igl/writePLY.cpp | 0 src/{ => libigl}/igl/writePLY.h | 0 src/{ => libigl}/igl/writeSTL.cpp | 0 src/{ => libigl}/igl/writeSTL.h | 0 src/{ => libigl}/igl/writeTGF.cpp | 0 src/{ => libigl}/igl/writeTGF.h | 0 src/{ => libigl}/igl/writeWRL.cpp | 0 src/{ => libigl}/igl/writeWRL.h | 0 src/{ => libigl}/igl/write_triangle_mesh.cpp | 0 src/{ => libigl}/igl/write_triangle_mesh.h | 0 .../igl/xml/ReAntTweakBarXMLSerialization.h | 0 src/{ => libigl}/igl/xml/XMLSerializable.h | 0 .../igl/xml/serialization_test.skip | 0 src/{ => libigl}/igl/xml/serialize_xml.cpp | 0 src/{ => libigl}/igl/xml/serialize_xml.h | 0 src/{ => libigl}/igl/xml/writeDAE.cpp | 0 src/{ => libigl}/igl/xml/writeDAE.h | 0 .../igl/xml/write_triangle_mesh.cpp | 0 .../igl/xml/write_triangle_mesh.h | 0 src/libslic3r/CMakeLists.txt | 1 + 1095 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 deps/igl-fixes.patch create mode 100644 src/libigl/CMakeLists.txt rename src/{ => libigl}/igl/AABB.cpp (100%) rename src/{ => libigl}/igl/AABB.h (100%) rename src/{ => libigl}/igl/ARAPEnergyType.h (100%) rename src/{ => libigl}/igl/AtA_cached.cpp (100%) rename src/{ => libigl}/igl/AtA_cached.h (100%) rename src/{ => libigl}/igl/C_STR.h (100%) rename src/{ => libigl}/igl/Camera.h (100%) rename src/{ => libigl}/igl/EPS.cpp (100%) rename src/{ => libigl}/igl/EPS.h (100%) rename src/{ => libigl}/igl/HalfEdgeIterator.cpp (100%) rename src/{ => libigl}/igl/HalfEdgeIterator.h (100%) rename src/{ => libigl}/igl/Hit.h (100%) rename src/{ => libigl}/igl/IO (100%) rename src/{ => libigl}/igl/IndexComparison.h (100%) rename src/{ => libigl}/igl/LinSpaced.h (100%) rename src/{ => libigl}/igl/MeshBooleanType.h (100%) rename src/{ => libigl}/igl/NormalType.h (100%) rename src/{ => libigl}/igl/ONE.h (100%) rename src/{ => libigl}/igl/PI.h (100%) rename src/{ => libigl}/igl/REDRUM.h (100%) rename src/{ => libigl}/igl/STR.h (100%) rename src/{ => libigl}/igl/Singular_Value_Decomposition_Givens_QR_Factorization_Kernel.hpp (100%) rename src/{ => libigl}/igl/Singular_Value_Decomposition_Jacobi_Conjugation_Kernel.hpp (100%) rename src/{ => libigl}/igl/Singular_Value_Decomposition_Kernel_Declarations.hpp (100%) rename src/{ => libigl}/igl/Singular_Value_Decomposition_Main_Kernel_Body.hpp (100%) rename src/{ => libigl}/igl/Singular_Value_Decomposition_Preamble.hpp (100%) rename src/{ => libigl}/igl/SolverStatus.h (100%) rename src/{ => libigl}/igl/SortableRow.h (100%) rename src/{ => libigl}/igl/Timer.h (100%) rename src/{ => libigl}/igl/Viewport.h (100%) rename src/{ => libigl}/igl/WindingNumberAABB.h (100%) rename src/{ => libigl}/igl/WindingNumberMethod.h (100%) rename src/{ => libigl}/igl/WindingNumberTree.h (100%) rename src/{ => libigl}/igl/ZERO.h (100%) rename src/{ => libigl}/igl/active_set.cpp (100%) rename src/{ => libigl}/igl/active_set.h (100%) rename src/{ => libigl}/igl/adjacency_list.cpp (100%) rename src/{ => libigl}/igl/adjacency_list.h (100%) rename src/{ => libigl}/igl/adjacency_matrix.cpp (100%) rename src/{ => libigl}/igl/adjacency_matrix.h (100%) rename src/{ => libigl}/igl/all.cpp (100%) rename src/{ => libigl}/igl/all.h (100%) rename src/{ => libigl}/igl/all_edges.cpp (100%) rename src/{ => libigl}/igl/all_edges.h (100%) rename src/{ => libigl}/igl/all_pairs_distances.cpp (100%) rename src/{ => libigl}/igl/all_pairs_distances.h (100%) rename src/{ => libigl}/igl/ambient_occlusion.cpp (100%) rename src/{ => libigl}/igl/ambient_occlusion.h (100%) rename src/{ => libigl}/igl/angular_distance.cpp (100%) rename src/{ => libigl}/igl/angular_distance.h (100%) rename src/{ => libigl}/igl/anttweakbar/ReAntTweakBar.cpp (100%) rename src/{ => libigl}/igl/anttweakbar/ReAntTweakBar.h (100%) rename src/{ => libigl}/igl/anttweakbar/cocoa_key_to_anttweakbar_key.cpp (100%) rename src/{ => libigl}/igl/anttweakbar/cocoa_key_to_anttweakbar_key.h (100%) rename src/{ => libigl}/igl/any.cpp (100%) rename src/{ => libigl}/igl/any.h (100%) rename src/{ => libigl}/igl/any_of.cpp (100%) rename src/{ => libigl}/igl/any_of.h (100%) rename src/{ => libigl}/igl/arap.cpp (100%) rename src/{ => libigl}/igl/arap.h (100%) rename src/{ => libigl}/igl/arap_dof.cpp (100%) rename src/{ => libigl}/igl/arap_dof.h (100%) rename src/{ => libigl}/igl/arap_linear_block.cpp (100%) rename src/{ => libigl}/igl/arap_linear_block.h (100%) rename src/{ => libigl}/igl/arap_rhs.cpp (100%) rename src/{ => libigl}/igl/arap_rhs.h (100%) rename src/{ => libigl}/igl/average_onto_faces.cpp (100%) rename src/{ => libigl}/igl/average_onto_faces.h (100%) rename src/{ => libigl}/igl/average_onto_vertices.cpp (100%) rename src/{ => libigl}/igl/average_onto_vertices.h (100%) rename src/{ => libigl}/igl/avg_edge_length.cpp (100%) rename src/{ => libigl}/igl/avg_edge_length.h (100%) rename src/{ => libigl}/igl/axis_angle_to_quat.cpp (100%) rename src/{ => libigl}/igl/axis_angle_to_quat.h (100%) rename src/{ => libigl}/igl/barycenter.cpp (100%) rename src/{ => libigl}/igl/barycenter.h (100%) rename src/{ => libigl}/igl/barycentric_coordinates.cpp (100%) rename src/{ => libigl}/igl/barycentric_coordinates.h (100%) rename src/{ => libigl}/igl/barycentric_to_global.cpp (100%) rename src/{ => libigl}/igl/barycentric_to_global.h (100%) rename src/{ => libigl}/igl/basename.cpp (100%) rename src/{ => libigl}/igl/basename.h (100%) rename src/{ => libigl}/igl/bbw.cpp (100%) rename src/{ => libigl}/igl/bbw.h (100%) rename src/{ => libigl}/igl/bfs.cpp (100%) rename src/{ => libigl}/igl/bfs.h (100%) rename src/{ => libigl}/igl/bfs_orient.cpp (100%) rename src/{ => libigl}/igl/bfs_orient.h (100%) rename src/{ => libigl}/igl/biharmonic_coordinates.cpp (100%) rename src/{ => libigl}/igl/biharmonic_coordinates.h (100%) rename src/{ => libigl}/igl/bijective_composite_harmonic_mapping.cpp (100%) rename src/{ => libigl}/igl/bijective_composite_harmonic_mapping.h (100%) rename src/{ => libigl}/igl/bone_parents.cpp (100%) rename src/{ => libigl}/igl/bone_parents.h (100%) rename src/{ => libigl}/igl/boundary_conditions.cpp (100%) rename src/{ => libigl}/igl/boundary_conditions.h (100%) rename src/{ => libigl}/igl/boundary_facets.cpp (100%) rename src/{ => libigl}/igl/boundary_facets.h (100%) rename src/{ => libigl}/igl/boundary_loop.cpp (100%) rename src/{ => libigl}/igl/boundary_loop.h (100%) rename src/{ => libigl}/igl/bounding_box.cpp (100%) rename src/{ => libigl}/igl/bounding_box.h (100%) rename src/{ => libigl}/igl/bounding_box_diagonal.cpp (100%) rename src/{ => libigl}/igl/bounding_box_diagonal.h (100%) rename src/{ => libigl}/igl/canonical_quaternions.cpp (100%) rename src/{ => libigl}/igl/canonical_quaternions.h (100%) rename src/{ => libigl}/igl/cat.cpp (100%) rename src/{ => libigl}/igl/cat.h (100%) rename src/{ => libigl}/igl/ceil.cpp (100%) rename src/{ => libigl}/igl/ceil.h (100%) rename src/{ => libigl}/igl/centroid.cpp (100%) rename src/{ => libigl}/igl/centroid.h (100%) rename src/{ => libigl}/igl/circulation.cpp (100%) rename src/{ => libigl}/igl/circulation.h (100%) rename src/{ => libigl}/igl/circumradius.cpp (100%) rename src/{ => libigl}/igl/circumradius.h (100%) rename src/{ => libigl}/igl/collapse_edge.cpp (100%) rename src/{ => libigl}/igl/collapse_edge.h (100%) rename src/{ => libigl}/igl/collapse_small_triangles.cpp (100%) rename src/{ => libigl}/igl/collapse_small_triangles.h (100%) rename src/{ => libigl}/igl/colon.cpp (100%) rename src/{ => libigl}/igl/colon.h (100%) rename src/{ => libigl}/igl/colormap.cpp (100%) rename src/{ => libigl}/igl/colormap.h (100%) rename src/{ => libigl}/igl/column_to_quats.cpp (100%) rename src/{ => libigl}/igl/column_to_quats.h (100%) rename src/{ => libigl}/igl/columnize.cpp (100%) rename src/{ => libigl}/igl/columnize.h (100%) rename src/{ => libigl}/igl/comb_cross_field.cpp (100%) rename src/{ => libigl}/igl/comb_cross_field.h (100%) rename src/{ => libigl}/igl/comb_frame_field.cpp (100%) rename src/{ => libigl}/igl/comb_frame_field.h (100%) rename src/{ => libigl}/igl/comb_line_field.cpp (100%) rename src/{ => libigl}/igl/comb_line_field.h (100%) rename src/{ => libigl}/igl/combine.cpp (100%) rename src/{ => libigl}/igl/combine.h (100%) rename src/{ => libigl}/igl/components.cpp (100%) rename src/{ => libigl}/igl/components.h (100%) rename src/{ => libigl}/igl/compute_frame_field_bisectors.cpp (100%) rename src/{ => libigl}/igl/compute_frame_field_bisectors.h (100%) rename src/{ => libigl}/igl/connect_boundary_to_infinity.cpp (100%) rename src/{ => libigl}/igl/connect_boundary_to_infinity.h (100%) rename src/{ => libigl}/igl/copyleft/README.md (100%) rename src/{ => libigl}/igl/copyleft/cgal/BinaryWindingNumberOperations.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/CGAL_includes.hpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/CSGTree.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/RemeshSelfIntersectionsParam.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/SelfIntersectMesh.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/assign.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/assign.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/assign_scalar.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/assign_scalar.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/barycenter.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/cell_adjacency.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/cell_adjacency.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/closest_facet.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/closest_facet.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/complex_to_mesh.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/complex_to_mesh.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/component_inside_component.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/component_inside_component.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/convex_hull.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/convex_hull.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/delaunay_triangulation.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/delaunay_triangulation.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/extract_cells.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/extract_cells.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/extract_feature.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/extract_feature.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/fast_winding_number.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/fast_winding_number.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/half_space_box.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/half_space_box.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/hausdorff.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/hausdorff.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/incircle.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/incircle.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/insert_into_cdt.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/insert_into_cdt.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/insphere.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/insphere.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/intersect_other.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/intersect_other.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/intersect_with_half_space.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/intersect_with_half_space.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/lexicographic_triangulation.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/lexicographic_triangulation.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/list_to_matrix.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/mesh_boolean.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/mesh_boolean.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/mesh_boolean_type_to_funcs.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/mesh_boolean_type_to_funcs.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/mesh_to_cgal_triangle_list.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/mesh_to_cgal_triangle_list.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/mesh_to_polyhedron.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/mesh_to_polyhedron.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/minkowski_sum.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/minkowski_sum.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/order_facets_around_edge.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/order_facets_around_edge.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/order_facets_around_edges.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/order_facets_around_edges.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/orient2D.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/orient2D.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/orient3D.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/orient3D.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/outer_element.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/outer_element.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/outer_facet.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/outer_facet.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/outer_hull.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/outer_hull.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/peel_outer_hull_layers.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/peel_outer_hull_layers.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/peel_winding_number_layers.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/peel_winding_number_layers.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/piecewise_constant_winding_number.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/piecewise_constant_winding_number.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/point_areas.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/point_areas.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/point_mesh_squared_distance.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/point_mesh_squared_distance.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/point_segment_squared_distance.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/point_segment_squared_distance.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/point_solid_signed_squared_distance.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/point_solid_signed_squared_distance.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/point_triangle_squared_distance.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/point_triangle_squared_distance.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/points_inside_component.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/points_inside_component.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/polyhedron_to_mesh.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/polyhedron_to_mesh.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/projected_cdt.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/projected_cdt.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/projected_delaunay.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/projected_delaunay.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/propagate_winding_numbers.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/propagate_winding_numbers.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/read_triangle_mesh.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/read_triangle_mesh.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/relabel_small_immersed_cells.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/relabel_small_immersed_cells.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/remesh_intersections.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/remesh_intersections.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/remesh_self_intersections.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/remesh_self_intersections.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/remove_unreferenced.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/resolve_intersections.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/resolve_intersections.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/row_to_point.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/row_to_point.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/segment_segment_squared_distance.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/segment_segment_squared_distance.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/signed_distance_isosurface.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/signed_distance_isosurface.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/slice.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/slice_mask.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/snap_rounding.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/snap_rounding.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/string_to_mesh_boolean_type.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/string_to_mesh_boolean_type.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/subdivide_segments.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/subdivide_segments.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/submesh_aabb_tree.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/submesh_aabb_tree.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/triangle_triangle_squared_distance.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/triangle_triangle_squared_distance.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/trim_with_solid.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/trim_with_solid.h (100%) rename src/{ => libigl}/igl/copyleft/cgal/unique.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/unique_rows.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/wire_mesh.cpp (100%) rename src/{ => libigl}/igl/copyleft/cgal/wire_mesh.h (100%) rename src/{ => libigl}/igl/copyleft/comiso/frame_field.cpp (100%) rename src/{ => libigl}/igl/copyleft/comiso/frame_field.h (100%) rename src/{ => libigl}/igl/copyleft/comiso/miq.cpp (100%) rename src/{ => libigl}/igl/copyleft/comiso/miq.h (100%) rename src/{ => libigl}/igl/copyleft/comiso/nrosy.cpp (100%) rename src/{ => libigl}/igl/copyleft/comiso/nrosy.h (100%) rename src/{ => libigl}/igl/copyleft/cork/from_cork_mesh.cpp (100%) rename src/{ => libigl}/igl/copyleft/cork/from_cork_mesh.h (100%) rename src/{ => libigl}/igl/copyleft/cork/mesh_boolean.cpp (100%) rename src/{ => libigl}/igl/copyleft/cork/mesh_boolean.h (100%) rename src/{ => libigl}/igl/copyleft/cork/to_cork_mesh.cpp (100%) rename src/{ => libigl}/igl/copyleft/cork/to_cork_mesh.h (100%) rename src/{ => libigl}/igl/copyleft/marching_cubes.cpp (100%) rename src/{ => libigl}/igl/copyleft/marching_cubes.h (100%) rename src/{ => libigl}/igl/copyleft/marching_cubes_tables.h (100%) rename src/{ => libigl}/igl/copyleft/offset_surface.cpp (100%) rename src/{ => libigl}/igl/copyleft/offset_surface.h (100%) rename src/{ => libigl}/igl/copyleft/opengl2/render_to_tga.cpp (100%) rename src/{ => libigl}/igl/copyleft/opengl2/render_to_tga.h (100%) rename src/{ => libigl}/igl/copyleft/opengl2/texture_from_tga.cpp (100%) rename src/{ => libigl}/igl/copyleft/opengl2/texture_from_tga.h (100%) rename src/{ => libigl}/igl/copyleft/opengl2/tga.cpp (100%) rename src/{ => libigl}/igl/copyleft/opengl2/tga.h (100%) rename src/{ => libigl}/igl/copyleft/progressive_hulls.cpp (100%) rename src/{ => libigl}/igl/copyleft/progressive_hulls.h (100%) rename src/{ => libigl}/igl/copyleft/progressive_hulls_cost_and_placement.cpp (100%) rename src/{ => libigl}/igl/copyleft/progressive_hulls_cost_and_placement.h (100%) rename src/{ => libigl}/igl/copyleft/quadprog.cpp (100%) rename src/{ => libigl}/igl/copyleft/quadprog.h (100%) rename src/{ => libigl}/igl/copyleft/swept_volume.cpp (100%) rename src/{ => libigl}/igl/copyleft/swept_volume.h (100%) rename src/{ => libigl}/igl/copyleft/tetgen/README (100%) rename src/{ => libigl}/igl/copyleft/tetgen/cdt.cpp (100%) rename src/{ => libigl}/igl/copyleft/tetgen/cdt.h (100%) rename src/{ => libigl}/igl/copyleft/tetgen/mesh_to_tetgenio.cpp (100%) rename src/{ => libigl}/igl/copyleft/tetgen/mesh_to_tetgenio.h (100%) rename src/{ => libigl}/igl/copyleft/tetgen/mesh_with_skeleton.cpp (100%) rename src/{ => libigl}/igl/copyleft/tetgen/mesh_with_skeleton.h (100%) rename src/{ => libigl}/igl/copyleft/tetgen/read_into_tetgenio.cpp (100%) rename src/{ => libigl}/igl/copyleft/tetgen/read_into_tetgenio.h (100%) rename src/{ => libigl}/igl/copyleft/tetgen/tetgenio_to_tetmesh.cpp (100%) rename src/{ => libigl}/igl/copyleft/tetgen/tetgenio_to_tetmesh.h (100%) rename src/{ => libigl}/igl/copyleft/tetgen/tetrahedralize.cpp (100%) rename src/{ => libigl}/igl/copyleft/tetgen/tetrahedralize.h (100%) rename src/{ => libigl}/igl/cotmatrix.cpp (100%) rename src/{ => libigl}/igl/cotmatrix.h (100%) rename src/{ => libigl}/igl/cotmatrix_entries.cpp (100%) rename src/{ => libigl}/igl/cotmatrix_entries.h (100%) rename src/{ => libigl}/igl/count.cpp (100%) rename src/{ => libigl}/igl/count.h (100%) rename src/{ => libigl}/igl/covariance_scatter_matrix.cpp (100%) rename src/{ => libigl}/igl/covariance_scatter_matrix.h (100%) rename src/{ => libigl}/igl/cross.cpp (100%) rename src/{ => libigl}/igl/cross.h (100%) rename src/{ => libigl}/igl/cross_field_missmatch.cpp (100%) rename src/{ => libigl}/igl/cross_field_missmatch.h (100%) rename src/{ => libigl}/igl/crouzeix_raviart_cotmatrix.cpp (100%) rename src/{ => libigl}/igl/crouzeix_raviart_cotmatrix.h (100%) rename src/{ => libigl}/igl/crouzeix_raviart_massmatrix.cpp (100%) rename src/{ => libigl}/igl/crouzeix_raviart_massmatrix.h (100%) rename src/{ => libigl}/igl/cumsum.cpp (100%) rename src/{ => libigl}/igl/cumsum.h (100%) rename src/{ => libigl}/igl/cut_mesh.cpp (100%) rename src/{ => libigl}/igl/cut_mesh.h (100%) rename src/{ => libigl}/igl/cut_mesh_from_singularities.cpp (100%) rename src/{ => libigl}/igl/cut_mesh_from_singularities.h (100%) rename src/{ => libigl}/igl/cylinder.cpp (100%) rename src/{ => libigl}/igl/cylinder.h (100%) rename src/{ => libigl}/igl/dated_copy.cpp (100%) rename src/{ => libigl}/igl/dated_copy.h (100%) rename src/{ => libigl}/igl/decimate.cpp (100%) rename src/{ => libigl}/igl/decimate.h (100%) rename src/{ => libigl}/igl/deform_skeleton.cpp (100%) rename src/{ => libigl}/igl/deform_skeleton.h (100%) rename src/{ => libigl}/igl/delaunay_triangulation.cpp (100%) rename src/{ => libigl}/igl/delaunay_triangulation.h (100%) rename src/{ => libigl}/igl/deprecated.h (100%) rename src/{ => libigl}/igl/dfs.cpp (100%) rename src/{ => libigl}/igl/dfs.h (100%) rename src/{ => libigl}/igl/diag.cpp (100%) rename src/{ => libigl}/igl/diag.h (100%) rename src/{ => libigl}/igl/dihedral_angles.cpp (100%) rename src/{ => libigl}/igl/dihedral_angles.h (100%) rename src/{ => libigl}/igl/dijkstra.cpp (100%) rename src/{ => libigl}/igl/dijkstra.h (100%) rename src/{ => libigl}/igl/directed_edge_orientations.cpp (100%) rename src/{ => libigl}/igl/directed_edge_orientations.h (100%) rename src/{ => libigl}/igl/directed_edge_parents.cpp (100%) rename src/{ => libigl}/igl/directed_edge_parents.h (100%) rename src/{ => libigl}/igl/dirname.cpp (100%) rename src/{ => libigl}/igl/dirname.h (100%) rename src/{ => libigl}/igl/dot.cpp (100%) rename src/{ => libigl}/igl/dot.h (100%) rename src/{ => libigl}/igl/dot_row.cpp (100%) rename src/{ => libigl}/igl/dot_row.h (100%) rename src/{ => libigl}/igl/doublearea.cpp (100%) rename src/{ => libigl}/igl/doublearea.h (100%) rename src/{ => libigl}/igl/dqs.cpp (100%) rename src/{ => libigl}/igl/dqs.h (100%) rename src/{ => libigl}/igl/ears.cpp (100%) rename src/{ => libigl}/igl/ears.h (100%) rename src/{ => libigl}/igl/edge_collapse_is_valid.cpp (100%) rename src/{ => libigl}/igl/edge_collapse_is_valid.h (100%) rename src/{ => libigl}/igl/edge_flaps.cpp (100%) rename src/{ => libigl}/igl/edge_flaps.h (100%) rename src/{ => libigl}/igl/edge_lengths.cpp (100%) rename src/{ => libigl}/igl/edge_lengths.h (100%) rename src/{ => libigl}/igl/edge_topology.cpp (100%) rename src/{ => libigl}/igl/edge_topology.h (100%) rename src/{ => libigl}/igl/edges.cpp (100%) rename src/{ => libigl}/igl/edges.h (100%) rename src/{ => libigl}/igl/edges_to_path.cpp (100%) rename src/{ => libigl}/igl/edges_to_path.h (100%) rename src/{ => libigl}/igl/eigs.cpp (100%) rename src/{ => libigl}/igl/eigs.h (100%) rename src/{ => libigl}/igl/embree/EmbreeIntersector.h (100%) rename src/{ => libigl}/igl/embree/Embree_convenience.h (100%) rename src/{ => libigl}/igl/embree/ambient_occlusion.cpp (100%) rename src/{ => libigl}/igl/embree/ambient_occlusion.h (100%) rename src/{ => libigl}/igl/embree/bone_heat.cpp (100%) rename src/{ => libigl}/igl/embree/bone_heat.h (100%) rename src/{ => libigl}/igl/embree/bone_visible.cpp (100%) rename src/{ => libigl}/igl/embree/bone_visible.h (100%) rename src/{ => libigl}/igl/embree/embree2/rtcore.h (100%) rename src/{ => libigl}/igl/embree/embree2/rtcore.isph (100%) rename src/{ => libigl}/igl/embree/embree2/rtcore_geometry.h (100%) rename src/{ => libigl}/igl/embree/embree2/rtcore_geometry.isph (100%) rename src/{ => libigl}/igl/embree/embree2/rtcore_geometry_user.h (100%) rename src/{ => libigl}/igl/embree/embree2/rtcore_geometry_user.isph (100%) rename src/{ => libigl}/igl/embree/embree2/rtcore_ray.h (100%) rename src/{ => libigl}/igl/embree/embree2/rtcore_ray.isph (100%) rename src/{ => libigl}/igl/embree/embree2/rtcore_scene.h (100%) rename src/{ => libigl}/igl/embree/embree2/rtcore_scene.isph (100%) rename src/{ => libigl}/igl/embree/line_mesh_intersection.cpp (100%) rename src/{ => libigl}/igl/embree/line_mesh_intersection.h (100%) rename src/{ => libigl}/igl/embree/reorient_facets_raycast.cpp (100%) rename src/{ => libigl}/igl/embree/reorient_facets_raycast.h (100%) rename src/{ => libigl}/igl/embree/shape_diameter_function.cpp (100%) rename src/{ => libigl}/igl/embree/shape_diameter_function.h (100%) rename src/{ => libigl}/igl/embree/unproject_in_mesh.cpp (100%) rename src/{ => libigl}/igl/embree/unproject_in_mesh.h (100%) rename src/{ => libigl}/igl/embree/unproject_onto_mesh.cpp (100%) rename src/{ => libigl}/igl/embree/unproject_onto_mesh.h (100%) rename src/{ => libigl}/igl/euler_characteristic.cpp (100%) rename src/{ => libigl}/igl/euler_characteristic.h (100%) rename src/{ => libigl}/igl/exact_geodesic.cpp (100%) rename src/{ => libigl}/igl/exact_geodesic.h (100%) rename src/{ => libigl}/igl/example_fun.cpp (100%) rename src/{ => libigl}/igl/example_fun.h (100%) rename src/{ => libigl}/igl/exterior_edges.cpp (100%) rename src/{ => libigl}/igl/exterior_edges.h (100%) rename src/{ => libigl}/igl/extract_manifold_patches.cpp (100%) rename src/{ => libigl}/igl/extract_manifold_patches.h (100%) rename src/{ => libigl}/igl/extract_non_manifold_edge_curves.cpp (100%) rename src/{ => libigl}/igl/extract_non_manifold_edge_curves.h (100%) rename src/{ => libigl}/igl/face_areas.cpp (100%) rename src/{ => libigl}/igl/face_areas.h (100%) rename src/{ => libigl}/igl/face_occurrences.cpp (100%) rename src/{ => libigl}/igl/face_occurrences.h (100%) rename src/{ => libigl}/igl/faces_first.cpp (100%) rename src/{ => libigl}/igl/faces_first.h (100%) rename src/{ => libigl}/igl/facet_components.cpp (100%) rename src/{ => libigl}/igl/facet_components.h (100%) rename src/{ => libigl}/igl/false_barycentric_subdivision.cpp (100%) rename src/{ => libigl}/igl/false_barycentric_subdivision.h (100%) rename src/{ => libigl}/igl/fast_winding_number.cpp (100%) rename src/{ => libigl}/igl/fast_winding_number.h (100%) rename src/{ => libigl}/igl/file_contents_as_string.cpp (100%) rename src/{ => libigl}/igl/file_contents_as_string.h (100%) rename src/{ => libigl}/igl/file_dialog_open.cpp (100%) rename src/{ => libigl}/igl/file_dialog_open.h (100%) rename src/{ => libigl}/igl/file_dialog_save.cpp (100%) rename src/{ => libigl}/igl/file_dialog_save.h (100%) rename src/{ => libigl}/igl/file_exists.cpp (100%) rename src/{ => libigl}/igl/file_exists.h (100%) rename src/{ => libigl}/igl/find.cpp (100%) rename src/{ => libigl}/igl/find.h (100%) rename src/{ => libigl}/igl/find_cross_field_singularities.cpp (100%) rename src/{ => libigl}/igl/find_cross_field_singularities.h (100%) rename src/{ => libigl}/igl/find_zero.cpp (100%) rename src/{ => libigl}/igl/find_zero.h (100%) rename src/{ => libigl}/igl/fit_plane.cpp (100%) rename src/{ => libigl}/igl/fit_plane.h (100%) rename src/{ => libigl}/igl/fit_rotations.cpp (100%) rename src/{ => libigl}/igl/fit_rotations.h (100%) rename src/{ => libigl}/igl/flip_avoiding_line_search.cpp (100%) rename src/{ => libigl}/igl/flip_avoiding_line_search.h (100%) rename src/{ => libigl}/igl/flip_edge.cpp (100%) rename src/{ => libigl}/igl/flip_edge.h (100%) rename src/{ => libigl}/igl/flipped_triangles.cpp (100%) rename src/{ => libigl}/igl/flipped_triangles.h (100%) rename src/{ => libigl}/igl/flood_fill.cpp (100%) rename src/{ => libigl}/igl/flood_fill.h (100%) rename src/{ => libigl}/igl/floor.cpp (100%) rename src/{ => libigl}/igl/floor.h (100%) rename src/{ => libigl}/igl/for_each.h (100%) rename src/{ => libigl}/igl/forward_kinematics.cpp (100%) rename src/{ => libigl}/igl/forward_kinematics.h (100%) rename src/{ => libigl}/igl/frame_field_deformer.cpp (100%) rename src/{ => libigl}/igl/frame_field_deformer.h (100%) rename src/{ => libigl}/igl/frame_to_cross_field.cpp (100%) rename src/{ => libigl}/igl/frame_to_cross_field.h (100%) rename src/{ => libigl}/igl/frustum.cpp (100%) rename src/{ => libigl}/igl/frustum.h (100%) rename src/{ => libigl}/igl/gaussian_curvature.cpp (100%) rename src/{ => libigl}/igl/gaussian_curvature.h (100%) rename src/{ => libigl}/igl/get_seconds.cpp (100%) rename src/{ => libigl}/igl/get_seconds.h (100%) rename src/{ => libigl}/igl/get_seconds_hires.cpp (100%) rename src/{ => libigl}/igl/get_seconds_hires.h (100%) rename src/{ => libigl}/igl/grad.cpp (100%) rename src/{ => libigl}/igl/grad.h (100%) rename src/{ => libigl}/igl/grid.cpp (100%) rename src/{ => libigl}/igl/grid.h (100%) rename src/{ => libigl}/igl/grid_search.cpp (100%) rename src/{ => libigl}/igl/grid_search.h (100%) rename src/{ => libigl}/igl/group_sum_matrix.cpp (100%) rename src/{ => libigl}/igl/group_sum_matrix.h (100%) rename src/{ => libigl}/igl/guess_extension.cpp (100%) rename src/{ => libigl}/igl/guess_extension.h (100%) rename src/{ => libigl}/igl/harmonic.cpp (100%) rename src/{ => libigl}/igl/harmonic.h (100%) rename src/{ => libigl}/igl/harwell_boeing.cpp (100%) rename src/{ => libigl}/igl/harwell_boeing.h (100%) rename src/{ => libigl}/igl/hausdorff.cpp (100%) rename src/{ => libigl}/igl/hausdorff.h (100%) rename src/{ => libigl}/igl/hessian.cpp (100%) rename src/{ => libigl}/igl/hessian.h (100%) rename src/{ => libigl}/igl/hessian_energy.cpp (100%) rename src/{ => libigl}/igl/hessian_energy.h (100%) rename src/{ => libigl}/igl/histc.cpp (100%) rename src/{ => libigl}/igl/histc.h (100%) rename src/{ => libigl}/igl/hsv_to_rgb.cpp (100%) rename src/{ => libigl}/igl/hsv_to_rgb.h (100%) rename src/{ => libigl}/igl/igl_inline.h (100%) rename src/{ => libigl}/igl/in_element.cpp (100%) rename src/{ => libigl}/igl/in_element.h (100%) rename src/{ => libigl}/igl/infinite_cost_stopping_condition.cpp (100%) rename src/{ => libigl}/igl/infinite_cost_stopping_condition.h (100%) rename src/{ => libigl}/igl/inradius.cpp (100%) rename src/{ => libigl}/igl/inradius.h (100%) rename src/{ => libigl}/igl/internal_angles.cpp (100%) rename src/{ => libigl}/igl/internal_angles.h (100%) rename src/{ => libigl}/igl/intersect.cpp (100%) rename src/{ => libigl}/igl/intersect.h (100%) rename src/{ => libigl}/igl/invert_diag.cpp (100%) rename src/{ => libigl}/igl/invert_diag.h (100%) rename src/{ => libigl}/igl/is_border_vertex.cpp (100%) rename src/{ => libigl}/igl/is_border_vertex.h (100%) rename src/{ => libigl}/igl/is_boundary_edge.cpp (100%) rename src/{ => libigl}/igl/is_boundary_edge.h (100%) rename src/{ => libigl}/igl/is_dir.cpp (100%) rename src/{ => libigl}/igl/is_dir.h (100%) rename src/{ => libigl}/igl/is_edge_manifold.cpp (100%) rename src/{ => libigl}/igl/is_edge_manifold.h (100%) rename src/{ => libigl}/igl/is_file.cpp (100%) rename src/{ => libigl}/igl/is_file.h (100%) rename src/{ => libigl}/igl/is_irregular_vertex.cpp (100%) rename src/{ => libigl}/igl/is_irregular_vertex.h (100%) rename src/{ => libigl}/igl/is_planar.cpp (100%) rename src/{ => libigl}/igl/is_planar.h (100%) rename src/{ => libigl}/igl/is_readable.cpp (100%) rename src/{ => libigl}/igl/is_readable.h (100%) rename src/{ => libigl}/igl/is_sparse.cpp (100%) rename src/{ => libigl}/igl/is_sparse.h (100%) rename src/{ => libigl}/igl/is_stl.cpp (100%) rename src/{ => libigl}/igl/is_stl.h (100%) rename src/{ => libigl}/igl/is_symmetric.cpp (100%) rename src/{ => libigl}/igl/is_symmetric.h (100%) rename src/{ => libigl}/igl/is_vertex_manifold.cpp (100%) rename src/{ => libigl}/igl/is_vertex_manifold.h (100%) rename src/{ => libigl}/igl/is_writable.cpp (100%) rename src/{ => libigl}/igl/is_writable.h (100%) rename src/{ => libigl}/igl/isdiag.cpp (100%) rename src/{ => libigl}/igl/isdiag.h (100%) rename src/{ => libigl}/igl/ismember.cpp (100%) rename src/{ => libigl}/igl/ismember.h (100%) rename src/{ => libigl}/igl/isolines.cpp (100%) rename src/{ => libigl}/igl/isolines.h (100%) rename src/{ => libigl}/igl/jet.cpp (100%) rename src/{ => libigl}/igl/jet.h (100%) rename src/{ => libigl}/igl/knn.cpp (100%) rename src/{ => libigl}/igl/knn.h (100%) rename src/{ => libigl}/igl/launch_medit.cpp (100%) rename src/{ => libigl}/igl/launch_medit.h (100%) rename src/{ => libigl}/igl/lbs_matrix.cpp (100%) rename src/{ => libigl}/igl/lbs_matrix.h (100%) rename src/{ => libigl}/igl/lexicographic_triangulation.cpp (100%) rename src/{ => libigl}/igl/lexicographic_triangulation.h (100%) rename src/{ => libigl}/igl/lim/lim.cpp (100%) rename src/{ => libigl}/igl/lim/lim.h (100%) rename src/{ => libigl}/igl/limit_faces.cpp (100%) rename src/{ => libigl}/igl/limit_faces.h (100%) rename src/{ => libigl}/igl/line_field_missmatch.cpp (100%) rename src/{ => libigl}/igl/line_field_missmatch.h (100%) rename src/{ => libigl}/igl/line_search.cpp (100%) rename src/{ => libigl}/igl/line_search.h (100%) rename src/{ => libigl}/igl/line_segment_in_rectangle.cpp (100%) rename src/{ => libigl}/igl/line_segment_in_rectangle.h (100%) rename src/{ => libigl}/igl/linprog.cpp (100%) rename src/{ => libigl}/igl/linprog.h (100%) rename src/{ => libigl}/igl/list_to_matrix.cpp (100%) rename src/{ => libigl}/igl/list_to_matrix.h (100%) rename src/{ => libigl}/igl/local_basis.cpp (100%) rename src/{ => libigl}/igl/local_basis.h (100%) rename src/{ => libigl}/igl/look_at.cpp (100%) rename src/{ => libigl}/igl/look_at.h (100%) rename src/{ => libigl}/igl/loop.cpp (100%) rename src/{ => libigl}/igl/loop.h (100%) rename src/{ => libigl}/igl/lscm.cpp (100%) rename src/{ => libigl}/igl/lscm.h (100%) rename src/{ => libigl}/igl/map_vertices_to_circle.cpp (100%) rename src/{ => libigl}/igl/map_vertices_to_circle.h (100%) rename src/{ => libigl}/igl/massmatrix.cpp (100%) rename src/{ => libigl}/igl/massmatrix.h (100%) rename src/{ => libigl}/igl/mat_max.cpp (100%) rename src/{ => libigl}/igl/mat_max.h (100%) rename src/{ => libigl}/igl/mat_min.cpp (100%) rename src/{ => libigl}/igl/mat_min.h (100%) rename src/{ => libigl}/igl/mat_to_quat.cpp (100%) rename src/{ => libigl}/igl/mat_to_quat.h (100%) rename src/{ => libigl}/igl/material_colors.h (100%) rename src/{ => libigl}/igl/matlab/MatlabWorkspace.h (100%) rename src/{ => libigl}/igl/matlab/MexStream.h (100%) rename src/{ => libigl}/igl/matlab/matlabinterface.cpp (100%) rename src/{ => libigl}/igl/matlab/matlabinterface.h (100%) rename src/{ => libigl}/igl/matlab/mexErrMsgTxt.cpp (100%) rename src/{ => libigl}/igl/matlab/mexErrMsgTxt.h (100%) rename src/{ => libigl}/igl/matlab/parse_rhs.cpp (100%) rename src/{ => libigl}/igl/matlab/parse_rhs.h (100%) rename src/{ => libigl}/igl/matlab/prepare_lhs.cpp (100%) rename src/{ => libigl}/igl/matlab/prepare_lhs.h (100%) rename src/{ => libigl}/igl/matlab/requires_arg.cpp (100%) rename src/{ => libigl}/igl/matlab/requires_arg.h (100%) rename src/{ => libigl}/igl/matlab/validate_arg.cpp (100%) rename src/{ => libigl}/igl/matlab/validate_arg.h (100%) rename src/{ => libigl}/igl/matlab_format.cpp (100%) rename src/{ => libigl}/igl/matlab_format.h (100%) rename src/{ => libigl}/igl/matrix_to_list.cpp (100%) rename src/{ => libigl}/igl/matrix_to_list.h (100%) rename src/{ => libigl}/igl/max.cpp (100%) rename src/{ => libigl}/igl/max.h (100%) rename src/{ => libigl}/igl/max_faces_stopping_condition.cpp (100%) rename src/{ => libigl}/igl/max_faces_stopping_condition.h (100%) rename src/{ => libigl}/igl/max_size.cpp (100%) rename src/{ => libigl}/igl/max_size.h (100%) rename src/{ => libigl}/igl/median.cpp (100%) rename src/{ => libigl}/igl/median.h (100%) rename src/{ => libigl}/igl/min.cpp (100%) rename src/{ => libigl}/igl/min.h (100%) rename src/{ => libigl}/igl/min_quad_dense.cpp (100%) rename src/{ => libigl}/igl/min_quad_dense.h (100%) rename src/{ => libigl}/igl/min_quad_with_fixed.cpp (100%) rename src/{ => libigl}/igl/min_quad_with_fixed.h (100%) rename src/{ => libigl}/igl/min_size.cpp (100%) rename src/{ => libigl}/igl/min_size.h (100%) rename src/{ => libigl}/igl/mod.cpp (100%) rename src/{ => libigl}/igl/mod.h (100%) rename src/{ => libigl}/igl/mode.cpp (100%) rename src/{ => libigl}/igl/mode.h (100%) rename src/{ => libigl}/igl/mosek/bbw.cpp (100%) rename src/{ => libigl}/igl/mosek/bbw.h (100%) rename src/{ => libigl}/igl/mosek/mosek_guarded.cpp (100%) rename src/{ => libigl}/igl/mosek/mosek_guarded.h (100%) rename src/{ => libigl}/igl/mosek/mosek_linprog.cpp (100%) rename src/{ => libigl}/igl/mosek/mosek_linprog.h (100%) rename src/{ => libigl}/igl/mosek/mosek_quadprog.cpp (100%) rename src/{ => libigl}/igl/mosek/mosek_quadprog.h (100%) rename src/{ => libigl}/igl/mvc.cpp (100%) rename src/{ => libigl}/igl/mvc.h (100%) rename src/{ => libigl}/igl/nchoosek.cpp (100%) rename src/{ => libigl}/igl/nchoosek.h (100%) rename src/{ => libigl}/igl/next_filename.cpp (100%) rename src/{ => libigl}/igl/next_filename.h (100%) rename src/{ => libigl}/igl/normal_derivative.cpp (100%) rename src/{ => libigl}/igl/normal_derivative.h (100%) rename src/{ => libigl}/igl/normalize_quat.cpp (100%) rename src/{ => libigl}/igl/normalize_quat.h (100%) rename src/{ => libigl}/igl/normalize_row_lengths.cpp (100%) rename src/{ => libigl}/igl/normalize_row_lengths.h (100%) rename src/{ => libigl}/igl/normalize_row_sums.cpp (100%) rename src/{ => libigl}/igl/normalize_row_sums.h (100%) rename src/{ => libigl}/igl/null.cpp (100%) rename src/{ => libigl}/igl/null.h (100%) rename src/{ => libigl}/igl/octree.cpp (100%) rename src/{ => libigl}/igl/octree.h (100%) rename src/{ => libigl}/igl/on_boundary.cpp (100%) rename src/{ => libigl}/igl/on_boundary.h (100%) rename src/{ => libigl}/igl/opengl/MeshGL.cpp (100%) rename src/{ => libigl}/igl/opengl/MeshGL.h (100%) rename src/{ => libigl}/igl/opengl/ViewerCore.cpp (100%) rename src/{ => libigl}/igl/opengl/ViewerCore.h (100%) rename src/{ => libigl}/igl/opengl/ViewerData.cpp (100%) rename src/{ => libigl}/igl/opengl/ViewerData.h (100%) rename src/{ => libigl}/igl/opengl/bind_vertex_attrib_array.cpp (100%) rename src/{ => libigl}/igl/opengl/bind_vertex_attrib_array.h (100%) rename src/{ => libigl}/igl/opengl/create_index_vbo.cpp (100%) rename src/{ => libigl}/igl/opengl/create_index_vbo.h (100%) rename src/{ => libigl}/igl/opengl/create_mesh_vbo.cpp (100%) rename src/{ => libigl}/igl/opengl/create_mesh_vbo.h (100%) rename src/{ => libigl}/igl/opengl/create_shader_program.cpp (100%) rename src/{ => libigl}/igl/opengl/create_shader_program.h (100%) rename src/{ => libigl}/igl/opengl/create_vector_vbo.cpp (100%) rename src/{ => libigl}/igl/opengl/create_vector_vbo.h (100%) rename src/{ => libigl}/igl/opengl/destroy_shader_program.cpp (100%) rename src/{ => libigl}/igl/opengl/destroy_shader_program.h (100%) rename src/{ => libigl}/igl/opengl/gl.h (100%) rename src/{ => libigl}/igl/opengl/gl_type_size.cpp (100%) rename src/{ => libigl}/igl/opengl/gl_type_size.h (100%) rename src/{ => libigl}/igl/opengl/glfw/Viewer.cpp (100%) rename src/{ => libigl}/igl/opengl/glfw/Viewer.h (100%) rename src/{ => libigl}/igl/opengl/glfw/ViewerPlugin.h (100%) rename src/{ => libigl}/igl/opengl/glfw/background_window.cpp (100%) rename src/{ => libigl}/igl/opengl/glfw/background_window.h (100%) rename src/{ => libigl}/igl/opengl/glfw/imgui/ImGuiHelpers.h (100%) rename src/{ => libigl}/igl/opengl/glfw/imgui/ImGuiMenu.cpp (100%) rename src/{ => libigl}/igl/opengl/glfw/imgui/ImGuiMenu.h (100%) rename src/{ => libigl}/igl/opengl/glfw/map_texture.cpp (100%) rename src/{ => libigl}/igl/opengl/glfw/map_texture.h (100%) rename src/{ => libigl}/igl/opengl/init_render_to_texture.cpp (100%) rename src/{ => libigl}/igl/opengl/init_render_to_texture.h (100%) rename src/{ => libigl}/igl/opengl/load_shader.cpp (100%) rename src/{ => libigl}/igl/opengl/load_shader.h (100%) rename src/{ => libigl}/igl/opengl/print_program_info_log.cpp (100%) rename src/{ => libigl}/igl/opengl/print_program_info_log.h (100%) rename src/{ => libigl}/igl/opengl/print_shader_info_log.cpp (100%) rename src/{ => libigl}/igl/opengl/print_shader_info_log.h (100%) rename src/{ => libigl}/igl/opengl/report_gl_error.cpp (100%) rename src/{ => libigl}/igl/opengl/report_gl_error.h (100%) rename src/{ => libigl}/igl/opengl/uniform_type_to_string.cpp (100%) rename src/{ => libigl}/igl/opengl/uniform_type_to_string.h (100%) rename src/{ => libigl}/igl/opengl/vertex_array.cpp (100%) rename src/{ => libigl}/igl/opengl/vertex_array.h (100%) rename src/{ => libigl}/igl/opengl2/MouseController.h (100%) rename src/{ => libigl}/igl/opengl2/RotateWidget.h (100%) rename src/{ => libigl}/igl/opengl2/TranslateWidget.h (100%) rename src/{ => libigl}/igl/opengl2/draw_beach_ball.cpp (100%) rename src/{ => libigl}/igl/opengl2/draw_beach_ball.h (100%) rename src/{ => libigl}/igl/opengl2/draw_floor.cpp (100%) rename src/{ => libigl}/igl/opengl2/draw_floor.h (100%) rename src/{ => libigl}/igl/opengl2/draw_mesh.cpp (100%) rename src/{ => libigl}/igl/opengl2/draw_mesh.h (100%) rename src/{ => libigl}/igl/opengl2/draw_point.cpp (100%) rename src/{ => libigl}/igl/opengl2/draw_point.h (100%) rename src/{ => libigl}/igl/opengl2/draw_rectangular_marquee.cpp (100%) rename src/{ => libigl}/igl/opengl2/draw_rectangular_marquee.h (100%) rename src/{ => libigl}/igl/opengl2/draw_skeleton_3d.cpp (100%) rename src/{ => libigl}/igl/opengl2/draw_skeleton_3d.h (100%) rename src/{ => libigl}/igl/opengl2/draw_skeleton_vector_graphics.cpp (100%) rename src/{ => libigl}/igl/opengl2/draw_skeleton_vector_graphics.h (100%) rename src/{ => libigl}/igl/opengl2/flare_textures.h (100%) rename src/{ => libigl}/igl/opengl2/gl.h (100%) rename src/{ => libigl}/igl/opengl2/glext.h (100%) rename src/{ => libigl}/igl/opengl2/glu.h (100%) rename src/{ => libigl}/igl/opengl2/lens_flare.cpp (100%) rename src/{ => libigl}/igl/opengl2/lens_flare.h (100%) rename src/{ => libigl}/igl/opengl2/model_proj_viewport.cpp (100%) rename src/{ => libigl}/igl/opengl2/model_proj_viewport.h (100%) rename src/{ => libigl}/igl/opengl2/print_gl_get.cpp (100%) rename src/{ => libigl}/igl/opengl2/print_gl_get.h (100%) rename src/{ => libigl}/igl/opengl2/project.cpp (100%) rename src/{ => libigl}/igl/opengl2/project.h (100%) rename src/{ => libigl}/igl/opengl2/right_axis.cpp (100%) rename src/{ => libigl}/igl/opengl2/right_axis.h (100%) rename src/{ => libigl}/igl/opengl2/shine_textures.h (100%) rename src/{ => libigl}/igl/opengl2/sort_triangles.cpp (100%) rename src/{ => libigl}/igl/opengl2/sort_triangles.h (100%) rename src/{ => libigl}/igl/opengl2/unproject.cpp (100%) rename src/{ => libigl}/igl/opengl2/unproject.h (100%) rename src/{ => libigl}/igl/opengl2/unproject_to_zero_plane.cpp (100%) rename src/{ => libigl}/igl/opengl2/unproject_to_zero_plane.h (100%) rename src/{ => libigl}/igl/opengl2/up_axis.cpp (100%) rename src/{ => libigl}/igl/opengl2/up_axis.h (100%) rename src/{ => libigl}/igl/opengl2/view_axis.cpp (100%) rename src/{ => libigl}/igl/opengl2/view_axis.h (100%) rename src/{ => libigl}/igl/orient_outward.cpp (100%) rename src/{ => libigl}/igl/orient_outward.h (100%) rename src/{ => libigl}/igl/orientable_patches.cpp (100%) rename src/{ => libigl}/igl/orientable_patches.h (100%) rename src/{ => libigl}/igl/oriented_facets.cpp (100%) rename src/{ => libigl}/igl/oriented_facets.h (100%) rename src/{ => libigl}/igl/orth.cpp (100%) rename src/{ => libigl}/igl/orth.h (100%) rename src/{ => libigl}/igl/ortho.cpp (100%) rename src/{ => libigl}/igl/ortho.h (100%) rename src/{ => libigl}/igl/outer_element.cpp (100%) rename src/{ => libigl}/igl/outer_element.h (100%) rename src/{ => libigl}/igl/parallel_for.h (100%) rename src/{ => libigl}/igl/parallel_transport_angles.cpp (100%) rename src/{ => libigl}/igl/parallel_transport_angles.h (100%) rename src/{ => libigl}/igl/partition.cpp (100%) rename src/{ => libigl}/igl/partition.h (100%) rename src/{ => libigl}/igl/parula.cpp (100%) rename src/{ => libigl}/igl/parula.h (100%) rename src/{ => libigl}/igl/path_to_executable.cpp (100%) rename src/{ => libigl}/igl/path_to_executable.h (100%) rename src/{ => libigl}/igl/pathinfo.cpp (100%) rename src/{ => libigl}/igl/pathinfo.h (100%) rename src/{ => libigl}/igl/per_corner_normals.cpp (100%) rename src/{ => libigl}/igl/per_corner_normals.h (100%) rename src/{ => libigl}/igl/per_edge_normals.cpp (100%) rename src/{ => libigl}/igl/per_edge_normals.h (100%) rename src/{ => libigl}/igl/per_face_normals.cpp (100%) rename src/{ => libigl}/igl/per_face_normals.h (100%) rename src/{ => libigl}/igl/per_vertex_attribute_smoothing.cpp (100%) rename src/{ => libigl}/igl/per_vertex_attribute_smoothing.h (100%) rename src/{ => libigl}/igl/per_vertex_normals.cpp (100%) rename src/{ => libigl}/igl/per_vertex_normals.h (100%) rename src/{ => libigl}/igl/per_vertex_point_to_plane_quadrics.cpp (100%) rename src/{ => libigl}/igl/per_vertex_point_to_plane_quadrics.h (100%) rename src/{ => libigl}/igl/piecewise_constant_winding_number.cpp (100%) rename src/{ => libigl}/igl/piecewise_constant_winding_number.h (100%) rename src/{ => libigl}/igl/pinv.cpp (100%) rename src/{ => libigl}/igl/pinv.h (100%) rename src/{ => libigl}/igl/planarize_quad_mesh.cpp (100%) rename src/{ => libigl}/igl/planarize_quad_mesh.h (100%) rename src/{ => libigl}/igl/ply.h (100%) rename src/{ => libigl}/igl/png/readPNG.cpp (100%) rename src/{ => libigl}/igl/png/readPNG.h (100%) rename src/{ => libigl}/igl/png/render_to_png.cpp (100%) rename src/{ => libigl}/igl/png/render_to_png.h (100%) rename src/{ => libigl}/igl/png/render_to_png_async.cpp (100%) rename src/{ => libigl}/igl/png/render_to_png_async.h (100%) rename src/{ => libigl}/igl/png/texture_from_file.cpp (100%) rename src/{ => libigl}/igl/png/texture_from_file.h (100%) rename src/{ => libigl}/igl/png/texture_from_png.cpp (100%) rename src/{ => libigl}/igl/png/texture_from_png.h (100%) rename src/{ => libigl}/igl/png/writePNG.cpp (100%) rename src/{ => libigl}/igl/png/writePNG.h (100%) rename src/{ => libigl}/igl/point_in_circle.cpp (100%) rename src/{ => libigl}/igl/point_in_circle.h (100%) rename src/{ => libigl}/igl/point_in_poly.cpp (100%) rename src/{ => libigl}/igl/point_in_poly.h (100%) rename src/{ => libigl}/igl/point_mesh_squared_distance.cpp (100%) rename src/{ => libigl}/igl/point_mesh_squared_distance.h (100%) rename src/{ => libigl}/igl/point_simplex_squared_distance.cpp (100%) rename src/{ => libigl}/igl/point_simplex_squared_distance.h (100%) rename src/{ => libigl}/igl/polar_dec.cpp (100%) rename src/{ => libigl}/igl/polar_dec.h (100%) rename src/{ => libigl}/igl/polar_svd.cpp (100%) rename src/{ => libigl}/igl/polar_svd.h (100%) rename src/{ => libigl}/igl/polar_svd3x3.cpp (100%) rename src/{ => libigl}/igl/polar_svd3x3.h (100%) rename src/{ => libigl}/igl/polygon_mesh_to_triangle_mesh.cpp (100%) rename src/{ => libigl}/igl/polygon_mesh_to_triangle_mesh.h (100%) rename src/{ => libigl}/igl/principal_curvature.cpp (100%) rename src/{ => libigl}/igl/principal_curvature.h (100%) rename src/{ => libigl}/igl/print_ijv.cpp (100%) rename src/{ => libigl}/igl/print_ijv.h (100%) rename src/{ => libigl}/igl/print_vector.cpp (100%) rename src/{ => libigl}/igl/print_vector.h (100%) rename src/{ => libigl}/igl/procrustes.cpp (100%) rename src/{ => libigl}/igl/procrustes.h (100%) rename src/{ => libigl}/igl/project.cpp (100%) rename src/{ => libigl}/igl/project.h (100%) rename src/{ => libigl}/igl/project_isometrically_to_plane.cpp (100%) rename src/{ => libigl}/igl/project_isometrically_to_plane.h (100%) rename src/{ => libigl}/igl/project_to_line.cpp (100%) rename src/{ => libigl}/igl/project_to_line.h (100%) rename src/{ => libigl}/igl/project_to_line_segment.cpp (100%) rename src/{ => libigl}/igl/project_to_line_segment.h (100%) rename src/{ => libigl}/igl/pseudonormal_test.cpp (100%) rename src/{ => libigl}/igl/pseudonormal_test.h (100%) rename src/{ => libigl}/igl/pso.cpp (100%) rename src/{ => libigl}/igl/pso.h (100%) rename src/{ => libigl}/igl/qslim.cpp (100%) rename src/{ => libigl}/igl/qslim.h (100%) rename src/{ => libigl}/igl/qslim_optimal_collapse_edge_callbacks.cpp (100%) rename src/{ => libigl}/igl/qslim_optimal_collapse_edge_callbacks.h (100%) rename src/{ => libigl}/igl/quad_planarity.cpp (100%) rename src/{ => libigl}/igl/quad_planarity.h (100%) rename src/{ => libigl}/igl/quadric_binary_plus_operator.cpp (100%) rename src/{ => libigl}/igl/quadric_binary_plus_operator.h (100%) rename src/{ => libigl}/igl/quat_conjugate.cpp (100%) rename src/{ => libigl}/igl/quat_conjugate.h (100%) rename src/{ => libigl}/igl/quat_mult.cpp (100%) rename src/{ => libigl}/igl/quat_mult.h (100%) rename src/{ => libigl}/igl/quat_to_axis_angle.cpp (100%) rename src/{ => libigl}/igl/quat_to_axis_angle.h (100%) rename src/{ => libigl}/igl/quat_to_mat.cpp (100%) rename src/{ => libigl}/igl/quat_to_mat.h (100%) rename src/{ => libigl}/igl/quats_to_column.cpp (100%) rename src/{ => libigl}/igl/quats_to_column.h (100%) rename src/{ => libigl}/igl/ramer_douglas_peucker.cpp (100%) rename src/{ => libigl}/igl/ramer_douglas_peucker.h (100%) rename src/{ => libigl}/igl/random_dir.cpp (100%) rename src/{ => libigl}/igl/random_dir.h (100%) rename src/{ => libigl}/igl/random_points_on_mesh.cpp (100%) rename src/{ => libigl}/igl/random_points_on_mesh.h (100%) rename src/{ => libigl}/igl/random_quaternion.cpp (100%) rename src/{ => libigl}/igl/random_quaternion.h (100%) rename src/{ => libigl}/igl/random_search.cpp (100%) rename src/{ => libigl}/igl/random_search.h (100%) rename src/{ => libigl}/igl/randperm.cpp (100%) rename src/{ => libigl}/igl/randperm.h (100%) rename src/{ => libigl}/igl/ray_box_intersect.cpp (100%) rename src/{ => libigl}/igl/ray_box_intersect.h (100%) rename src/{ => libigl}/igl/ray_mesh_intersect.cpp (100%) rename src/{ => libigl}/igl/ray_mesh_intersect.h (100%) rename src/{ => libigl}/igl/ray_sphere_intersect.cpp (100%) rename src/{ => libigl}/igl/ray_sphere_intersect.h (100%) rename src/{ => libigl}/igl/raytri.c (100%) rename src/{ => libigl}/igl/readBF.cpp (100%) rename src/{ => libigl}/igl/readBF.h (100%) rename src/{ => libigl}/igl/readCSV.cpp (100%) rename src/{ => libigl}/igl/readCSV.h (100%) rename src/{ => libigl}/igl/readDMAT.cpp (100%) rename src/{ => libigl}/igl/readDMAT.h (100%) rename src/{ => libigl}/igl/readMESH.cpp (100%) rename src/{ => libigl}/igl/readMESH.h (100%) rename src/{ => libigl}/igl/readMSH.cpp (100%) rename src/{ => libigl}/igl/readMSH.h (100%) rename src/{ => libigl}/igl/readNODE.cpp (100%) rename src/{ => libigl}/igl/readNODE.h (100%) rename src/{ => libigl}/igl/readOBJ.cpp (100%) rename src/{ => libigl}/igl/readOBJ.h (100%) rename src/{ => libigl}/igl/readOFF.cpp (100%) rename src/{ => libigl}/igl/readOFF.h (100%) rename src/{ => libigl}/igl/readPLY.cpp (100%) rename src/{ => libigl}/igl/readPLY.h (100%) rename src/{ => libigl}/igl/readSTL.cpp (100%) rename src/{ => libigl}/igl/readSTL.h (100%) rename src/{ => libigl}/igl/readTGF.cpp (100%) rename src/{ => libigl}/igl/readTGF.h (100%) rename src/{ => libigl}/igl/readWRL.cpp (100%) rename src/{ => libigl}/igl/readWRL.h (100%) rename src/{ => libigl}/igl/read_triangle_mesh.cpp (100%) rename src/{ => libigl}/igl/read_triangle_mesh.h (100%) rename src/{ => libigl}/igl/redux.h (100%) rename src/{ => libigl}/igl/remesh_along_isoline.cpp (100%) rename src/{ => libigl}/igl/remesh_along_isoline.h (100%) rename src/{ => libigl}/igl/remove_duplicate_vertices.cpp (100%) rename src/{ => libigl}/igl/remove_duplicate_vertices.h (100%) rename src/{ => libigl}/igl/remove_duplicates.cpp (100%) rename src/{ => libigl}/igl/remove_duplicates.h (100%) rename src/{ => libigl}/igl/remove_unreferenced.cpp (100%) rename src/{ => libigl}/igl/remove_unreferenced.h (100%) rename src/{ => libigl}/igl/reorder.cpp (100%) rename src/{ => libigl}/igl/reorder.h (100%) rename src/{ => libigl}/igl/repdiag.cpp (100%) rename src/{ => libigl}/igl/repdiag.h (100%) rename src/{ => libigl}/igl/repmat.cpp (100%) rename src/{ => libigl}/igl/repmat.h (100%) rename src/{ => libigl}/igl/resolve_duplicated_faces.cpp (100%) rename src/{ => libigl}/igl/resolve_duplicated_faces.h (100%) rename src/{ => libigl}/igl/rgb_to_hsv.cpp (100%) rename src/{ => libigl}/igl/rgb_to_hsv.h (100%) rename src/{ => libigl}/igl/rotate_by_quat.cpp (100%) rename src/{ => libigl}/igl/rotate_by_quat.h (100%) rename src/{ => libigl}/igl/rotate_vectors.cpp (100%) rename src/{ => libigl}/igl/rotate_vectors.h (100%) rename src/{ => libigl}/igl/rotation_matrix_from_directions.cpp (100%) rename src/{ => libigl}/igl/rotation_matrix_from_directions.h (100%) rename src/{ => libigl}/igl/round.cpp (100%) rename src/{ => libigl}/igl/round.h (100%) rename src/{ => libigl}/igl/rows_to_matrix.cpp (100%) rename src/{ => libigl}/igl/rows_to_matrix.h (100%) rename src/{ => libigl}/igl/sample_edges.cpp (100%) rename src/{ => libigl}/igl/sample_edges.h (100%) rename src/{ => libigl}/igl/seam_edges.cpp (100%) rename src/{ => libigl}/igl/seam_edges.h (100%) rename src/{ => libigl}/igl/segment_segment_intersect.cpp (100%) rename src/{ => libigl}/igl/segment_segment_intersect.h (100%) rename src/{ => libigl}/igl/serialize.h (100%) rename src/{ => libigl}/igl/setdiff.cpp (100%) rename src/{ => libigl}/igl/setdiff.h (100%) rename src/{ => libigl}/igl/setunion.cpp (100%) rename src/{ => libigl}/igl/setunion.h (100%) rename src/{ => libigl}/igl/setxor.cpp (100%) rename src/{ => libigl}/igl/setxor.h (100%) rename src/{ => libigl}/igl/shape_diameter_function.cpp (100%) rename src/{ => libigl}/igl/shape_diameter_function.h (100%) rename src/{ => libigl}/igl/shapeup.cpp (100%) rename src/{ => libigl}/igl/shapeup.h (100%) rename src/{ => libigl}/igl/shortest_edge_and_midpoint.cpp (100%) rename src/{ => libigl}/igl/shortest_edge_and_midpoint.h (100%) rename src/{ => libigl}/igl/signed_angle.cpp (100%) rename src/{ => libigl}/igl/signed_angle.h (100%) rename src/{ => libigl}/igl/signed_distance.cpp (100%) rename src/{ => libigl}/igl/signed_distance.h (100%) rename src/{ => libigl}/igl/simplify_polyhedron.cpp (100%) rename src/{ => libigl}/igl/simplify_polyhedron.h (100%) rename src/{ => libigl}/igl/slice.cpp (100%) rename src/{ => libigl}/igl/slice.h (100%) rename src/{ => libigl}/igl/slice_cached.cpp (100%) rename src/{ => libigl}/igl/slice_cached.h (100%) rename src/{ => libigl}/igl/slice_into.cpp (100%) rename src/{ => libigl}/igl/slice_into.h (100%) rename src/{ => libigl}/igl/slice_mask.cpp (100%) rename src/{ => libigl}/igl/slice_mask.h (100%) rename src/{ => libigl}/igl/slice_tets.cpp (100%) rename src/{ => libigl}/igl/slice_tets.h (100%) rename src/{ => libigl}/igl/slim.cpp (100%) rename src/{ => libigl}/igl/slim.h (100%) rename src/{ => libigl}/igl/snap_points.cpp (100%) rename src/{ => libigl}/igl/snap_points.h (100%) rename src/{ => libigl}/igl/snap_to_canonical_view_quat.cpp (100%) rename src/{ => libigl}/igl/snap_to_canonical_view_quat.h (100%) rename src/{ => libigl}/igl/snap_to_fixed_up.cpp (100%) rename src/{ => libigl}/igl/snap_to_fixed_up.h (100%) rename src/{ => libigl}/igl/solid_angle.cpp (100%) rename src/{ => libigl}/igl/solid_angle.h (100%) rename src/{ => libigl}/igl/sort.cpp (100%) rename src/{ => libigl}/igl/sort.h (100%) rename src/{ => libigl}/igl/sort_angles.cpp (100%) rename src/{ => libigl}/igl/sort_angles.h (100%) rename src/{ => libigl}/igl/sort_triangles.cpp (100%) rename src/{ => libigl}/igl/sort_triangles.h (100%) rename src/{ => libigl}/igl/sort_vectors_ccw.cpp (100%) rename src/{ => libigl}/igl/sort_vectors_ccw.h (100%) rename src/{ => libigl}/igl/sortrows.cpp (100%) rename src/{ => libigl}/igl/sortrows.h (100%) rename src/{ => libigl}/igl/sparse.cpp (100%) rename src/{ => libigl}/igl/sparse.h (100%) rename src/{ => libigl}/igl/sparse_cached.cpp (100%) rename src/{ => libigl}/igl/sparse_cached.h (100%) rename src/{ => libigl}/igl/speye.cpp (100%) rename src/{ => libigl}/igl/speye.h (100%) rename src/{ => libigl}/igl/squared_edge_lengths.cpp (100%) rename src/{ => libigl}/igl/squared_edge_lengths.h (100%) rename src/{ => libigl}/igl/stdin_to_temp.cpp (100%) rename src/{ => libigl}/igl/stdin_to_temp.h (100%) rename src/{ => libigl}/igl/straighten_seams.cpp (100%) rename src/{ => libigl}/igl/straighten_seams.h (100%) rename src/{ => libigl}/igl/sum.cpp (100%) rename src/{ => libigl}/igl/sum.h (100%) rename src/{ => libigl}/igl/svd3x3.cpp (100%) rename src/{ => libigl}/igl/svd3x3.h (100%) rename src/{ => libigl}/igl/svd3x3_avx.cpp (100%) rename src/{ => libigl}/igl/svd3x3_avx.h (100%) rename src/{ => libigl}/igl/svd3x3_sse.cpp (100%) rename src/{ => libigl}/igl/svd3x3_sse.h (100%) rename src/{ => libigl}/igl/swept_volume_bounding_box.cpp (100%) rename src/{ => libigl}/igl/swept_volume_bounding_box.h (100%) rename src/{ => libigl}/igl/swept_volume_signed_distance.cpp (100%) rename src/{ => libigl}/igl/swept_volume_signed_distance.h (100%) rename src/{ => libigl}/igl/trackball.cpp (100%) rename src/{ => libigl}/igl/trackball.h (100%) rename src/{ => libigl}/igl/transpose_blocks.cpp (100%) rename src/{ => libigl}/igl/transpose_blocks.h (100%) rename src/{ => libigl}/igl/triangle/cdt.cpp (100%) rename src/{ => libigl}/igl/triangle/cdt.h (100%) rename src/{ => libigl}/igl/triangle/triangulate.cpp (100%) rename src/{ => libigl}/igl/triangle/triangulate.h (100%) rename src/{ => libigl}/igl/triangle_fan.cpp (100%) rename src/{ => libigl}/igl/triangle_fan.h (100%) rename src/{ => libigl}/igl/triangle_triangle_adjacency.cpp (100%) rename src/{ => libigl}/igl/triangle_triangle_adjacency.h (100%) rename src/{ => libigl}/igl/triangles_from_strip.cpp (100%) rename src/{ => libigl}/igl/triangles_from_strip.h (100%) rename src/{ => libigl}/igl/two_axis_valuator_fixed_up.cpp (100%) rename src/{ => libigl}/igl/two_axis_valuator_fixed_up.h (100%) rename src/{ => libigl}/igl/uniformly_sample_two_manifold.cpp (100%) rename src/{ => libigl}/igl/uniformly_sample_two_manifold.h (100%) rename src/{ => libigl}/igl/unique.cpp (100%) rename src/{ => libigl}/igl/unique.h (100%) rename src/{ => libigl}/igl/unique_edge_map.cpp (100%) rename src/{ => libigl}/igl/unique_edge_map.h (100%) rename src/{ => libigl}/igl/unique_rows.cpp (100%) rename src/{ => libigl}/igl/unique_rows.h (100%) rename src/{ => libigl}/igl/unique_simplices.cpp (100%) rename src/{ => libigl}/igl/unique_simplices.h (100%) rename src/{ => libigl}/igl/unproject.cpp (100%) rename src/{ => libigl}/igl/unproject.h (100%) rename src/{ => libigl}/igl/unproject_in_mesh.cpp (100%) rename src/{ => libigl}/igl/unproject_in_mesh.h (100%) rename src/{ => libigl}/igl/unproject_onto_mesh.cpp (100%) rename src/{ => libigl}/igl/unproject_onto_mesh.h (100%) rename src/{ => libigl}/igl/unproject_ray.cpp (100%) rename src/{ => libigl}/igl/unproject_ray.h (100%) rename src/{ => libigl}/igl/unzip_corners.cpp (100%) rename src/{ => libigl}/igl/unzip_corners.h (100%) rename src/{ => libigl}/igl/upsample.cpp (100%) rename src/{ => libigl}/igl/upsample.h (100%) rename src/{ => libigl}/igl/vector_area_matrix.cpp (100%) rename src/{ => libigl}/igl/vector_area_matrix.h (100%) rename src/{ => libigl}/igl/verbose.h (100%) rename src/{ => libigl}/igl/vertex_triangle_adjacency.cpp (100%) rename src/{ => libigl}/igl/vertex_triangle_adjacency.h (100%) rename src/{ => libigl}/igl/volume.cpp (100%) rename src/{ => libigl}/igl/volume.h (100%) rename src/{ => libigl}/igl/voxel_grid.cpp (100%) rename src/{ => libigl}/igl/voxel_grid.h (100%) rename src/{ => libigl}/igl/winding_number.cpp (100%) rename src/{ => libigl}/igl/winding_number.h (100%) rename src/{ => libigl}/igl/writeBF.cpp (100%) rename src/{ => libigl}/igl/writeBF.h (100%) rename src/{ => libigl}/igl/writeDMAT.cpp (100%) rename src/{ => libigl}/igl/writeDMAT.h (100%) rename src/{ => libigl}/igl/writeMESH.cpp (100%) rename src/{ => libigl}/igl/writeMESH.h (100%) rename src/{ => libigl}/igl/writeOBJ.cpp (100%) rename src/{ => libigl}/igl/writeOBJ.h (100%) rename src/{ => libigl}/igl/writeOFF.cpp (100%) rename src/{ => libigl}/igl/writeOFF.h (100%) rename src/{ => libigl}/igl/writePLY.cpp (100%) rename src/{ => libigl}/igl/writePLY.h (100%) rename src/{ => libigl}/igl/writeSTL.cpp (100%) rename src/{ => libigl}/igl/writeSTL.h (100%) rename src/{ => libigl}/igl/writeTGF.cpp (100%) rename src/{ => libigl}/igl/writeTGF.h (100%) rename src/{ => libigl}/igl/writeWRL.cpp (100%) rename src/{ => libigl}/igl/writeWRL.h (100%) rename src/{ => libigl}/igl/write_triangle_mesh.cpp (100%) rename src/{ => libigl}/igl/write_triangle_mesh.h (100%) rename src/{ => libigl}/igl/xml/ReAntTweakBarXMLSerialization.h (100%) rename src/{ => libigl}/igl/xml/XMLSerializable.h (100%) rename src/{ => libigl}/igl/xml/serialization_test.skip (100%) rename src/{ => libigl}/igl/xml/serialize_xml.cpp (100%) rename src/{ => libigl}/igl/xml/serialize_xml.h (100%) rename src/{ => libigl}/igl/xml/writeDAE.cpp (100%) rename src/{ => libigl}/igl/xml/writeDAE.h (100%) rename src/{ => libigl}/igl/xml/write_triangle_mesh.cpp (100%) rename src/{ => libigl}/igl/xml/write_triangle_mesh.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index b2fc12c480..ba264c8c36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,8 +89,6 @@ enable_testing () # Enable C++11 language standard. set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) if(NOT WIN32) # Add DEBUG flags to debug builds. @@ -172,7 +170,7 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STRE add_compile_options(-Werror=return-type) #removes LOTS of extraneous Eigen warnings - add_compile_options(-Wno-ignored-attributes) + # add_compile_options(-Wno-ignored-attributes) # Tamas: Eigen include dirs are marked as SYSTEM if (SLIC3R_ASAN) add_compile_options(-fsanitize=address -fno-omit-frame-pointer) @@ -314,7 +312,7 @@ if (NOT Eigen3_FOUND) set(Eigen3_FOUND 1) set(EIGEN3_INCLUDE_DIR ${LIBDIR}/eigen/) endif () -include_directories(${EIGEN3_INCLUDE_DIR}) +include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR}) # Find expat or use bundled version # Always use the system libexpat on Linux. diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index ca4e63fbc1..c98941b5af 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -35,6 +35,7 @@ set(DESTDIR "${CMAKE_CURRENT_BINARY_DIR}/destdir" CACHE PATH "Destination direct option(DEP_DEBUG "Build debug variants (only applicable on Windows)" ON) option(DEP_WX_STABLE "Build against wxWidgets stable 3.0 as opposed to default 3.1 (Linux only)" OFF) +option(DEP_BUILD_IGL_STATIC "Build IGL as a static library. Might cause link errors." OFF) message(STATUS "PrusaSlicer deps DESTDIR: ${DESTDIR}") message(STATUS "PrusaSlicer deps debug build: ${DEP_DEBUG}") @@ -99,6 +100,7 @@ else() dep_gtest dep_nlopt dep_qhull + dep_libigl ) endif() diff --git a/deps/deps-unix-common.cmake b/deps/deps-unix-common.cmake index d6a92efcb3..c44a6ec205 100644 --- a/deps/deps-unix-common.cmake +++ b/deps/deps-unix-common.cmake @@ -32,7 +32,6 @@ ExternalProject_Add(dep_nlopt -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local ${DEP_CMAKE_OPTS} ) - find_package(Git REQUIRED) ExternalProject_Add(dep_qhull @@ -45,3 +44,32 @@ ExternalProject_Add(dep_qhull ${DEP_CMAKE_OPTS} PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/qhull-mods.patch ) + +ExternalProject_Add(dep_libigl + EXCLUDE_FROM_ALL 1 + URL "https://github.com/libigl/libigl/archive/v2.0.0.tar.gz" + URL_HASH SHA256=42518e6b106c7209c73435fd260ed5d34edeb254852495b4c95dce2d95401328 + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + -DLIBIGL_BUILD_PYTHON=OFF + -DLIBIGL_BUILD_TESTS=OFF + -DLIBIGL_BUILD_TUTORIALS=OFF + -DLIBIGL_USE_STATIC_LIBRARY=${DEP_BUILD_IGL_STATIC} + -DLIBIGL_WITHOUT_COPYLEFT=OFF + -DLIBIGL_WITH_CGAL=OFF + -DLIBIGL_WITH_COMISO=OFF + -DLIBIGL_WITH_CORK=OFF + -DLIBIGL_WITH_EMBREE=OFF + -DLIBIGL_WITH_MATLAB=OFF + -DLIBIGL_WITH_MOSEK=OFF + -DLIBIGL_WITH_OPENGL=OFF + -DLIBIGL_WITH_OPENGL_GLFW=OFF + -DLIBIGL_WITH_OPENGL_GLFW_IMGUI=OFF + -DLIBIGL_WITH_PNG=OFF + -DLIBIGL_WITH_PYTHON=OFF + -DLIBIGL_WITH_TETGEN=OFF + -DLIBIGL_WITH_TRIANGLE=OFF + -DLIBIGL_WITH_XML=OFF + PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/igl-fixes.patch +) + diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index 041160f407..d7daf84253 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -252,6 +252,51 @@ else () set(DEP_WXWIDGETS_LIBDIR "vc_x64_lib") endif () +find_package(Git REQUIRED) + +ExternalProject_Add(dep_libigl + EXCLUDE_FROM_ALL 1 + URL "https://github.com/libigl/libigl/archive/v2.0.0.tar.gz" + URL_HASH SHA256=42518e6b106c7209c73435fd260ed5d34edeb254852495b4c95dce2d95401328 + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + -DLIBIGL_BUILD_PYTHON=OFF + -DLIBIGL_BUILD_TESTS=OFF + -DLIBIGL_BUILD_TUTORIALS=OFF + -DLIBIGL_USE_STATIC_LIBRARY=${DEP_BUILD_IGL_STATIC} + -DLIBIGL_WITHOUT_COPYLEFT=OFF + -DLIBIGL_WITH_CGAL=OFF + -DLIBIGL_WITH_COMISO=OFF + -DLIBIGL_WITH_CORK=OFF + -DLIBIGL_WITH_EMBREE=OFF + -DLIBIGL_WITH_MATLAB=OFF + -DLIBIGL_WITH_MOSEK=OFF + -DLIBIGL_WITH_OPENGL=OFF + -DLIBIGL_WITH_OPENGL_GLFW=OFF + -DLIBIGL_WITH_OPENGL_GLFW_IMGUI=OFF + -DLIBIGL_WITH_PNG=OFF + -DLIBIGL_WITH_PYTHON=OFF + -DLIBIGL_WITH_TETGEN=OFF + -DLIBIGL_WITH_TRIANGLE=OFF + -DLIBIGL_WITH_XML=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_DEBUG_POSTFIX=d + PATCH_COMMAND ${GIT_EXECUTABLE} apply --ignore-space-change --ignore-whitespace ${CMAKE_CURRENT_SOURCE_DIR}/igl-fixes.patch + BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) + +if (${DEP_DEBUG}) + ExternalProject_Get_Property(dep_libigl BINARY_DIR) + ExternalProject_Add_Step(dep_libigl build_debug + DEPENDEES build + DEPENDERS install + COMMAND msbuild /m /P:Configuration=Debug INSTALL.vcxproj + WORKING_DIRECTORY "${BINARY_DIR}" + ) +endif () + ExternalProject_Add(dep_wxwidgets EXCLUDE_FROM_ALL 1 GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets" diff --git a/deps/igl-fixes.patch b/deps/igl-fixes.patch new file mode 100644 index 0000000000..2b50e200b9 --- /dev/null +++ b/deps/igl-fixes.patch @@ -0,0 +1,87 @@ +diff --git a/cmake/libigl-config.cmake.in b/cmake/libigl-config.cmake.in +index 317c745c..f9808e1e 100644 +--- a/cmake/libigl-config.cmake.in ++++ b/cmake/libigl-config.cmake.in +@@ -2,28 +2,28 @@ + + include(${CMAKE_CURRENT_LIST_DIR}/libigl-export.cmake) + +-if (TARGET igl::core) +- if (NOT TARGET Eigen3::Eigen) +- find_package(Eigen3 QUIET) +- if (NOT Eigen3_FOUND) +- # try with PkgCOnfig +- find_package(PkgConfig REQUIRED) +- pkg_check_modules(Eigen3 QUIET IMPORTED_TARGET eigen3) +- endif() +- +- if (NOT Eigen3_FOUND) +- message(FATAL_ERROR "Could not find required dependency Eigen3") +- set(libigl_core_FOUND FALSE) +- else() +- target_link_libraries(igl::core INTERFACE PkgConfig::Eigen3) +- set(libigl_core_FOUND TRUE) +- endif() +- else() +- target_link_libraries(igl::core INTERFACE Eigen3::Eigen) +- set(libigl_core_FOUND TRUE) +- endif() +- +-endif() ++# if (TARGET igl::core) ++# if (NOT TARGET Eigen3::Eigen) ++# find_package(Eigen3 QUIET) ++# if (NOT Eigen3_FOUND) ++# # try with PkgCOnfig ++# find_package(PkgConfig REQUIRED) ++# pkg_check_modules(Eigen3 QUIET IMPORTED_TARGET eigen3) ++# endif() ++# ++# if (NOT Eigen3_FOUND) ++# message(FATAL_ERROR "Could not find required dependency Eigen3") ++# set(libigl_core_FOUND FALSE) ++# else() ++# target_link_libraries(igl::core INTERFACE PkgConfig::Eigen3) ++# set(libigl_core_FOUND TRUE) ++# endif() ++# else() ++# target_link_libraries(igl::core INTERFACE Eigen3::Eigen) ++# set(libigl_core_FOUND TRUE) ++# endif() ++# ++# endif() + + check_required_components(libigl) + +diff --git a/cmake/libigl.cmake b/cmake/libigl.cmake +index 4b11007a..47e6c395 100644 +--- a/cmake/libigl.cmake ++++ b/cmake/libigl.cmake +@@ -445,6 +445,7 @@ function(install_dir_files dir_name) + if(NOT LIBIGL_USE_STATIC_LIBRARY) + file(GLOB public_sources + ${CMAKE_CURRENT_SOURCE_DIR}/include/igl${subpath}/*.cpp ++ ${CMAKE_CURRENT_SOURCE_DIR}/include/igl${subpath}/*.c + ) + endif() + list(APPEND files_to_install ${public_sources}) +diff --git a/include/igl/AABB.cpp b/include/igl/AABB.cpp +index 09537335..31594314 100644 +--- a/include/igl/AABB.cpp ++++ b/include/igl/AABB.cpp +@@ -1072,4 +1072,5 @@ template void igl::AABB, 3>::init, 2>::init >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); + template double igl::AABB, 3>::squared_distance >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, double, int&, Eigen::PlainObjectBase >&) const; + template bool igl::AABB, 3>::intersect_ray >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, igl::Hit&) const; ++template bool igl::AABB, 3>::intersect_ray >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, std::vector&) const; + #endif +diff --git a/include/igl/ray_mesh_intersect.cpp b/include/igl/ray_mesh_intersect.cpp +index 9a70a22b..dda1654b 100644 +--- a/include/igl/ray_mesh_intersect.cpp ++++ b/include/igl/ray_mesh_intersect.cpp +@@ -83,4 +83,5 @@ IGL_INLINE bool igl::ray_mesh_intersect( + template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >&); + template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::Hit&); + template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Block const, 1, -1, false> >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase const, 1, -1, false> > const&, igl::Hit&); ++template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Block const, 1, -1, false> >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase const, 1, -1, false> > const&, std::vector >&); + #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7240634668..61faa05710 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,7 @@ add_subdirectory(poly2tri) add_subdirectory(qhull) add_subdirectory(Shiny) add_subdirectory(semver) +add_subdirectory(libigl) # Adding libnest2d project for bin packing... set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d") diff --git a/src/libigl/CMakeLists.txt b/src/libigl/CMakeLists.txt new file mode 100644 index 0000000000..0852fad729 --- /dev/null +++ b/src/libigl/CMakeLists.txt @@ -0,0 +1,14 @@ +project(libigl) +cmake_minimum_required(VERSION 3.0) + +add_library(libigl INTERFACE) + +find_package(libigl QUIET) + +if(libigl_FOUND) + message(STATUS "IGL found, using system version...") + target_link_libraries(libigl INTERFACE igl::core) +else() + message(STATUS "IGL NOT found, using bundled version...") + target_include_directories(libigl INTERFACE SYSTEM ${LIBDIR}/libigl) +endif() diff --git a/src/igl/AABB.cpp b/src/libigl/igl/AABB.cpp similarity index 100% rename from src/igl/AABB.cpp rename to src/libigl/igl/AABB.cpp diff --git a/src/igl/AABB.h b/src/libigl/igl/AABB.h similarity index 100% rename from src/igl/AABB.h rename to src/libigl/igl/AABB.h diff --git a/src/igl/ARAPEnergyType.h b/src/libigl/igl/ARAPEnergyType.h similarity index 100% rename from src/igl/ARAPEnergyType.h rename to src/libigl/igl/ARAPEnergyType.h diff --git a/src/igl/AtA_cached.cpp b/src/libigl/igl/AtA_cached.cpp similarity index 100% rename from src/igl/AtA_cached.cpp rename to src/libigl/igl/AtA_cached.cpp diff --git a/src/igl/AtA_cached.h b/src/libigl/igl/AtA_cached.h similarity index 100% rename from src/igl/AtA_cached.h rename to src/libigl/igl/AtA_cached.h diff --git a/src/igl/C_STR.h b/src/libigl/igl/C_STR.h similarity index 100% rename from src/igl/C_STR.h rename to src/libigl/igl/C_STR.h diff --git a/src/igl/Camera.h b/src/libigl/igl/Camera.h similarity index 100% rename from src/igl/Camera.h rename to src/libigl/igl/Camera.h diff --git a/src/igl/EPS.cpp b/src/libigl/igl/EPS.cpp similarity index 100% rename from src/igl/EPS.cpp rename to src/libigl/igl/EPS.cpp diff --git a/src/igl/EPS.h b/src/libigl/igl/EPS.h similarity index 100% rename from src/igl/EPS.h rename to src/libigl/igl/EPS.h diff --git a/src/igl/HalfEdgeIterator.cpp b/src/libigl/igl/HalfEdgeIterator.cpp similarity index 100% rename from src/igl/HalfEdgeIterator.cpp rename to src/libigl/igl/HalfEdgeIterator.cpp diff --git a/src/igl/HalfEdgeIterator.h b/src/libigl/igl/HalfEdgeIterator.h similarity index 100% rename from src/igl/HalfEdgeIterator.h rename to src/libigl/igl/HalfEdgeIterator.h diff --git a/src/igl/Hit.h b/src/libigl/igl/Hit.h similarity index 100% rename from src/igl/Hit.h rename to src/libigl/igl/Hit.h diff --git a/src/igl/IO b/src/libigl/igl/IO similarity index 100% rename from src/igl/IO rename to src/libigl/igl/IO diff --git a/src/igl/IndexComparison.h b/src/libigl/igl/IndexComparison.h similarity index 100% rename from src/igl/IndexComparison.h rename to src/libigl/igl/IndexComparison.h diff --git a/src/igl/LinSpaced.h b/src/libigl/igl/LinSpaced.h similarity index 100% rename from src/igl/LinSpaced.h rename to src/libigl/igl/LinSpaced.h diff --git a/src/igl/MeshBooleanType.h b/src/libigl/igl/MeshBooleanType.h similarity index 100% rename from src/igl/MeshBooleanType.h rename to src/libigl/igl/MeshBooleanType.h diff --git a/src/igl/NormalType.h b/src/libigl/igl/NormalType.h similarity index 100% rename from src/igl/NormalType.h rename to src/libigl/igl/NormalType.h diff --git a/src/igl/ONE.h b/src/libigl/igl/ONE.h similarity index 100% rename from src/igl/ONE.h rename to src/libigl/igl/ONE.h diff --git a/src/igl/PI.h b/src/libigl/igl/PI.h similarity index 100% rename from src/igl/PI.h rename to src/libigl/igl/PI.h diff --git a/src/igl/REDRUM.h b/src/libigl/igl/REDRUM.h similarity index 100% rename from src/igl/REDRUM.h rename to src/libigl/igl/REDRUM.h diff --git a/src/igl/STR.h b/src/libigl/igl/STR.h similarity index 100% rename from src/igl/STR.h rename to src/libigl/igl/STR.h diff --git a/src/igl/Singular_Value_Decomposition_Givens_QR_Factorization_Kernel.hpp b/src/libigl/igl/Singular_Value_Decomposition_Givens_QR_Factorization_Kernel.hpp similarity index 100% rename from src/igl/Singular_Value_Decomposition_Givens_QR_Factorization_Kernel.hpp rename to src/libigl/igl/Singular_Value_Decomposition_Givens_QR_Factorization_Kernel.hpp diff --git a/src/igl/Singular_Value_Decomposition_Jacobi_Conjugation_Kernel.hpp b/src/libigl/igl/Singular_Value_Decomposition_Jacobi_Conjugation_Kernel.hpp similarity index 100% rename from src/igl/Singular_Value_Decomposition_Jacobi_Conjugation_Kernel.hpp rename to src/libigl/igl/Singular_Value_Decomposition_Jacobi_Conjugation_Kernel.hpp diff --git a/src/igl/Singular_Value_Decomposition_Kernel_Declarations.hpp b/src/libigl/igl/Singular_Value_Decomposition_Kernel_Declarations.hpp similarity index 100% rename from src/igl/Singular_Value_Decomposition_Kernel_Declarations.hpp rename to src/libigl/igl/Singular_Value_Decomposition_Kernel_Declarations.hpp diff --git a/src/igl/Singular_Value_Decomposition_Main_Kernel_Body.hpp b/src/libigl/igl/Singular_Value_Decomposition_Main_Kernel_Body.hpp similarity index 100% rename from src/igl/Singular_Value_Decomposition_Main_Kernel_Body.hpp rename to src/libigl/igl/Singular_Value_Decomposition_Main_Kernel_Body.hpp diff --git a/src/igl/Singular_Value_Decomposition_Preamble.hpp b/src/libigl/igl/Singular_Value_Decomposition_Preamble.hpp similarity index 100% rename from src/igl/Singular_Value_Decomposition_Preamble.hpp rename to src/libigl/igl/Singular_Value_Decomposition_Preamble.hpp diff --git a/src/igl/SolverStatus.h b/src/libigl/igl/SolverStatus.h similarity index 100% rename from src/igl/SolverStatus.h rename to src/libigl/igl/SolverStatus.h diff --git a/src/igl/SortableRow.h b/src/libigl/igl/SortableRow.h similarity index 100% rename from src/igl/SortableRow.h rename to src/libigl/igl/SortableRow.h diff --git a/src/igl/Timer.h b/src/libigl/igl/Timer.h similarity index 100% rename from src/igl/Timer.h rename to src/libigl/igl/Timer.h diff --git a/src/igl/Viewport.h b/src/libigl/igl/Viewport.h similarity index 100% rename from src/igl/Viewport.h rename to src/libigl/igl/Viewport.h diff --git a/src/igl/WindingNumberAABB.h b/src/libigl/igl/WindingNumberAABB.h similarity index 100% rename from src/igl/WindingNumberAABB.h rename to src/libigl/igl/WindingNumberAABB.h diff --git a/src/igl/WindingNumberMethod.h b/src/libigl/igl/WindingNumberMethod.h similarity index 100% rename from src/igl/WindingNumberMethod.h rename to src/libigl/igl/WindingNumberMethod.h diff --git a/src/igl/WindingNumberTree.h b/src/libigl/igl/WindingNumberTree.h similarity index 100% rename from src/igl/WindingNumberTree.h rename to src/libigl/igl/WindingNumberTree.h diff --git a/src/igl/ZERO.h b/src/libigl/igl/ZERO.h similarity index 100% rename from src/igl/ZERO.h rename to src/libigl/igl/ZERO.h diff --git a/src/igl/active_set.cpp b/src/libigl/igl/active_set.cpp similarity index 100% rename from src/igl/active_set.cpp rename to src/libigl/igl/active_set.cpp diff --git a/src/igl/active_set.h b/src/libigl/igl/active_set.h similarity index 100% rename from src/igl/active_set.h rename to src/libigl/igl/active_set.h diff --git a/src/igl/adjacency_list.cpp b/src/libigl/igl/adjacency_list.cpp similarity index 100% rename from src/igl/adjacency_list.cpp rename to src/libigl/igl/adjacency_list.cpp diff --git a/src/igl/adjacency_list.h b/src/libigl/igl/adjacency_list.h similarity index 100% rename from src/igl/adjacency_list.h rename to src/libigl/igl/adjacency_list.h diff --git a/src/igl/adjacency_matrix.cpp b/src/libigl/igl/adjacency_matrix.cpp similarity index 100% rename from src/igl/adjacency_matrix.cpp rename to src/libigl/igl/adjacency_matrix.cpp diff --git a/src/igl/adjacency_matrix.h b/src/libigl/igl/adjacency_matrix.h similarity index 100% rename from src/igl/adjacency_matrix.h rename to src/libigl/igl/adjacency_matrix.h diff --git a/src/igl/all.cpp b/src/libigl/igl/all.cpp similarity index 100% rename from src/igl/all.cpp rename to src/libigl/igl/all.cpp diff --git a/src/igl/all.h b/src/libigl/igl/all.h similarity index 100% rename from src/igl/all.h rename to src/libigl/igl/all.h diff --git a/src/igl/all_edges.cpp b/src/libigl/igl/all_edges.cpp similarity index 100% rename from src/igl/all_edges.cpp rename to src/libigl/igl/all_edges.cpp diff --git a/src/igl/all_edges.h b/src/libigl/igl/all_edges.h similarity index 100% rename from src/igl/all_edges.h rename to src/libigl/igl/all_edges.h diff --git a/src/igl/all_pairs_distances.cpp b/src/libigl/igl/all_pairs_distances.cpp similarity index 100% rename from src/igl/all_pairs_distances.cpp rename to src/libigl/igl/all_pairs_distances.cpp diff --git a/src/igl/all_pairs_distances.h b/src/libigl/igl/all_pairs_distances.h similarity index 100% rename from src/igl/all_pairs_distances.h rename to src/libigl/igl/all_pairs_distances.h diff --git a/src/igl/ambient_occlusion.cpp b/src/libigl/igl/ambient_occlusion.cpp similarity index 100% rename from src/igl/ambient_occlusion.cpp rename to src/libigl/igl/ambient_occlusion.cpp diff --git a/src/igl/ambient_occlusion.h b/src/libigl/igl/ambient_occlusion.h similarity index 100% rename from src/igl/ambient_occlusion.h rename to src/libigl/igl/ambient_occlusion.h diff --git a/src/igl/angular_distance.cpp b/src/libigl/igl/angular_distance.cpp similarity index 100% rename from src/igl/angular_distance.cpp rename to src/libigl/igl/angular_distance.cpp diff --git a/src/igl/angular_distance.h b/src/libigl/igl/angular_distance.h similarity index 100% rename from src/igl/angular_distance.h rename to src/libigl/igl/angular_distance.h diff --git a/src/igl/anttweakbar/ReAntTweakBar.cpp b/src/libigl/igl/anttweakbar/ReAntTweakBar.cpp similarity index 100% rename from src/igl/anttweakbar/ReAntTweakBar.cpp rename to src/libigl/igl/anttweakbar/ReAntTweakBar.cpp diff --git a/src/igl/anttweakbar/ReAntTweakBar.h b/src/libigl/igl/anttweakbar/ReAntTweakBar.h similarity index 100% rename from src/igl/anttweakbar/ReAntTweakBar.h rename to src/libigl/igl/anttweakbar/ReAntTweakBar.h diff --git a/src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.cpp b/src/libigl/igl/anttweakbar/cocoa_key_to_anttweakbar_key.cpp similarity index 100% rename from src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.cpp rename to src/libigl/igl/anttweakbar/cocoa_key_to_anttweakbar_key.cpp diff --git a/src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.h b/src/libigl/igl/anttweakbar/cocoa_key_to_anttweakbar_key.h similarity index 100% rename from src/igl/anttweakbar/cocoa_key_to_anttweakbar_key.h rename to src/libigl/igl/anttweakbar/cocoa_key_to_anttweakbar_key.h diff --git a/src/igl/any.cpp b/src/libigl/igl/any.cpp similarity index 100% rename from src/igl/any.cpp rename to src/libigl/igl/any.cpp diff --git a/src/igl/any.h b/src/libigl/igl/any.h similarity index 100% rename from src/igl/any.h rename to src/libigl/igl/any.h diff --git a/src/igl/any_of.cpp b/src/libigl/igl/any_of.cpp similarity index 100% rename from src/igl/any_of.cpp rename to src/libigl/igl/any_of.cpp diff --git a/src/igl/any_of.h b/src/libigl/igl/any_of.h similarity index 100% rename from src/igl/any_of.h rename to src/libigl/igl/any_of.h diff --git a/src/igl/arap.cpp b/src/libigl/igl/arap.cpp similarity index 100% rename from src/igl/arap.cpp rename to src/libigl/igl/arap.cpp diff --git a/src/igl/arap.h b/src/libigl/igl/arap.h similarity index 100% rename from src/igl/arap.h rename to src/libigl/igl/arap.h diff --git a/src/igl/arap_dof.cpp b/src/libigl/igl/arap_dof.cpp similarity index 100% rename from src/igl/arap_dof.cpp rename to src/libigl/igl/arap_dof.cpp diff --git a/src/igl/arap_dof.h b/src/libigl/igl/arap_dof.h similarity index 100% rename from src/igl/arap_dof.h rename to src/libigl/igl/arap_dof.h diff --git a/src/igl/arap_linear_block.cpp b/src/libigl/igl/arap_linear_block.cpp similarity index 100% rename from src/igl/arap_linear_block.cpp rename to src/libigl/igl/arap_linear_block.cpp diff --git a/src/igl/arap_linear_block.h b/src/libigl/igl/arap_linear_block.h similarity index 100% rename from src/igl/arap_linear_block.h rename to src/libigl/igl/arap_linear_block.h diff --git a/src/igl/arap_rhs.cpp b/src/libigl/igl/arap_rhs.cpp similarity index 100% rename from src/igl/arap_rhs.cpp rename to src/libigl/igl/arap_rhs.cpp diff --git a/src/igl/arap_rhs.h b/src/libigl/igl/arap_rhs.h similarity index 100% rename from src/igl/arap_rhs.h rename to src/libigl/igl/arap_rhs.h diff --git a/src/igl/average_onto_faces.cpp b/src/libigl/igl/average_onto_faces.cpp similarity index 100% rename from src/igl/average_onto_faces.cpp rename to src/libigl/igl/average_onto_faces.cpp diff --git a/src/igl/average_onto_faces.h b/src/libigl/igl/average_onto_faces.h similarity index 100% rename from src/igl/average_onto_faces.h rename to src/libigl/igl/average_onto_faces.h diff --git a/src/igl/average_onto_vertices.cpp b/src/libigl/igl/average_onto_vertices.cpp similarity index 100% rename from src/igl/average_onto_vertices.cpp rename to src/libigl/igl/average_onto_vertices.cpp diff --git a/src/igl/average_onto_vertices.h b/src/libigl/igl/average_onto_vertices.h similarity index 100% rename from src/igl/average_onto_vertices.h rename to src/libigl/igl/average_onto_vertices.h diff --git a/src/igl/avg_edge_length.cpp b/src/libigl/igl/avg_edge_length.cpp similarity index 100% rename from src/igl/avg_edge_length.cpp rename to src/libigl/igl/avg_edge_length.cpp diff --git a/src/igl/avg_edge_length.h b/src/libigl/igl/avg_edge_length.h similarity index 100% rename from src/igl/avg_edge_length.h rename to src/libigl/igl/avg_edge_length.h diff --git a/src/igl/axis_angle_to_quat.cpp b/src/libigl/igl/axis_angle_to_quat.cpp similarity index 100% rename from src/igl/axis_angle_to_quat.cpp rename to src/libigl/igl/axis_angle_to_quat.cpp diff --git a/src/igl/axis_angle_to_quat.h b/src/libigl/igl/axis_angle_to_quat.h similarity index 100% rename from src/igl/axis_angle_to_quat.h rename to src/libigl/igl/axis_angle_to_quat.h diff --git a/src/igl/barycenter.cpp b/src/libigl/igl/barycenter.cpp similarity index 100% rename from src/igl/barycenter.cpp rename to src/libigl/igl/barycenter.cpp diff --git a/src/igl/barycenter.h b/src/libigl/igl/barycenter.h similarity index 100% rename from src/igl/barycenter.h rename to src/libigl/igl/barycenter.h diff --git a/src/igl/barycentric_coordinates.cpp b/src/libigl/igl/barycentric_coordinates.cpp similarity index 100% rename from src/igl/barycentric_coordinates.cpp rename to src/libigl/igl/barycentric_coordinates.cpp diff --git a/src/igl/barycentric_coordinates.h b/src/libigl/igl/barycentric_coordinates.h similarity index 100% rename from src/igl/barycentric_coordinates.h rename to src/libigl/igl/barycentric_coordinates.h diff --git a/src/igl/barycentric_to_global.cpp b/src/libigl/igl/barycentric_to_global.cpp similarity index 100% rename from src/igl/barycentric_to_global.cpp rename to src/libigl/igl/barycentric_to_global.cpp diff --git a/src/igl/barycentric_to_global.h b/src/libigl/igl/barycentric_to_global.h similarity index 100% rename from src/igl/barycentric_to_global.h rename to src/libigl/igl/barycentric_to_global.h diff --git a/src/igl/basename.cpp b/src/libigl/igl/basename.cpp similarity index 100% rename from src/igl/basename.cpp rename to src/libigl/igl/basename.cpp diff --git a/src/igl/basename.h b/src/libigl/igl/basename.h similarity index 100% rename from src/igl/basename.h rename to src/libigl/igl/basename.h diff --git a/src/igl/bbw.cpp b/src/libigl/igl/bbw.cpp similarity index 100% rename from src/igl/bbw.cpp rename to src/libigl/igl/bbw.cpp diff --git a/src/igl/bbw.h b/src/libigl/igl/bbw.h similarity index 100% rename from src/igl/bbw.h rename to src/libigl/igl/bbw.h diff --git a/src/igl/bfs.cpp b/src/libigl/igl/bfs.cpp similarity index 100% rename from src/igl/bfs.cpp rename to src/libigl/igl/bfs.cpp diff --git a/src/igl/bfs.h b/src/libigl/igl/bfs.h similarity index 100% rename from src/igl/bfs.h rename to src/libigl/igl/bfs.h diff --git a/src/igl/bfs_orient.cpp b/src/libigl/igl/bfs_orient.cpp similarity index 100% rename from src/igl/bfs_orient.cpp rename to src/libigl/igl/bfs_orient.cpp diff --git a/src/igl/bfs_orient.h b/src/libigl/igl/bfs_orient.h similarity index 100% rename from src/igl/bfs_orient.h rename to src/libigl/igl/bfs_orient.h diff --git a/src/igl/biharmonic_coordinates.cpp b/src/libigl/igl/biharmonic_coordinates.cpp similarity index 100% rename from src/igl/biharmonic_coordinates.cpp rename to src/libigl/igl/biharmonic_coordinates.cpp diff --git a/src/igl/biharmonic_coordinates.h b/src/libigl/igl/biharmonic_coordinates.h similarity index 100% rename from src/igl/biharmonic_coordinates.h rename to src/libigl/igl/biharmonic_coordinates.h diff --git a/src/igl/bijective_composite_harmonic_mapping.cpp b/src/libigl/igl/bijective_composite_harmonic_mapping.cpp similarity index 100% rename from src/igl/bijective_composite_harmonic_mapping.cpp rename to src/libigl/igl/bijective_composite_harmonic_mapping.cpp diff --git a/src/igl/bijective_composite_harmonic_mapping.h b/src/libigl/igl/bijective_composite_harmonic_mapping.h similarity index 100% rename from src/igl/bijective_composite_harmonic_mapping.h rename to src/libigl/igl/bijective_composite_harmonic_mapping.h diff --git a/src/igl/bone_parents.cpp b/src/libigl/igl/bone_parents.cpp similarity index 100% rename from src/igl/bone_parents.cpp rename to src/libigl/igl/bone_parents.cpp diff --git a/src/igl/bone_parents.h b/src/libigl/igl/bone_parents.h similarity index 100% rename from src/igl/bone_parents.h rename to src/libigl/igl/bone_parents.h diff --git a/src/igl/boundary_conditions.cpp b/src/libigl/igl/boundary_conditions.cpp similarity index 100% rename from src/igl/boundary_conditions.cpp rename to src/libigl/igl/boundary_conditions.cpp diff --git a/src/igl/boundary_conditions.h b/src/libigl/igl/boundary_conditions.h similarity index 100% rename from src/igl/boundary_conditions.h rename to src/libigl/igl/boundary_conditions.h diff --git a/src/igl/boundary_facets.cpp b/src/libigl/igl/boundary_facets.cpp similarity index 100% rename from src/igl/boundary_facets.cpp rename to src/libigl/igl/boundary_facets.cpp diff --git a/src/igl/boundary_facets.h b/src/libigl/igl/boundary_facets.h similarity index 100% rename from src/igl/boundary_facets.h rename to src/libigl/igl/boundary_facets.h diff --git a/src/igl/boundary_loop.cpp b/src/libigl/igl/boundary_loop.cpp similarity index 100% rename from src/igl/boundary_loop.cpp rename to src/libigl/igl/boundary_loop.cpp diff --git a/src/igl/boundary_loop.h b/src/libigl/igl/boundary_loop.h similarity index 100% rename from src/igl/boundary_loop.h rename to src/libigl/igl/boundary_loop.h diff --git a/src/igl/bounding_box.cpp b/src/libigl/igl/bounding_box.cpp similarity index 100% rename from src/igl/bounding_box.cpp rename to src/libigl/igl/bounding_box.cpp diff --git a/src/igl/bounding_box.h b/src/libigl/igl/bounding_box.h similarity index 100% rename from src/igl/bounding_box.h rename to src/libigl/igl/bounding_box.h diff --git a/src/igl/bounding_box_diagonal.cpp b/src/libigl/igl/bounding_box_diagonal.cpp similarity index 100% rename from src/igl/bounding_box_diagonal.cpp rename to src/libigl/igl/bounding_box_diagonal.cpp diff --git a/src/igl/bounding_box_diagonal.h b/src/libigl/igl/bounding_box_diagonal.h similarity index 100% rename from src/igl/bounding_box_diagonal.h rename to src/libigl/igl/bounding_box_diagonal.h diff --git a/src/igl/canonical_quaternions.cpp b/src/libigl/igl/canonical_quaternions.cpp similarity index 100% rename from src/igl/canonical_quaternions.cpp rename to src/libigl/igl/canonical_quaternions.cpp diff --git a/src/igl/canonical_quaternions.h b/src/libigl/igl/canonical_quaternions.h similarity index 100% rename from src/igl/canonical_quaternions.h rename to src/libigl/igl/canonical_quaternions.h diff --git a/src/igl/cat.cpp b/src/libigl/igl/cat.cpp similarity index 100% rename from src/igl/cat.cpp rename to src/libigl/igl/cat.cpp diff --git a/src/igl/cat.h b/src/libigl/igl/cat.h similarity index 100% rename from src/igl/cat.h rename to src/libigl/igl/cat.h diff --git a/src/igl/ceil.cpp b/src/libigl/igl/ceil.cpp similarity index 100% rename from src/igl/ceil.cpp rename to src/libigl/igl/ceil.cpp diff --git a/src/igl/ceil.h b/src/libigl/igl/ceil.h similarity index 100% rename from src/igl/ceil.h rename to src/libigl/igl/ceil.h diff --git a/src/igl/centroid.cpp b/src/libigl/igl/centroid.cpp similarity index 100% rename from src/igl/centroid.cpp rename to src/libigl/igl/centroid.cpp diff --git a/src/igl/centroid.h b/src/libigl/igl/centroid.h similarity index 100% rename from src/igl/centroid.h rename to src/libigl/igl/centroid.h diff --git a/src/igl/circulation.cpp b/src/libigl/igl/circulation.cpp similarity index 100% rename from src/igl/circulation.cpp rename to src/libigl/igl/circulation.cpp diff --git a/src/igl/circulation.h b/src/libigl/igl/circulation.h similarity index 100% rename from src/igl/circulation.h rename to src/libigl/igl/circulation.h diff --git a/src/igl/circumradius.cpp b/src/libigl/igl/circumradius.cpp similarity index 100% rename from src/igl/circumradius.cpp rename to src/libigl/igl/circumradius.cpp diff --git a/src/igl/circumradius.h b/src/libigl/igl/circumradius.h similarity index 100% rename from src/igl/circumradius.h rename to src/libigl/igl/circumradius.h diff --git a/src/igl/collapse_edge.cpp b/src/libigl/igl/collapse_edge.cpp similarity index 100% rename from src/igl/collapse_edge.cpp rename to src/libigl/igl/collapse_edge.cpp diff --git a/src/igl/collapse_edge.h b/src/libigl/igl/collapse_edge.h similarity index 100% rename from src/igl/collapse_edge.h rename to src/libigl/igl/collapse_edge.h diff --git a/src/igl/collapse_small_triangles.cpp b/src/libigl/igl/collapse_small_triangles.cpp similarity index 100% rename from src/igl/collapse_small_triangles.cpp rename to src/libigl/igl/collapse_small_triangles.cpp diff --git a/src/igl/collapse_small_triangles.h b/src/libigl/igl/collapse_small_triangles.h similarity index 100% rename from src/igl/collapse_small_triangles.h rename to src/libigl/igl/collapse_small_triangles.h diff --git a/src/igl/colon.cpp b/src/libigl/igl/colon.cpp similarity index 100% rename from src/igl/colon.cpp rename to src/libigl/igl/colon.cpp diff --git a/src/igl/colon.h b/src/libigl/igl/colon.h similarity index 100% rename from src/igl/colon.h rename to src/libigl/igl/colon.h diff --git a/src/igl/colormap.cpp b/src/libigl/igl/colormap.cpp similarity index 100% rename from src/igl/colormap.cpp rename to src/libigl/igl/colormap.cpp diff --git a/src/igl/colormap.h b/src/libigl/igl/colormap.h similarity index 100% rename from src/igl/colormap.h rename to src/libigl/igl/colormap.h diff --git a/src/igl/column_to_quats.cpp b/src/libigl/igl/column_to_quats.cpp similarity index 100% rename from src/igl/column_to_quats.cpp rename to src/libigl/igl/column_to_quats.cpp diff --git a/src/igl/column_to_quats.h b/src/libigl/igl/column_to_quats.h similarity index 100% rename from src/igl/column_to_quats.h rename to src/libigl/igl/column_to_quats.h diff --git a/src/igl/columnize.cpp b/src/libigl/igl/columnize.cpp similarity index 100% rename from src/igl/columnize.cpp rename to src/libigl/igl/columnize.cpp diff --git a/src/igl/columnize.h b/src/libigl/igl/columnize.h similarity index 100% rename from src/igl/columnize.h rename to src/libigl/igl/columnize.h diff --git a/src/igl/comb_cross_field.cpp b/src/libigl/igl/comb_cross_field.cpp similarity index 100% rename from src/igl/comb_cross_field.cpp rename to src/libigl/igl/comb_cross_field.cpp diff --git a/src/igl/comb_cross_field.h b/src/libigl/igl/comb_cross_field.h similarity index 100% rename from src/igl/comb_cross_field.h rename to src/libigl/igl/comb_cross_field.h diff --git a/src/igl/comb_frame_field.cpp b/src/libigl/igl/comb_frame_field.cpp similarity index 100% rename from src/igl/comb_frame_field.cpp rename to src/libigl/igl/comb_frame_field.cpp diff --git a/src/igl/comb_frame_field.h b/src/libigl/igl/comb_frame_field.h similarity index 100% rename from src/igl/comb_frame_field.h rename to src/libigl/igl/comb_frame_field.h diff --git a/src/igl/comb_line_field.cpp b/src/libigl/igl/comb_line_field.cpp similarity index 100% rename from src/igl/comb_line_field.cpp rename to src/libigl/igl/comb_line_field.cpp diff --git a/src/igl/comb_line_field.h b/src/libigl/igl/comb_line_field.h similarity index 100% rename from src/igl/comb_line_field.h rename to src/libigl/igl/comb_line_field.h diff --git a/src/igl/combine.cpp b/src/libigl/igl/combine.cpp similarity index 100% rename from src/igl/combine.cpp rename to src/libigl/igl/combine.cpp diff --git a/src/igl/combine.h b/src/libigl/igl/combine.h similarity index 100% rename from src/igl/combine.h rename to src/libigl/igl/combine.h diff --git a/src/igl/components.cpp b/src/libigl/igl/components.cpp similarity index 100% rename from src/igl/components.cpp rename to src/libigl/igl/components.cpp diff --git a/src/igl/components.h b/src/libigl/igl/components.h similarity index 100% rename from src/igl/components.h rename to src/libigl/igl/components.h diff --git a/src/igl/compute_frame_field_bisectors.cpp b/src/libigl/igl/compute_frame_field_bisectors.cpp similarity index 100% rename from src/igl/compute_frame_field_bisectors.cpp rename to src/libigl/igl/compute_frame_field_bisectors.cpp diff --git a/src/igl/compute_frame_field_bisectors.h b/src/libigl/igl/compute_frame_field_bisectors.h similarity index 100% rename from src/igl/compute_frame_field_bisectors.h rename to src/libigl/igl/compute_frame_field_bisectors.h diff --git a/src/igl/connect_boundary_to_infinity.cpp b/src/libigl/igl/connect_boundary_to_infinity.cpp similarity index 100% rename from src/igl/connect_boundary_to_infinity.cpp rename to src/libigl/igl/connect_boundary_to_infinity.cpp diff --git a/src/igl/connect_boundary_to_infinity.h b/src/libigl/igl/connect_boundary_to_infinity.h similarity index 100% rename from src/igl/connect_boundary_to_infinity.h rename to src/libigl/igl/connect_boundary_to_infinity.h diff --git a/src/igl/copyleft/README.md b/src/libigl/igl/copyleft/README.md similarity index 100% rename from src/igl/copyleft/README.md rename to src/libigl/igl/copyleft/README.md diff --git a/src/igl/copyleft/cgal/BinaryWindingNumberOperations.h b/src/libigl/igl/copyleft/cgal/BinaryWindingNumberOperations.h similarity index 100% rename from src/igl/copyleft/cgal/BinaryWindingNumberOperations.h rename to src/libigl/igl/copyleft/cgal/BinaryWindingNumberOperations.h diff --git a/src/igl/copyleft/cgal/CGAL_includes.hpp b/src/libigl/igl/copyleft/cgal/CGAL_includes.hpp similarity index 100% rename from src/igl/copyleft/cgal/CGAL_includes.hpp rename to src/libigl/igl/copyleft/cgal/CGAL_includes.hpp diff --git a/src/igl/copyleft/cgal/CSGTree.h b/src/libigl/igl/copyleft/cgal/CSGTree.h similarity index 100% rename from src/igl/copyleft/cgal/CSGTree.h rename to src/libigl/igl/copyleft/cgal/CSGTree.h diff --git a/src/igl/copyleft/cgal/RemeshSelfIntersectionsParam.h b/src/libigl/igl/copyleft/cgal/RemeshSelfIntersectionsParam.h similarity index 100% rename from src/igl/copyleft/cgal/RemeshSelfIntersectionsParam.h rename to src/libigl/igl/copyleft/cgal/RemeshSelfIntersectionsParam.h diff --git a/src/igl/copyleft/cgal/SelfIntersectMesh.h b/src/libigl/igl/copyleft/cgal/SelfIntersectMesh.h similarity index 100% rename from src/igl/copyleft/cgal/SelfIntersectMesh.h rename to src/libigl/igl/copyleft/cgal/SelfIntersectMesh.h diff --git a/src/igl/copyleft/cgal/assign.cpp b/src/libigl/igl/copyleft/cgal/assign.cpp similarity index 100% rename from src/igl/copyleft/cgal/assign.cpp rename to src/libigl/igl/copyleft/cgal/assign.cpp diff --git a/src/igl/copyleft/cgal/assign.h b/src/libigl/igl/copyleft/cgal/assign.h similarity index 100% rename from src/igl/copyleft/cgal/assign.h rename to src/libigl/igl/copyleft/cgal/assign.h diff --git a/src/igl/copyleft/cgal/assign_scalar.cpp b/src/libigl/igl/copyleft/cgal/assign_scalar.cpp similarity index 100% rename from src/igl/copyleft/cgal/assign_scalar.cpp rename to src/libigl/igl/copyleft/cgal/assign_scalar.cpp diff --git a/src/igl/copyleft/cgal/assign_scalar.h b/src/libigl/igl/copyleft/cgal/assign_scalar.h similarity index 100% rename from src/igl/copyleft/cgal/assign_scalar.h rename to src/libigl/igl/copyleft/cgal/assign_scalar.h diff --git a/src/igl/copyleft/cgal/barycenter.cpp b/src/libigl/igl/copyleft/cgal/barycenter.cpp similarity index 100% rename from src/igl/copyleft/cgal/barycenter.cpp rename to src/libigl/igl/copyleft/cgal/barycenter.cpp diff --git a/src/igl/copyleft/cgal/cell_adjacency.cpp b/src/libigl/igl/copyleft/cgal/cell_adjacency.cpp similarity index 100% rename from src/igl/copyleft/cgal/cell_adjacency.cpp rename to src/libigl/igl/copyleft/cgal/cell_adjacency.cpp diff --git a/src/igl/copyleft/cgal/cell_adjacency.h b/src/libigl/igl/copyleft/cgal/cell_adjacency.h similarity index 100% rename from src/igl/copyleft/cgal/cell_adjacency.h rename to src/libigl/igl/copyleft/cgal/cell_adjacency.h diff --git a/src/igl/copyleft/cgal/closest_facet.cpp b/src/libigl/igl/copyleft/cgal/closest_facet.cpp similarity index 100% rename from src/igl/copyleft/cgal/closest_facet.cpp rename to src/libigl/igl/copyleft/cgal/closest_facet.cpp diff --git a/src/igl/copyleft/cgal/closest_facet.h b/src/libigl/igl/copyleft/cgal/closest_facet.h similarity index 100% rename from src/igl/copyleft/cgal/closest_facet.h rename to src/libigl/igl/copyleft/cgal/closest_facet.h diff --git a/src/igl/copyleft/cgal/complex_to_mesh.cpp b/src/libigl/igl/copyleft/cgal/complex_to_mesh.cpp similarity index 100% rename from src/igl/copyleft/cgal/complex_to_mesh.cpp rename to src/libigl/igl/copyleft/cgal/complex_to_mesh.cpp diff --git a/src/igl/copyleft/cgal/complex_to_mesh.h b/src/libigl/igl/copyleft/cgal/complex_to_mesh.h similarity index 100% rename from src/igl/copyleft/cgal/complex_to_mesh.h rename to src/libigl/igl/copyleft/cgal/complex_to_mesh.h diff --git a/src/igl/copyleft/cgal/component_inside_component.cpp b/src/libigl/igl/copyleft/cgal/component_inside_component.cpp similarity index 100% rename from src/igl/copyleft/cgal/component_inside_component.cpp rename to src/libigl/igl/copyleft/cgal/component_inside_component.cpp diff --git a/src/igl/copyleft/cgal/component_inside_component.h b/src/libigl/igl/copyleft/cgal/component_inside_component.h similarity index 100% rename from src/igl/copyleft/cgal/component_inside_component.h rename to src/libigl/igl/copyleft/cgal/component_inside_component.h diff --git a/src/igl/copyleft/cgal/convex_hull.cpp b/src/libigl/igl/copyleft/cgal/convex_hull.cpp similarity index 100% rename from src/igl/copyleft/cgal/convex_hull.cpp rename to src/libigl/igl/copyleft/cgal/convex_hull.cpp diff --git a/src/igl/copyleft/cgal/convex_hull.h b/src/libigl/igl/copyleft/cgal/convex_hull.h similarity index 100% rename from src/igl/copyleft/cgal/convex_hull.h rename to src/libigl/igl/copyleft/cgal/convex_hull.h diff --git a/src/igl/copyleft/cgal/delaunay_triangulation.cpp b/src/libigl/igl/copyleft/cgal/delaunay_triangulation.cpp similarity index 100% rename from src/igl/copyleft/cgal/delaunay_triangulation.cpp rename to src/libigl/igl/copyleft/cgal/delaunay_triangulation.cpp diff --git a/src/igl/copyleft/cgal/delaunay_triangulation.h b/src/libigl/igl/copyleft/cgal/delaunay_triangulation.h similarity index 100% rename from src/igl/copyleft/cgal/delaunay_triangulation.h rename to src/libigl/igl/copyleft/cgal/delaunay_triangulation.h diff --git a/src/igl/copyleft/cgal/extract_cells.cpp b/src/libigl/igl/copyleft/cgal/extract_cells.cpp similarity index 100% rename from src/igl/copyleft/cgal/extract_cells.cpp rename to src/libigl/igl/copyleft/cgal/extract_cells.cpp diff --git a/src/igl/copyleft/cgal/extract_cells.h b/src/libigl/igl/copyleft/cgal/extract_cells.h similarity index 100% rename from src/igl/copyleft/cgal/extract_cells.h rename to src/libigl/igl/copyleft/cgal/extract_cells.h diff --git a/src/igl/copyleft/cgal/extract_feature.cpp b/src/libigl/igl/copyleft/cgal/extract_feature.cpp similarity index 100% rename from src/igl/copyleft/cgal/extract_feature.cpp rename to src/libigl/igl/copyleft/cgal/extract_feature.cpp diff --git a/src/igl/copyleft/cgal/extract_feature.h b/src/libigl/igl/copyleft/cgal/extract_feature.h similarity index 100% rename from src/igl/copyleft/cgal/extract_feature.h rename to src/libigl/igl/copyleft/cgal/extract_feature.h diff --git a/src/igl/copyleft/cgal/fast_winding_number.cpp b/src/libigl/igl/copyleft/cgal/fast_winding_number.cpp similarity index 100% rename from src/igl/copyleft/cgal/fast_winding_number.cpp rename to src/libigl/igl/copyleft/cgal/fast_winding_number.cpp diff --git a/src/igl/copyleft/cgal/fast_winding_number.h b/src/libigl/igl/copyleft/cgal/fast_winding_number.h similarity index 100% rename from src/igl/copyleft/cgal/fast_winding_number.h rename to src/libigl/igl/copyleft/cgal/fast_winding_number.h diff --git a/src/igl/copyleft/cgal/half_space_box.cpp b/src/libigl/igl/copyleft/cgal/half_space_box.cpp similarity index 100% rename from src/igl/copyleft/cgal/half_space_box.cpp rename to src/libigl/igl/copyleft/cgal/half_space_box.cpp diff --git a/src/igl/copyleft/cgal/half_space_box.h b/src/libigl/igl/copyleft/cgal/half_space_box.h similarity index 100% rename from src/igl/copyleft/cgal/half_space_box.h rename to src/libigl/igl/copyleft/cgal/half_space_box.h diff --git a/src/igl/copyleft/cgal/hausdorff.cpp b/src/libigl/igl/copyleft/cgal/hausdorff.cpp similarity index 100% rename from src/igl/copyleft/cgal/hausdorff.cpp rename to src/libigl/igl/copyleft/cgal/hausdorff.cpp diff --git a/src/igl/copyleft/cgal/hausdorff.h b/src/libigl/igl/copyleft/cgal/hausdorff.h similarity index 100% rename from src/igl/copyleft/cgal/hausdorff.h rename to src/libigl/igl/copyleft/cgal/hausdorff.h diff --git a/src/igl/copyleft/cgal/incircle.cpp b/src/libigl/igl/copyleft/cgal/incircle.cpp similarity index 100% rename from src/igl/copyleft/cgal/incircle.cpp rename to src/libigl/igl/copyleft/cgal/incircle.cpp diff --git a/src/igl/copyleft/cgal/incircle.h b/src/libigl/igl/copyleft/cgal/incircle.h similarity index 100% rename from src/igl/copyleft/cgal/incircle.h rename to src/libigl/igl/copyleft/cgal/incircle.h diff --git a/src/igl/copyleft/cgal/insert_into_cdt.cpp b/src/libigl/igl/copyleft/cgal/insert_into_cdt.cpp similarity index 100% rename from src/igl/copyleft/cgal/insert_into_cdt.cpp rename to src/libigl/igl/copyleft/cgal/insert_into_cdt.cpp diff --git a/src/igl/copyleft/cgal/insert_into_cdt.h b/src/libigl/igl/copyleft/cgal/insert_into_cdt.h similarity index 100% rename from src/igl/copyleft/cgal/insert_into_cdt.h rename to src/libigl/igl/copyleft/cgal/insert_into_cdt.h diff --git a/src/igl/copyleft/cgal/insphere.cpp b/src/libigl/igl/copyleft/cgal/insphere.cpp similarity index 100% rename from src/igl/copyleft/cgal/insphere.cpp rename to src/libigl/igl/copyleft/cgal/insphere.cpp diff --git a/src/igl/copyleft/cgal/insphere.h b/src/libigl/igl/copyleft/cgal/insphere.h similarity index 100% rename from src/igl/copyleft/cgal/insphere.h rename to src/libigl/igl/copyleft/cgal/insphere.h diff --git a/src/igl/copyleft/cgal/intersect_other.cpp b/src/libigl/igl/copyleft/cgal/intersect_other.cpp similarity index 100% rename from src/igl/copyleft/cgal/intersect_other.cpp rename to src/libigl/igl/copyleft/cgal/intersect_other.cpp diff --git a/src/igl/copyleft/cgal/intersect_other.h b/src/libigl/igl/copyleft/cgal/intersect_other.h similarity index 100% rename from src/igl/copyleft/cgal/intersect_other.h rename to src/libigl/igl/copyleft/cgal/intersect_other.h diff --git a/src/igl/copyleft/cgal/intersect_with_half_space.cpp b/src/libigl/igl/copyleft/cgal/intersect_with_half_space.cpp similarity index 100% rename from src/igl/copyleft/cgal/intersect_with_half_space.cpp rename to src/libigl/igl/copyleft/cgal/intersect_with_half_space.cpp diff --git a/src/igl/copyleft/cgal/intersect_with_half_space.h b/src/libigl/igl/copyleft/cgal/intersect_with_half_space.h similarity index 100% rename from src/igl/copyleft/cgal/intersect_with_half_space.h rename to src/libigl/igl/copyleft/cgal/intersect_with_half_space.h diff --git a/src/igl/copyleft/cgal/lexicographic_triangulation.cpp b/src/libigl/igl/copyleft/cgal/lexicographic_triangulation.cpp similarity index 100% rename from src/igl/copyleft/cgal/lexicographic_triangulation.cpp rename to src/libigl/igl/copyleft/cgal/lexicographic_triangulation.cpp diff --git a/src/igl/copyleft/cgal/lexicographic_triangulation.h b/src/libigl/igl/copyleft/cgal/lexicographic_triangulation.h similarity index 100% rename from src/igl/copyleft/cgal/lexicographic_triangulation.h rename to src/libigl/igl/copyleft/cgal/lexicographic_triangulation.h diff --git a/src/igl/copyleft/cgal/list_to_matrix.cpp b/src/libigl/igl/copyleft/cgal/list_to_matrix.cpp similarity index 100% rename from src/igl/copyleft/cgal/list_to_matrix.cpp rename to src/libigl/igl/copyleft/cgal/list_to_matrix.cpp diff --git a/src/igl/copyleft/cgal/mesh_boolean.cpp b/src/libigl/igl/copyleft/cgal/mesh_boolean.cpp similarity index 100% rename from src/igl/copyleft/cgal/mesh_boolean.cpp rename to src/libigl/igl/copyleft/cgal/mesh_boolean.cpp diff --git a/src/igl/copyleft/cgal/mesh_boolean.h b/src/libigl/igl/copyleft/cgal/mesh_boolean.h similarity index 100% rename from src/igl/copyleft/cgal/mesh_boolean.h rename to src/libigl/igl/copyleft/cgal/mesh_boolean.h diff --git a/src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.cpp b/src/libigl/igl/copyleft/cgal/mesh_boolean_type_to_funcs.cpp similarity index 100% rename from src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.cpp rename to src/libigl/igl/copyleft/cgal/mesh_boolean_type_to_funcs.cpp diff --git a/src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.h b/src/libigl/igl/copyleft/cgal/mesh_boolean_type_to_funcs.h similarity index 100% rename from src/igl/copyleft/cgal/mesh_boolean_type_to_funcs.h rename to src/libigl/igl/copyleft/cgal/mesh_boolean_type_to_funcs.h diff --git a/src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.cpp b/src/libigl/igl/copyleft/cgal/mesh_to_cgal_triangle_list.cpp similarity index 100% rename from src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.cpp rename to src/libigl/igl/copyleft/cgal/mesh_to_cgal_triangle_list.cpp diff --git a/src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.h b/src/libigl/igl/copyleft/cgal/mesh_to_cgal_triangle_list.h similarity index 100% rename from src/igl/copyleft/cgal/mesh_to_cgal_triangle_list.h rename to src/libigl/igl/copyleft/cgal/mesh_to_cgal_triangle_list.h diff --git a/src/igl/copyleft/cgal/mesh_to_polyhedron.cpp b/src/libigl/igl/copyleft/cgal/mesh_to_polyhedron.cpp similarity index 100% rename from src/igl/copyleft/cgal/mesh_to_polyhedron.cpp rename to src/libigl/igl/copyleft/cgal/mesh_to_polyhedron.cpp diff --git a/src/igl/copyleft/cgal/mesh_to_polyhedron.h b/src/libigl/igl/copyleft/cgal/mesh_to_polyhedron.h similarity index 100% rename from src/igl/copyleft/cgal/mesh_to_polyhedron.h rename to src/libigl/igl/copyleft/cgal/mesh_to_polyhedron.h diff --git a/src/igl/copyleft/cgal/minkowski_sum.cpp b/src/libigl/igl/copyleft/cgal/minkowski_sum.cpp similarity index 100% rename from src/igl/copyleft/cgal/minkowski_sum.cpp rename to src/libigl/igl/copyleft/cgal/minkowski_sum.cpp diff --git a/src/igl/copyleft/cgal/minkowski_sum.h b/src/libigl/igl/copyleft/cgal/minkowski_sum.h similarity index 100% rename from src/igl/copyleft/cgal/minkowski_sum.h rename to src/libigl/igl/copyleft/cgal/minkowski_sum.h diff --git a/src/igl/copyleft/cgal/order_facets_around_edge.cpp b/src/libigl/igl/copyleft/cgal/order_facets_around_edge.cpp similarity index 100% rename from src/igl/copyleft/cgal/order_facets_around_edge.cpp rename to src/libigl/igl/copyleft/cgal/order_facets_around_edge.cpp diff --git a/src/igl/copyleft/cgal/order_facets_around_edge.h b/src/libigl/igl/copyleft/cgal/order_facets_around_edge.h similarity index 100% rename from src/igl/copyleft/cgal/order_facets_around_edge.h rename to src/libigl/igl/copyleft/cgal/order_facets_around_edge.h diff --git a/src/igl/copyleft/cgal/order_facets_around_edges.cpp b/src/libigl/igl/copyleft/cgal/order_facets_around_edges.cpp similarity index 100% rename from src/igl/copyleft/cgal/order_facets_around_edges.cpp rename to src/libigl/igl/copyleft/cgal/order_facets_around_edges.cpp diff --git a/src/igl/copyleft/cgal/order_facets_around_edges.h b/src/libigl/igl/copyleft/cgal/order_facets_around_edges.h similarity index 100% rename from src/igl/copyleft/cgal/order_facets_around_edges.h rename to src/libigl/igl/copyleft/cgal/order_facets_around_edges.h diff --git a/src/igl/copyleft/cgal/orient2D.cpp b/src/libigl/igl/copyleft/cgal/orient2D.cpp similarity index 100% rename from src/igl/copyleft/cgal/orient2D.cpp rename to src/libigl/igl/copyleft/cgal/orient2D.cpp diff --git a/src/igl/copyleft/cgal/orient2D.h b/src/libigl/igl/copyleft/cgal/orient2D.h similarity index 100% rename from src/igl/copyleft/cgal/orient2D.h rename to src/libigl/igl/copyleft/cgal/orient2D.h diff --git a/src/igl/copyleft/cgal/orient3D.cpp b/src/libigl/igl/copyleft/cgal/orient3D.cpp similarity index 100% rename from src/igl/copyleft/cgal/orient3D.cpp rename to src/libigl/igl/copyleft/cgal/orient3D.cpp diff --git a/src/igl/copyleft/cgal/orient3D.h b/src/libigl/igl/copyleft/cgal/orient3D.h similarity index 100% rename from src/igl/copyleft/cgal/orient3D.h rename to src/libigl/igl/copyleft/cgal/orient3D.h diff --git a/src/igl/copyleft/cgal/outer_element.cpp b/src/libigl/igl/copyleft/cgal/outer_element.cpp similarity index 100% rename from src/igl/copyleft/cgal/outer_element.cpp rename to src/libigl/igl/copyleft/cgal/outer_element.cpp diff --git a/src/igl/copyleft/cgal/outer_element.h b/src/libigl/igl/copyleft/cgal/outer_element.h similarity index 100% rename from src/igl/copyleft/cgal/outer_element.h rename to src/libigl/igl/copyleft/cgal/outer_element.h diff --git a/src/igl/copyleft/cgal/outer_facet.cpp b/src/libigl/igl/copyleft/cgal/outer_facet.cpp similarity index 100% rename from src/igl/copyleft/cgal/outer_facet.cpp rename to src/libigl/igl/copyleft/cgal/outer_facet.cpp diff --git a/src/igl/copyleft/cgal/outer_facet.h b/src/libigl/igl/copyleft/cgal/outer_facet.h similarity index 100% rename from src/igl/copyleft/cgal/outer_facet.h rename to src/libigl/igl/copyleft/cgal/outer_facet.h diff --git a/src/igl/copyleft/cgal/outer_hull.cpp b/src/libigl/igl/copyleft/cgal/outer_hull.cpp similarity index 100% rename from src/igl/copyleft/cgal/outer_hull.cpp rename to src/libigl/igl/copyleft/cgal/outer_hull.cpp diff --git a/src/igl/copyleft/cgal/outer_hull.h b/src/libigl/igl/copyleft/cgal/outer_hull.h similarity index 100% rename from src/igl/copyleft/cgal/outer_hull.h rename to src/libigl/igl/copyleft/cgal/outer_hull.h diff --git a/src/igl/copyleft/cgal/peel_outer_hull_layers.cpp b/src/libigl/igl/copyleft/cgal/peel_outer_hull_layers.cpp similarity index 100% rename from src/igl/copyleft/cgal/peel_outer_hull_layers.cpp rename to src/libigl/igl/copyleft/cgal/peel_outer_hull_layers.cpp diff --git a/src/igl/copyleft/cgal/peel_outer_hull_layers.h b/src/libigl/igl/copyleft/cgal/peel_outer_hull_layers.h similarity index 100% rename from src/igl/copyleft/cgal/peel_outer_hull_layers.h rename to src/libigl/igl/copyleft/cgal/peel_outer_hull_layers.h diff --git a/src/igl/copyleft/cgal/peel_winding_number_layers.cpp b/src/libigl/igl/copyleft/cgal/peel_winding_number_layers.cpp similarity index 100% rename from src/igl/copyleft/cgal/peel_winding_number_layers.cpp rename to src/libigl/igl/copyleft/cgal/peel_winding_number_layers.cpp diff --git a/src/igl/copyleft/cgal/peel_winding_number_layers.h b/src/libigl/igl/copyleft/cgal/peel_winding_number_layers.h similarity index 100% rename from src/igl/copyleft/cgal/peel_winding_number_layers.h rename to src/libigl/igl/copyleft/cgal/peel_winding_number_layers.h diff --git a/src/igl/copyleft/cgal/piecewise_constant_winding_number.cpp b/src/libigl/igl/copyleft/cgal/piecewise_constant_winding_number.cpp similarity index 100% rename from src/igl/copyleft/cgal/piecewise_constant_winding_number.cpp rename to src/libigl/igl/copyleft/cgal/piecewise_constant_winding_number.cpp diff --git a/src/igl/copyleft/cgal/piecewise_constant_winding_number.h b/src/libigl/igl/copyleft/cgal/piecewise_constant_winding_number.h similarity index 100% rename from src/igl/copyleft/cgal/piecewise_constant_winding_number.h rename to src/libigl/igl/copyleft/cgal/piecewise_constant_winding_number.h diff --git a/src/igl/copyleft/cgal/point_areas.cpp b/src/libigl/igl/copyleft/cgal/point_areas.cpp similarity index 100% rename from src/igl/copyleft/cgal/point_areas.cpp rename to src/libigl/igl/copyleft/cgal/point_areas.cpp diff --git a/src/igl/copyleft/cgal/point_areas.h b/src/libigl/igl/copyleft/cgal/point_areas.h similarity index 100% rename from src/igl/copyleft/cgal/point_areas.h rename to src/libigl/igl/copyleft/cgal/point_areas.h diff --git a/src/igl/copyleft/cgal/point_mesh_squared_distance.cpp b/src/libigl/igl/copyleft/cgal/point_mesh_squared_distance.cpp similarity index 100% rename from src/igl/copyleft/cgal/point_mesh_squared_distance.cpp rename to src/libigl/igl/copyleft/cgal/point_mesh_squared_distance.cpp diff --git a/src/igl/copyleft/cgal/point_mesh_squared_distance.h b/src/libigl/igl/copyleft/cgal/point_mesh_squared_distance.h similarity index 100% rename from src/igl/copyleft/cgal/point_mesh_squared_distance.h rename to src/libigl/igl/copyleft/cgal/point_mesh_squared_distance.h diff --git a/src/igl/copyleft/cgal/point_segment_squared_distance.cpp b/src/libigl/igl/copyleft/cgal/point_segment_squared_distance.cpp similarity index 100% rename from src/igl/copyleft/cgal/point_segment_squared_distance.cpp rename to src/libigl/igl/copyleft/cgal/point_segment_squared_distance.cpp diff --git a/src/igl/copyleft/cgal/point_segment_squared_distance.h b/src/libigl/igl/copyleft/cgal/point_segment_squared_distance.h similarity index 100% rename from src/igl/copyleft/cgal/point_segment_squared_distance.h rename to src/libigl/igl/copyleft/cgal/point_segment_squared_distance.h diff --git a/src/igl/copyleft/cgal/point_solid_signed_squared_distance.cpp b/src/libigl/igl/copyleft/cgal/point_solid_signed_squared_distance.cpp similarity index 100% rename from src/igl/copyleft/cgal/point_solid_signed_squared_distance.cpp rename to src/libigl/igl/copyleft/cgal/point_solid_signed_squared_distance.cpp diff --git a/src/igl/copyleft/cgal/point_solid_signed_squared_distance.h b/src/libigl/igl/copyleft/cgal/point_solid_signed_squared_distance.h similarity index 100% rename from src/igl/copyleft/cgal/point_solid_signed_squared_distance.h rename to src/libigl/igl/copyleft/cgal/point_solid_signed_squared_distance.h diff --git a/src/igl/copyleft/cgal/point_triangle_squared_distance.cpp b/src/libigl/igl/copyleft/cgal/point_triangle_squared_distance.cpp similarity index 100% rename from src/igl/copyleft/cgal/point_triangle_squared_distance.cpp rename to src/libigl/igl/copyleft/cgal/point_triangle_squared_distance.cpp diff --git a/src/igl/copyleft/cgal/point_triangle_squared_distance.h b/src/libigl/igl/copyleft/cgal/point_triangle_squared_distance.h similarity index 100% rename from src/igl/copyleft/cgal/point_triangle_squared_distance.h rename to src/libigl/igl/copyleft/cgal/point_triangle_squared_distance.h diff --git a/src/igl/copyleft/cgal/points_inside_component.cpp b/src/libigl/igl/copyleft/cgal/points_inside_component.cpp similarity index 100% rename from src/igl/copyleft/cgal/points_inside_component.cpp rename to src/libigl/igl/copyleft/cgal/points_inside_component.cpp diff --git a/src/igl/copyleft/cgal/points_inside_component.h b/src/libigl/igl/copyleft/cgal/points_inside_component.h similarity index 100% rename from src/igl/copyleft/cgal/points_inside_component.h rename to src/libigl/igl/copyleft/cgal/points_inside_component.h diff --git a/src/igl/copyleft/cgal/polyhedron_to_mesh.cpp b/src/libigl/igl/copyleft/cgal/polyhedron_to_mesh.cpp similarity index 100% rename from src/igl/copyleft/cgal/polyhedron_to_mesh.cpp rename to src/libigl/igl/copyleft/cgal/polyhedron_to_mesh.cpp diff --git a/src/igl/copyleft/cgal/polyhedron_to_mesh.h b/src/libigl/igl/copyleft/cgal/polyhedron_to_mesh.h similarity index 100% rename from src/igl/copyleft/cgal/polyhedron_to_mesh.h rename to src/libigl/igl/copyleft/cgal/polyhedron_to_mesh.h diff --git a/src/igl/copyleft/cgal/projected_cdt.cpp b/src/libigl/igl/copyleft/cgal/projected_cdt.cpp similarity index 100% rename from src/igl/copyleft/cgal/projected_cdt.cpp rename to src/libigl/igl/copyleft/cgal/projected_cdt.cpp diff --git a/src/igl/copyleft/cgal/projected_cdt.h b/src/libigl/igl/copyleft/cgal/projected_cdt.h similarity index 100% rename from src/igl/copyleft/cgal/projected_cdt.h rename to src/libigl/igl/copyleft/cgal/projected_cdt.h diff --git a/src/igl/copyleft/cgal/projected_delaunay.cpp b/src/libigl/igl/copyleft/cgal/projected_delaunay.cpp similarity index 100% rename from src/igl/copyleft/cgal/projected_delaunay.cpp rename to src/libigl/igl/copyleft/cgal/projected_delaunay.cpp diff --git a/src/igl/copyleft/cgal/projected_delaunay.h b/src/libigl/igl/copyleft/cgal/projected_delaunay.h similarity index 100% rename from src/igl/copyleft/cgal/projected_delaunay.h rename to src/libigl/igl/copyleft/cgal/projected_delaunay.h diff --git a/src/igl/copyleft/cgal/propagate_winding_numbers.cpp b/src/libigl/igl/copyleft/cgal/propagate_winding_numbers.cpp similarity index 100% rename from src/igl/copyleft/cgal/propagate_winding_numbers.cpp rename to src/libigl/igl/copyleft/cgal/propagate_winding_numbers.cpp diff --git a/src/igl/copyleft/cgal/propagate_winding_numbers.h b/src/libigl/igl/copyleft/cgal/propagate_winding_numbers.h similarity index 100% rename from src/igl/copyleft/cgal/propagate_winding_numbers.h rename to src/libigl/igl/copyleft/cgal/propagate_winding_numbers.h diff --git a/src/igl/copyleft/cgal/read_triangle_mesh.cpp b/src/libigl/igl/copyleft/cgal/read_triangle_mesh.cpp similarity index 100% rename from src/igl/copyleft/cgal/read_triangle_mesh.cpp rename to src/libigl/igl/copyleft/cgal/read_triangle_mesh.cpp diff --git a/src/igl/copyleft/cgal/read_triangle_mesh.h b/src/libigl/igl/copyleft/cgal/read_triangle_mesh.h similarity index 100% rename from src/igl/copyleft/cgal/read_triangle_mesh.h rename to src/libigl/igl/copyleft/cgal/read_triangle_mesh.h diff --git a/src/igl/copyleft/cgal/relabel_small_immersed_cells.cpp b/src/libigl/igl/copyleft/cgal/relabel_small_immersed_cells.cpp similarity index 100% rename from src/igl/copyleft/cgal/relabel_small_immersed_cells.cpp rename to src/libigl/igl/copyleft/cgal/relabel_small_immersed_cells.cpp diff --git a/src/igl/copyleft/cgal/relabel_small_immersed_cells.h b/src/libigl/igl/copyleft/cgal/relabel_small_immersed_cells.h similarity index 100% rename from src/igl/copyleft/cgal/relabel_small_immersed_cells.h rename to src/libigl/igl/copyleft/cgal/relabel_small_immersed_cells.h diff --git a/src/igl/copyleft/cgal/remesh_intersections.cpp b/src/libigl/igl/copyleft/cgal/remesh_intersections.cpp similarity index 100% rename from src/igl/copyleft/cgal/remesh_intersections.cpp rename to src/libigl/igl/copyleft/cgal/remesh_intersections.cpp diff --git a/src/igl/copyleft/cgal/remesh_intersections.h b/src/libigl/igl/copyleft/cgal/remesh_intersections.h similarity index 100% rename from src/igl/copyleft/cgal/remesh_intersections.h rename to src/libigl/igl/copyleft/cgal/remesh_intersections.h diff --git a/src/igl/copyleft/cgal/remesh_self_intersections.cpp b/src/libigl/igl/copyleft/cgal/remesh_self_intersections.cpp similarity index 100% rename from src/igl/copyleft/cgal/remesh_self_intersections.cpp rename to src/libigl/igl/copyleft/cgal/remesh_self_intersections.cpp diff --git a/src/igl/copyleft/cgal/remesh_self_intersections.h b/src/libigl/igl/copyleft/cgal/remesh_self_intersections.h similarity index 100% rename from src/igl/copyleft/cgal/remesh_self_intersections.h rename to src/libigl/igl/copyleft/cgal/remesh_self_intersections.h diff --git a/src/igl/copyleft/cgal/remove_unreferenced.cpp b/src/libigl/igl/copyleft/cgal/remove_unreferenced.cpp similarity index 100% rename from src/igl/copyleft/cgal/remove_unreferenced.cpp rename to src/libigl/igl/copyleft/cgal/remove_unreferenced.cpp diff --git a/src/igl/copyleft/cgal/resolve_intersections.cpp b/src/libigl/igl/copyleft/cgal/resolve_intersections.cpp similarity index 100% rename from src/igl/copyleft/cgal/resolve_intersections.cpp rename to src/libigl/igl/copyleft/cgal/resolve_intersections.cpp diff --git a/src/igl/copyleft/cgal/resolve_intersections.h b/src/libigl/igl/copyleft/cgal/resolve_intersections.h similarity index 100% rename from src/igl/copyleft/cgal/resolve_intersections.h rename to src/libigl/igl/copyleft/cgal/resolve_intersections.h diff --git a/src/igl/copyleft/cgal/row_to_point.cpp b/src/libigl/igl/copyleft/cgal/row_to_point.cpp similarity index 100% rename from src/igl/copyleft/cgal/row_to_point.cpp rename to src/libigl/igl/copyleft/cgal/row_to_point.cpp diff --git a/src/igl/copyleft/cgal/row_to_point.h b/src/libigl/igl/copyleft/cgal/row_to_point.h similarity index 100% rename from src/igl/copyleft/cgal/row_to_point.h rename to src/libigl/igl/copyleft/cgal/row_to_point.h diff --git a/src/igl/copyleft/cgal/segment_segment_squared_distance.cpp b/src/libigl/igl/copyleft/cgal/segment_segment_squared_distance.cpp similarity index 100% rename from src/igl/copyleft/cgal/segment_segment_squared_distance.cpp rename to src/libigl/igl/copyleft/cgal/segment_segment_squared_distance.cpp diff --git a/src/igl/copyleft/cgal/segment_segment_squared_distance.h b/src/libigl/igl/copyleft/cgal/segment_segment_squared_distance.h similarity index 100% rename from src/igl/copyleft/cgal/segment_segment_squared_distance.h rename to src/libigl/igl/copyleft/cgal/segment_segment_squared_distance.h diff --git a/src/igl/copyleft/cgal/signed_distance_isosurface.cpp b/src/libigl/igl/copyleft/cgal/signed_distance_isosurface.cpp similarity index 100% rename from src/igl/copyleft/cgal/signed_distance_isosurface.cpp rename to src/libigl/igl/copyleft/cgal/signed_distance_isosurface.cpp diff --git a/src/igl/copyleft/cgal/signed_distance_isosurface.h b/src/libigl/igl/copyleft/cgal/signed_distance_isosurface.h similarity index 100% rename from src/igl/copyleft/cgal/signed_distance_isosurface.h rename to src/libigl/igl/copyleft/cgal/signed_distance_isosurface.h diff --git a/src/igl/copyleft/cgal/slice.cpp b/src/libigl/igl/copyleft/cgal/slice.cpp similarity index 100% rename from src/igl/copyleft/cgal/slice.cpp rename to src/libigl/igl/copyleft/cgal/slice.cpp diff --git a/src/igl/copyleft/cgal/slice_mask.cpp b/src/libigl/igl/copyleft/cgal/slice_mask.cpp similarity index 100% rename from src/igl/copyleft/cgal/slice_mask.cpp rename to src/libigl/igl/copyleft/cgal/slice_mask.cpp diff --git a/src/igl/copyleft/cgal/snap_rounding.cpp b/src/libigl/igl/copyleft/cgal/snap_rounding.cpp similarity index 100% rename from src/igl/copyleft/cgal/snap_rounding.cpp rename to src/libigl/igl/copyleft/cgal/snap_rounding.cpp diff --git a/src/igl/copyleft/cgal/snap_rounding.h b/src/libigl/igl/copyleft/cgal/snap_rounding.h similarity index 100% rename from src/igl/copyleft/cgal/snap_rounding.h rename to src/libigl/igl/copyleft/cgal/snap_rounding.h diff --git a/src/igl/copyleft/cgal/string_to_mesh_boolean_type.cpp b/src/libigl/igl/copyleft/cgal/string_to_mesh_boolean_type.cpp similarity index 100% rename from src/igl/copyleft/cgal/string_to_mesh_boolean_type.cpp rename to src/libigl/igl/copyleft/cgal/string_to_mesh_boolean_type.cpp diff --git a/src/igl/copyleft/cgal/string_to_mesh_boolean_type.h b/src/libigl/igl/copyleft/cgal/string_to_mesh_boolean_type.h similarity index 100% rename from src/igl/copyleft/cgal/string_to_mesh_boolean_type.h rename to src/libigl/igl/copyleft/cgal/string_to_mesh_boolean_type.h diff --git a/src/igl/copyleft/cgal/subdivide_segments.cpp b/src/libigl/igl/copyleft/cgal/subdivide_segments.cpp similarity index 100% rename from src/igl/copyleft/cgal/subdivide_segments.cpp rename to src/libigl/igl/copyleft/cgal/subdivide_segments.cpp diff --git a/src/igl/copyleft/cgal/subdivide_segments.h b/src/libigl/igl/copyleft/cgal/subdivide_segments.h similarity index 100% rename from src/igl/copyleft/cgal/subdivide_segments.h rename to src/libigl/igl/copyleft/cgal/subdivide_segments.h diff --git a/src/igl/copyleft/cgal/submesh_aabb_tree.cpp b/src/libigl/igl/copyleft/cgal/submesh_aabb_tree.cpp similarity index 100% rename from src/igl/copyleft/cgal/submesh_aabb_tree.cpp rename to src/libigl/igl/copyleft/cgal/submesh_aabb_tree.cpp diff --git a/src/igl/copyleft/cgal/submesh_aabb_tree.h b/src/libigl/igl/copyleft/cgal/submesh_aabb_tree.h similarity index 100% rename from src/igl/copyleft/cgal/submesh_aabb_tree.h rename to src/libigl/igl/copyleft/cgal/submesh_aabb_tree.h diff --git a/src/igl/copyleft/cgal/triangle_triangle_squared_distance.cpp b/src/libigl/igl/copyleft/cgal/triangle_triangle_squared_distance.cpp similarity index 100% rename from src/igl/copyleft/cgal/triangle_triangle_squared_distance.cpp rename to src/libigl/igl/copyleft/cgal/triangle_triangle_squared_distance.cpp diff --git a/src/igl/copyleft/cgal/triangle_triangle_squared_distance.h b/src/libigl/igl/copyleft/cgal/triangle_triangle_squared_distance.h similarity index 100% rename from src/igl/copyleft/cgal/triangle_triangle_squared_distance.h rename to src/libigl/igl/copyleft/cgal/triangle_triangle_squared_distance.h diff --git a/src/igl/copyleft/cgal/trim_with_solid.cpp b/src/libigl/igl/copyleft/cgal/trim_with_solid.cpp similarity index 100% rename from src/igl/copyleft/cgal/trim_with_solid.cpp rename to src/libigl/igl/copyleft/cgal/trim_with_solid.cpp diff --git a/src/igl/copyleft/cgal/trim_with_solid.h b/src/libigl/igl/copyleft/cgal/trim_with_solid.h similarity index 100% rename from src/igl/copyleft/cgal/trim_with_solid.h rename to src/libigl/igl/copyleft/cgal/trim_with_solid.h diff --git a/src/igl/copyleft/cgal/unique.cpp b/src/libigl/igl/copyleft/cgal/unique.cpp similarity index 100% rename from src/igl/copyleft/cgal/unique.cpp rename to src/libigl/igl/copyleft/cgal/unique.cpp diff --git a/src/igl/copyleft/cgal/unique_rows.cpp b/src/libigl/igl/copyleft/cgal/unique_rows.cpp similarity index 100% rename from src/igl/copyleft/cgal/unique_rows.cpp rename to src/libigl/igl/copyleft/cgal/unique_rows.cpp diff --git a/src/igl/copyleft/cgal/wire_mesh.cpp b/src/libigl/igl/copyleft/cgal/wire_mesh.cpp similarity index 100% rename from src/igl/copyleft/cgal/wire_mesh.cpp rename to src/libigl/igl/copyleft/cgal/wire_mesh.cpp diff --git a/src/igl/copyleft/cgal/wire_mesh.h b/src/libigl/igl/copyleft/cgal/wire_mesh.h similarity index 100% rename from src/igl/copyleft/cgal/wire_mesh.h rename to src/libigl/igl/copyleft/cgal/wire_mesh.h diff --git a/src/igl/copyleft/comiso/frame_field.cpp b/src/libigl/igl/copyleft/comiso/frame_field.cpp similarity index 100% rename from src/igl/copyleft/comiso/frame_field.cpp rename to src/libigl/igl/copyleft/comiso/frame_field.cpp diff --git a/src/igl/copyleft/comiso/frame_field.h b/src/libigl/igl/copyleft/comiso/frame_field.h similarity index 100% rename from src/igl/copyleft/comiso/frame_field.h rename to src/libigl/igl/copyleft/comiso/frame_field.h diff --git a/src/igl/copyleft/comiso/miq.cpp b/src/libigl/igl/copyleft/comiso/miq.cpp similarity index 100% rename from src/igl/copyleft/comiso/miq.cpp rename to src/libigl/igl/copyleft/comiso/miq.cpp diff --git a/src/igl/copyleft/comiso/miq.h b/src/libigl/igl/copyleft/comiso/miq.h similarity index 100% rename from src/igl/copyleft/comiso/miq.h rename to src/libigl/igl/copyleft/comiso/miq.h diff --git a/src/igl/copyleft/comiso/nrosy.cpp b/src/libigl/igl/copyleft/comiso/nrosy.cpp similarity index 100% rename from src/igl/copyleft/comiso/nrosy.cpp rename to src/libigl/igl/copyleft/comiso/nrosy.cpp diff --git a/src/igl/copyleft/comiso/nrosy.h b/src/libigl/igl/copyleft/comiso/nrosy.h similarity index 100% rename from src/igl/copyleft/comiso/nrosy.h rename to src/libigl/igl/copyleft/comiso/nrosy.h diff --git a/src/igl/copyleft/cork/from_cork_mesh.cpp b/src/libigl/igl/copyleft/cork/from_cork_mesh.cpp similarity index 100% rename from src/igl/copyleft/cork/from_cork_mesh.cpp rename to src/libigl/igl/copyleft/cork/from_cork_mesh.cpp diff --git a/src/igl/copyleft/cork/from_cork_mesh.h b/src/libigl/igl/copyleft/cork/from_cork_mesh.h similarity index 100% rename from src/igl/copyleft/cork/from_cork_mesh.h rename to src/libigl/igl/copyleft/cork/from_cork_mesh.h diff --git a/src/igl/copyleft/cork/mesh_boolean.cpp b/src/libigl/igl/copyleft/cork/mesh_boolean.cpp similarity index 100% rename from src/igl/copyleft/cork/mesh_boolean.cpp rename to src/libigl/igl/copyleft/cork/mesh_boolean.cpp diff --git a/src/igl/copyleft/cork/mesh_boolean.h b/src/libigl/igl/copyleft/cork/mesh_boolean.h similarity index 100% rename from src/igl/copyleft/cork/mesh_boolean.h rename to src/libigl/igl/copyleft/cork/mesh_boolean.h diff --git a/src/igl/copyleft/cork/to_cork_mesh.cpp b/src/libigl/igl/copyleft/cork/to_cork_mesh.cpp similarity index 100% rename from src/igl/copyleft/cork/to_cork_mesh.cpp rename to src/libigl/igl/copyleft/cork/to_cork_mesh.cpp diff --git a/src/igl/copyleft/cork/to_cork_mesh.h b/src/libigl/igl/copyleft/cork/to_cork_mesh.h similarity index 100% rename from src/igl/copyleft/cork/to_cork_mesh.h rename to src/libigl/igl/copyleft/cork/to_cork_mesh.h diff --git a/src/igl/copyleft/marching_cubes.cpp b/src/libigl/igl/copyleft/marching_cubes.cpp similarity index 100% rename from src/igl/copyleft/marching_cubes.cpp rename to src/libigl/igl/copyleft/marching_cubes.cpp diff --git a/src/igl/copyleft/marching_cubes.h b/src/libigl/igl/copyleft/marching_cubes.h similarity index 100% rename from src/igl/copyleft/marching_cubes.h rename to src/libigl/igl/copyleft/marching_cubes.h diff --git a/src/igl/copyleft/marching_cubes_tables.h b/src/libigl/igl/copyleft/marching_cubes_tables.h similarity index 100% rename from src/igl/copyleft/marching_cubes_tables.h rename to src/libigl/igl/copyleft/marching_cubes_tables.h diff --git a/src/igl/copyleft/offset_surface.cpp b/src/libigl/igl/copyleft/offset_surface.cpp similarity index 100% rename from src/igl/copyleft/offset_surface.cpp rename to src/libigl/igl/copyleft/offset_surface.cpp diff --git a/src/igl/copyleft/offset_surface.h b/src/libigl/igl/copyleft/offset_surface.h similarity index 100% rename from src/igl/copyleft/offset_surface.h rename to src/libigl/igl/copyleft/offset_surface.h diff --git a/src/igl/copyleft/opengl2/render_to_tga.cpp b/src/libigl/igl/copyleft/opengl2/render_to_tga.cpp similarity index 100% rename from src/igl/copyleft/opengl2/render_to_tga.cpp rename to src/libigl/igl/copyleft/opengl2/render_to_tga.cpp diff --git a/src/igl/copyleft/opengl2/render_to_tga.h b/src/libigl/igl/copyleft/opengl2/render_to_tga.h similarity index 100% rename from src/igl/copyleft/opengl2/render_to_tga.h rename to src/libigl/igl/copyleft/opengl2/render_to_tga.h diff --git a/src/igl/copyleft/opengl2/texture_from_tga.cpp b/src/libigl/igl/copyleft/opengl2/texture_from_tga.cpp similarity index 100% rename from src/igl/copyleft/opengl2/texture_from_tga.cpp rename to src/libigl/igl/copyleft/opengl2/texture_from_tga.cpp diff --git a/src/igl/copyleft/opengl2/texture_from_tga.h b/src/libigl/igl/copyleft/opengl2/texture_from_tga.h similarity index 100% rename from src/igl/copyleft/opengl2/texture_from_tga.h rename to src/libigl/igl/copyleft/opengl2/texture_from_tga.h diff --git a/src/igl/copyleft/opengl2/tga.cpp b/src/libigl/igl/copyleft/opengl2/tga.cpp similarity index 100% rename from src/igl/copyleft/opengl2/tga.cpp rename to src/libigl/igl/copyleft/opengl2/tga.cpp diff --git a/src/igl/copyleft/opengl2/tga.h b/src/libigl/igl/copyleft/opengl2/tga.h similarity index 100% rename from src/igl/copyleft/opengl2/tga.h rename to src/libigl/igl/copyleft/opengl2/tga.h diff --git a/src/igl/copyleft/progressive_hulls.cpp b/src/libigl/igl/copyleft/progressive_hulls.cpp similarity index 100% rename from src/igl/copyleft/progressive_hulls.cpp rename to src/libigl/igl/copyleft/progressive_hulls.cpp diff --git a/src/igl/copyleft/progressive_hulls.h b/src/libigl/igl/copyleft/progressive_hulls.h similarity index 100% rename from src/igl/copyleft/progressive_hulls.h rename to src/libigl/igl/copyleft/progressive_hulls.h diff --git a/src/igl/copyleft/progressive_hulls_cost_and_placement.cpp b/src/libigl/igl/copyleft/progressive_hulls_cost_and_placement.cpp similarity index 100% rename from src/igl/copyleft/progressive_hulls_cost_and_placement.cpp rename to src/libigl/igl/copyleft/progressive_hulls_cost_and_placement.cpp diff --git a/src/igl/copyleft/progressive_hulls_cost_and_placement.h b/src/libigl/igl/copyleft/progressive_hulls_cost_and_placement.h similarity index 100% rename from src/igl/copyleft/progressive_hulls_cost_and_placement.h rename to src/libigl/igl/copyleft/progressive_hulls_cost_and_placement.h diff --git a/src/igl/copyleft/quadprog.cpp b/src/libigl/igl/copyleft/quadprog.cpp similarity index 100% rename from src/igl/copyleft/quadprog.cpp rename to src/libigl/igl/copyleft/quadprog.cpp diff --git a/src/igl/copyleft/quadprog.h b/src/libigl/igl/copyleft/quadprog.h similarity index 100% rename from src/igl/copyleft/quadprog.h rename to src/libigl/igl/copyleft/quadprog.h diff --git a/src/igl/copyleft/swept_volume.cpp b/src/libigl/igl/copyleft/swept_volume.cpp similarity index 100% rename from src/igl/copyleft/swept_volume.cpp rename to src/libigl/igl/copyleft/swept_volume.cpp diff --git a/src/igl/copyleft/swept_volume.h b/src/libigl/igl/copyleft/swept_volume.h similarity index 100% rename from src/igl/copyleft/swept_volume.h rename to src/libigl/igl/copyleft/swept_volume.h diff --git a/src/igl/copyleft/tetgen/README b/src/libigl/igl/copyleft/tetgen/README similarity index 100% rename from src/igl/copyleft/tetgen/README rename to src/libigl/igl/copyleft/tetgen/README diff --git a/src/igl/copyleft/tetgen/cdt.cpp b/src/libigl/igl/copyleft/tetgen/cdt.cpp similarity index 100% rename from src/igl/copyleft/tetgen/cdt.cpp rename to src/libigl/igl/copyleft/tetgen/cdt.cpp diff --git a/src/igl/copyleft/tetgen/cdt.h b/src/libigl/igl/copyleft/tetgen/cdt.h similarity index 100% rename from src/igl/copyleft/tetgen/cdt.h rename to src/libigl/igl/copyleft/tetgen/cdt.h diff --git a/src/igl/copyleft/tetgen/mesh_to_tetgenio.cpp b/src/libigl/igl/copyleft/tetgen/mesh_to_tetgenio.cpp similarity index 100% rename from src/igl/copyleft/tetgen/mesh_to_tetgenio.cpp rename to src/libigl/igl/copyleft/tetgen/mesh_to_tetgenio.cpp diff --git a/src/igl/copyleft/tetgen/mesh_to_tetgenio.h b/src/libigl/igl/copyleft/tetgen/mesh_to_tetgenio.h similarity index 100% rename from src/igl/copyleft/tetgen/mesh_to_tetgenio.h rename to src/libigl/igl/copyleft/tetgen/mesh_to_tetgenio.h diff --git a/src/igl/copyleft/tetgen/mesh_with_skeleton.cpp b/src/libigl/igl/copyleft/tetgen/mesh_with_skeleton.cpp similarity index 100% rename from src/igl/copyleft/tetgen/mesh_with_skeleton.cpp rename to src/libigl/igl/copyleft/tetgen/mesh_with_skeleton.cpp diff --git a/src/igl/copyleft/tetgen/mesh_with_skeleton.h b/src/libigl/igl/copyleft/tetgen/mesh_with_skeleton.h similarity index 100% rename from src/igl/copyleft/tetgen/mesh_with_skeleton.h rename to src/libigl/igl/copyleft/tetgen/mesh_with_skeleton.h diff --git a/src/igl/copyleft/tetgen/read_into_tetgenio.cpp b/src/libigl/igl/copyleft/tetgen/read_into_tetgenio.cpp similarity index 100% rename from src/igl/copyleft/tetgen/read_into_tetgenio.cpp rename to src/libigl/igl/copyleft/tetgen/read_into_tetgenio.cpp diff --git a/src/igl/copyleft/tetgen/read_into_tetgenio.h b/src/libigl/igl/copyleft/tetgen/read_into_tetgenio.h similarity index 100% rename from src/igl/copyleft/tetgen/read_into_tetgenio.h rename to src/libigl/igl/copyleft/tetgen/read_into_tetgenio.h diff --git a/src/igl/copyleft/tetgen/tetgenio_to_tetmesh.cpp b/src/libigl/igl/copyleft/tetgen/tetgenio_to_tetmesh.cpp similarity index 100% rename from src/igl/copyleft/tetgen/tetgenio_to_tetmesh.cpp rename to src/libigl/igl/copyleft/tetgen/tetgenio_to_tetmesh.cpp diff --git a/src/igl/copyleft/tetgen/tetgenio_to_tetmesh.h b/src/libigl/igl/copyleft/tetgen/tetgenio_to_tetmesh.h similarity index 100% rename from src/igl/copyleft/tetgen/tetgenio_to_tetmesh.h rename to src/libigl/igl/copyleft/tetgen/tetgenio_to_tetmesh.h diff --git a/src/igl/copyleft/tetgen/tetrahedralize.cpp b/src/libigl/igl/copyleft/tetgen/tetrahedralize.cpp similarity index 100% rename from src/igl/copyleft/tetgen/tetrahedralize.cpp rename to src/libigl/igl/copyleft/tetgen/tetrahedralize.cpp diff --git a/src/igl/copyleft/tetgen/tetrahedralize.h b/src/libigl/igl/copyleft/tetgen/tetrahedralize.h similarity index 100% rename from src/igl/copyleft/tetgen/tetrahedralize.h rename to src/libigl/igl/copyleft/tetgen/tetrahedralize.h diff --git a/src/igl/cotmatrix.cpp b/src/libigl/igl/cotmatrix.cpp similarity index 100% rename from src/igl/cotmatrix.cpp rename to src/libigl/igl/cotmatrix.cpp diff --git a/src/igl/cotmatrix.h b/src/libigl/igl/cotmatrix.h similarity index 100% rename from src/igl/cotmatrix.h rename to src/libigl/igl/cotmatrix.h diff --git a/src/igl/cotmatrix_entries.cpp b/src/libigl/igl/cotmatrix_entries.cpp similarity index 100% rename from src/igl/cotmatrix_entries.cpp rename to src/libigl/igl/cotmatrix_entries.cpp diff --git a/src/igl/cotmatrix_entries.h b/src/libigl/igl/cotmatrix_entries.h similarity index 100% rename from src/igl/cotmatrix_entries.h rename to src/libigl/igl/cotmatrix_entries.h diff --git a/src/igl/count.cpp b/src/libigl/igl/count.cpp similarity index 100% rename from src/igl/count.cpp rename to src/libigl/igl/count.cpp diff --git a/src/igl/count.h b/src/libigl/igl/count.h similarity index 100% rename from src/igl/count.h rename to src/libigl/igl/count.h diff --git a/src/igl/covariance_scatter_matrix.cpp b/src/libigl/igl/covariance_scatter_matrix.cpp similarity index 100% rename from src/igl/covariance_scatter_matrix.cpp rename to src/libigl/igl/covariance_scatter_matrix.cpp diff --git a/src/igl/covariance_scatter_matrix.h b/src/libigl/igl/covariance_scatter_matrix.h similarity index 100% rename from src/igl/covariance_scatter_matrix.h rename to src/libigl/igl/covariance_scatter_matrix.h diff --git a/src/igl/cross.cpp b/src/libigl/igl/cross.cpp similarity index 100% rename from src/igl/cross.cpp rename to src/libigl/igl/cross.cpp diff --git a/src/igl/cross.h b/src/libigl/igl/cross.h similarity index 100% rename from src/igl/cross.h rename to src/libigl/igl/cross.h diff --git a/src/igl/cross_field_missmatch.cpp b/src/libigl/igl/cross_field_missmatch.cpp similarity index 100% rename from src/igl/cross_field_missmatch.cpp rename to src/libigl/igl/cross_field_missmatch.cpp diff --git a/src/igl/cross_field_missmatch.h b/src/libigl/igl/cross_field_missmatch.h similarity index 100% rename from src/igl/cross_field_missmatch.h rename to src/libigl/igl/cross_field_missmatch.h diff --git a/src/igl/crouzeix_raviart_cotmatrix.cpp b/src/libigl/igl/crouzeix_raviart_cotmatrix.cpp similarity index 100% rename from src/igl/crouzeix_raviart_cotmatrix.cpp rename to src/libigl/igl/crouzeix_raviart_cotmatrix.cpp diff --git a/src/igl/crouzeix_raviart_cotmatrix.h b/src/libigl/igl/crouzeix_raviart_cotmatrix.h similarity index 100% rename from src/igl/crouzeix_raviart_cotmatrix.h rename to src/libigl/igl/crouzeix_raviart_cotmatrix.h diff --git a/src/igl/crouzeix_raviart_massmatrix.cpp b/src/libigl/igl/crouzeix_raviart_massmatrix.cpp similarity index 100% rename from src/igl/crouzeix_raviart_massmatrix.cpp rename to src/libigl/igl/crouzeix_raviart_massmatrix.cpp diff --git a/src/igl/crouzeix_raviart_massmatrix.h b/src/libigl/igl/crouzeix_raviart_massmatrix.h similarity index 100% rename from src/igl/crouzeix_raviart_massmatrix.h rename to src/libigl/igl/crouzeix_raviart_massmatrix.h diff --git a/src/igl/cumsum.cpp b/src/libigl/igl/cumsum.cpp similarity index 100% rename from src/igl/cumsum.cpp rename to src/libigl/igl/cumsum.cpp diff --git a/src/igl/cumsum.h b/src/libigl/igl/cumsum.h similarity index 100% rename from src/igl/cumsum.h rename to src/libigl/igl/cumsum.h diff --git a/src/igl/cut_mesh.cpp b/src/libigl/igl/cut_mesh.cpp similarity index 100% rename from src/igl/cut_mesh.cpp rename to src/libigl/igl/cut_mesh.cpp diff --git a/src/igl/cut_mesh.h b/src/libigl/igl/cut_mesh.h similarity index 100% rename from src/igl/cut_mesh.h rename to src/libigl/igl/cut_mesh.h diff --git a/src/igl/cut_mesh_from_singularities.cpp b/src/libigl/igl/cut_mesh_from_singularities.cpp similarity index 100% rename from src/igl/cut_mesh_from_singularities.cpp rename to src/libigl/igl/cut_mesh_from_singularities.cpp diff --git a/src/igl/cut_mesh_from_singularities.h b/src/libigl/igl/cut_mesh_from_singularities.h similarity index 100% rename from src/igl/cut_mesh_from_singularities.h rename to src/libigl/igl/cut_mesh_from_singularities.h diff --git a/src/igl/cylinder.cpp b/src/libigl/igl/cylinder.cpp similarity index 100% rename from src/igl/cylinder.cpp rename to src/libigl/igl/cylinder.cpp diff --git a/src/igl/cylinder.h b/src/libigl/igl/cylinder.h similarity index 100% rename from src/igl/cylinder.h rename to src/libigl/igl/cylinder.h diff --git a/src/igl/dated_copy.cpp b/src/libigl/igl/dated_copy.cpp similarity index 100% rename from src/igl/dated_copy.cpp rename to src/libigl/igl/dated_copy.cpp diff --git a/src/igl/dated_copy.h b/src/libigl/igl/dated_copy.h similarity index 100% rename from src/igl/dated_copy.h rename to src/libigl/igl/dated_copy.h diff --git a/src/igl/decimate.cpp b/src/libigl/igl/decimate.cpp similarity index 100% rename from src/igl/decimate.cpp rename to src/libigl/igl/decimate.cpp diff --git a/src/igl/decimate.h b/src/libigl/igl/decimate.h similarity index 100% rename from src/igl/decimate.h rename to src/libigl/igl/decimate.h diff --git a/src/igl/deform_skeleton.cpp b/src/libigl/igl/deform_skeleton.cpp similarity index 100% rename from src/igl/deform_skeleton.cpp rename to src/libigl/igl/deform_skeleton.cpp diff --git a/src/igl/deform_skeleton.h b/src/libigl/igl/deform_skeleton.h similarity index 100% rename from src/igl/deform_skeleton.h rename to src/libigl/igl/deform_skeleton.h diff --git a/src/igl/delaunay_triangulation.cpp b/src/libigl/igl/delaunay_triangulation.cpp similarity index 100% rename from src/igl/delaunay_triangulation.cpp rename to src/libigl/igl/delaunay_triangulation.cpp diff --git a/src/igl/delaunay_triangulation.h b/src/libigl/igl/delaunay_triangulation.h similarity index 100% rename from src/igl/delaunay_triangulation.h rename to src/libigl/igl/delaunay_triangulation.h diff --git a/src/igl/deprecated.h b/src/libigl/igl/deprecated.h similarity index 100% rename from src/igl/deprecated.h rename to src/libigl/igl/deprecated.h diff --git a/src/igl/dfs.cpp b/src/libigl/igl/dfs.cpp similarity index 100% rename from src/igl/dfs.cpp rename to src/libigl/igl/dfs.cpp diff --git a/src/igl/dfs.h b/src/libigl/igl/dfs.h similarity index 100% rename from src/igl/dfs.h rename to src/libigl/igl/dfs.h diff --git a/src/igl/diag.cpp b/src/libigl/igl/diag.cpp similarity index 100% rename from src/igl/diag.cpp rename to src/libigl/igl/diag.cpp diff --git a/src/igl/diag.h b/src/libigl/igl/diag.h similarity index 100% rename from src/igl/diag.h rename to src/libigl/igl/diag.h diff --git a/src/igl/dihedral_angles.cpp b/src/libigl/igl/dihedral_angles.cpp similarity index 100% rename from src/igl/dihedral_angles.cpp rename to src/libigl/igl/dihedral_angles.cpp diff --git a/src/igl/dihedral_angles.h b/src/libigl/igl/dihedral_angles.h similarity index 100% rename from src/igl/dihedral_angles.h rename to src/libigl/igl/dihedral_angles.h diff --git a/src/igl/dijkstra.cpp b/src/libigl/igl/dijkstra.cpp similarity index 100% rename from src/igl/dijkstra.cpp rename to src/libigl/igl/dijkstra.cpp diff --git a/src/igl/dijkstra.h b/src/libigl/igl/dijkstra.h similarity index 100% rename from src/igl/dijkstra.h rename to src/libigl/igl/dijkstra.h diff --git a/src/igl/directed_edge_orientations.cpp b/src/libigl/igl/directed_edge_orientations.cpp similarity index 100% rename from src/igl/directed_edge_orientations.cpp rename to src/libigl/igl/directed_edge_orientations.cpp diff --git a/src/igl/directed_edge_orientations.h b/src/libigl/igl/directed_edge_orientations.h similarity index 100% rename from src/igl/directed_edge_orientations.h rename to src/libigl/igl/directed_edge_orientations.h diff --git a/src/igl/directed_edge_parents.cpp b/src/libigl/igl/directed_edge_parents.cpp similarity index 100% rename from src/igl/directed_edge_parents.cpp rename to src/libigl/igl/directed_edge_parents.cpp diff --git a/src/igl/directed_edge_parents.h b/src/libigl/igl/directed_edge_parents.h similarity index 100% rename from src/igl/directed_edge_parents.h rename to src/libigl/igl/directed_edge_parents.h diff --git a/src/igl/dirname.cpp b/src/libigl/igl/dirname.cpp similarity index 100% rename from src/igl/dirname.cpp rename to src/libigl/igl/dirname.cpp diff --git a/src/igl/dirname.h b/src/libigl/igl/dirname.h similarity index 100% rename from src/igl/dirname.h rename to src/libigl/igl/dirname.h diff --git a/src/igl/dot.cpp b/src/libigl/igl/dot.cpp similarity index 100% rename from src/igl/dot.cpp rename to src/libigl/igl/dot.cpp diff --git a/src/igl/dot.h b/src/libigl/igl/dot.h similarity index 100% rename from src/igl/dot.h rename to src/libigl/igl/dot.h diff --git a/src/igl/dot_row.cpp b/src/libigl/igl/dot_row.cpp similarity index 100% rename from src/igl/dot_row.cpp rename to src/libigl/igl/dot_row.cpp diff --git a/src/igl/dot_row.h b/src/libigl/igl/dot_row.h similarity index 100% rename from src/igl/dot_row.h rename to src/libigl/igl/dot_row.h diff --git a/src/igl/doublearea.cpp b/src/libigl/igl/doublearea.cpp similarity index 100% rename from src/igl/doublearea.cpp rename to src/libigl/igl/doublearea.cpp diff --git a/src/igl/doublearea.h b/src/libigl/igl/doublearea.h similarity index 100% rename from src/igl/doublearea.h rename to src/libigl/igl/doublearea.h diff --git a/src/igl/dqs.cpp b/src/libigl/igl/dqs.cpp similarity index 100% rename from src/igl/dqs.cpp rename to src/libigl/igl/dqs.cpp diff --git a/src/igl/dqs.h b/src/libigl/igl/dqs.h similarity index 100% rename from src/igl/dqs.h rename to src/libigl/igl/dqs.h diff --git a/src/igl/ears.cpp b/src/libigl/igl/ears.cpp similarity index 100% rename from src/igl/ears.cpp rename to src/libigl/igl/ears.cpp diff --git a/src/igl/ears.h b/src/libigl/igl/ears.h similarity index 100% rename from src/igl/ears.h rename to src/libigl/igl/ears.h diff --git a/src/igl/edge_collapse_is_valid.cpp b/src/libigl/igl/edge_collapse_is_valid.cpp similarity index 100% rename from src/igl/edge_collapse_is_valid.cpp rename to src/libigl/igl/edge_collapse_is_valid.cpp diff --git a/src/igl/edge_collapse_is_valid.h b/src/libigl/igl/edge_collapse_is_valid.h similarity index 100% rename from src/igl/edge_collapse_is_valid.h rename to src/libigl/igl/edge_collapse_is_valid.h diff --git a/src/igl/edge_flaps.cpp b/src/libigl/igl/edge_flaps.cpp similarity index 100% rename from src/igl/edge_flaps.cpp rename to src/libigl/igl/edge_flaps.cpp diff --git a/src/igl/edge_flaps.h b/src/libigl/igl/edge_flaps.h similarity index 100% rename from src/igl/edge_flaps.h rename to src/libigl/igl/edge_flaps.h diff --git a/src/igl/edge_lengths.cpp b/src/libigl/igl/edge_lengths.cpp similarity index 100% rename from src/igl/edge_lengths.cpp rename to src/libigl/igl/edge_lengths.cpp diff --git a/src/igl/edge_lengths.h b/src/libigl/igl/edge_lengths.h similarity index 100% rename from src/igl/edge_lengths.h rename to src/libigl/igl/edge_lengths.h diff --git a/src/igl/edge_topology.cpp b/src/libigl/igl/edge_topology.cpp similarity index 100% rename from src/igl/edge_topology.cpp rename to src/libigl/igl/edge_topology.cpp diff --git a/src/igl/edge_topology.h b/src/libigl/igl/edge_topology.h similarity index 100% rename from src/igl/edge_topology.h rename to src/libigl/igl/edge_topology.h diff --git a/src/igl/edges.cpp b/src/libigl/igl/edges.cpp similarity index 100% rename from src/igl/edges.cpp rename to src/libigl/igl/edges.cpp diff --git a/src/igl/edges.h b/src/libigl/igl/edges.h similarity index 100% rename from src/igl/edges.h rename to src/libigl/igl/edges.h diff --git a/src/igl/edges_to_path.cpp b/src/libigl/igl/edges_to_path.cpp similarity index 100% rename from src/igl/edges_to_path.cpp rename to src/libigl/igl/edges_to_path.cpp diff --git a/src/igl/edges_to_path.h b/src/libigl/igl/edges_to_path.h similarity index 100% rename from src/igl/edges_to_path.h rename to src/libigl/igl/edges_to_path.h diff --git a/src/igl/eigs.cpp b/src/libigl/igl/eigs.cpp similarity index 100% rename from src/igl/eigs.cpp rename to src/libigl/igl/eigs.cpp diff --git a/src/igl/eigs.h b/src/libigl/igl/eigs.h similarity index 100% rename from src/igl/eigs.h rename to src/libigl/igl/eigs.h diff --git a/src/igl/embree/EmbreeIntersector.h b/src/libigl/igl/embree/EmbreeIntersector.h similarity index 100% rename from src/igl/embree/EmbreeIntersector.h rename to src/libigl/igl/embree/EmbreeIntersector.h diff --git a/src/igl/embree/Embree_convenience.h b/src/libigl/igl/embree/Embree_convenience.h similarity index 100% rename from src/igl/embree/Embree_convenience.h rename to src/libigl/igl/embree/Embree_convenience.h diff --git a/src/igl/embree/ambient_occlusion.cpp b/src/libigl/igl/embree/ambient_occlusion.cpp similarity index 100% rename from src/igl/embree/ambient_occlusion.cpp rename to src/libigl/igl/embree/ambient_occlusion.cpp diff --git a/src/igl/embree/ambient_occlusion.h b/src/libigl/igl/embree/ambient_occlusion.h similarity index 100% rename from src/igl/embree/ambient_occlusion.h rename to src/libigl/igl/embree/ambient_occlusion.h diff --git a/src/igl/embree/bone_heat.cpp b/src/libigl/igl/embree/bone_heat.cpp similarity index 100% rename from src/igl/embree/bone_heat.cpp rename to src/libigl/igl/embree/bone_heat.cpp diff --git a/src/igl/embree/bone_heat.h b/src/libigl/igl/embree/bone_heat.h similarity index 100% rename from src/igl/embree/bone_heat.h rename to src/libigl/igl/embree/bone_heat.h diff --git a/src/igl/embree/bone_visible.cpp b/src/libigl/igl/embree/bone_visible.cpp similarity index 100% rename from src/igl/embree/bone_visible.cpp rename to src/libigl/igl/embree/bone_visible.cpp diff --git a/src/igl/embree/bone_visible.h b/src/libigl/igl/embree/bone_visible.h similarity index 100% rename from src/igl/embree/bone_visible.h rename to src/libigl/igl/embree/bone_visible.h diff --git a/src/igl/embree/embree2/rtcore.h b/src/libigl/igl/embree/embree2/rtcore.h similarity index 100% rename from src/igl/embree/embree2/rtcore.h rename to src/libigl/igl/embree/embree2/rtcore.h diff --git a/src/igl/embree/embree2/rtcore.isph b/src/libigl/igl/embree/embree2/rtcore.isph similarity index 100% rename from src/igl/embree/embree2/rtcore.isph rename to src/libigl/igl/embree/embree2/rtcore.isph diff --git a/src/igl/embree/embree2/rtcore_geometry.h b/src/libigl/igl/embree/embree2/rtcore_geometry.h similarity index 100% rename from src/igl/embree/embree2/rtcore_geometry.h rename to src/libigl/igl/embree/embree2/rtcore_geometry.h diff --git a/src/igl/embree/embree2/rtcore_geometry.isph b/src/libigl/igl/embree/embree2/rtcore_geometry.isph similarity index 100% rename from src/igl/embree/embree2/rtcore_geometry.isph rename to src/libigl/igl/embree/embree2/rtcore_geometry.isph diff --git a/src/igl/embree/embree2/rtcore_geometry_user.h b/src/libigl/igl/embree/embree2/rtcore_geometry_user.h similarity index 100% rename from src/igl/embree/embree2/rtcore_geometry_user.h rename to src/libigl/igl/embree/embree2/rtcore_geometry_user.h diff --git a/src/igl/embree/embree2/rtcore_geometry_user.isph b/src/libigl/igl/embree/embree2/rtcore_geometry_user.isph similarity index 100% rename from src/igl/embree/embree2/rtcore_geometry_user.isph rename to src/libigl/igl/embree/embree2/rtcore_geometry_user.isph diff --git a/src/igl/embree/embree2/rtcore_ray.h b/src/libigl/igl/embree/embree2/rtcore_ray.h similarity index 100% rename from src/igl/embree/embree2/rtcore_ray.h rename to src/libigl/igl/embree/embree2/rtcore_ray.h diff --git a/src/igl/embree/embree2/rtcore_ray.isph b/src/libigl/igl/embree/embree2/rtcore_ray.isph similarity index 100% rename from src/igl/embree/embree2/rtcore_ray.isph rename to src/libigl/igl/embree/embree2/rtcore_ray.isph diff --git a/src/igl/embree/embree2/rtcore_scene.h b/src/libigl/igl/embree/embree2/rtcore_scene.h similarity index 100% rename from src/igl/embree/embree2/rtcore_scene.h rename to src/libigl/igl/embree/embree2/rtcore_scene.h diff --git a/src/igl/embree/embree2/rtcore_scene.isph b/src/libigl/igl/embree/embree2/rtcore_scene.isph similarity index 100% rename from src/igl/embree/embree2/rtcore_scene.isph rename to src/libigl/igl/embree/embree2/rtcore_scene.isph diff --git a/src/igl/embree/line_mesh_intersection.cpp b/src/libigl/igl/embree/line_mesh_intersection.cpp similarity index 100% rename from src/igl/embree/line_mesh_intersection.cpp rename to src/libigl/igl/embree/line_mesh_intersection.cpp diff --git a/src/igl/embree/line_mesh_intersection.h b/src/libigl/igl/embree/line_mesh_intersection.h similarity index 100% rename from src/igl/embree/line_mesh_intersection.h rename to src/libigl/igl/embree/line_mesh_intersection.h diff --git a/src/igl/embree/reorient_facets_raycast.cpp b/src/libigl/igl/embree/reorient_facets_raycast.cpp similarity index 100% rename from src/igl/embree/reorient_facets_raycast.cpp rename to src/libigl/igl/embree/reorient_facets_raycast.cpp diff --git a/src/igl/embree/reorient_facets_raycast.h b/src/libigl/igl/embree/reorient_facets_raycast.h similarity index 100% rename from src/igl/embree/reorient_facets_raycast.h rename to src/libigl/igl/embree/reorient_facets_raycast.h diff --git a/src/igl/embree/shape_diameter_function.cpp b/src/libigl/igl/embree/shape_diameter_function.cpp similarity index 100% rename from src/igl/embree/shape_diameter_function.cpp rename to src/libigl/igl/embree/shape_diameter_function.cpp diff --git a/src/igl/embree/shape_diameter_function.h b/src/libigl/igl/embree/shape_diameter_function.h similarity index 100% rename from src/igl/embree/shape_diameter_function.h rename to src/libigl/igl/embree/shape_diameter_function.h diff --git a/src/igl/embree/unproject_in_mesh.cpp b/src/libigl/igl/embree/unproject_in_mesh.cpp similarity index 100% rename from src/igl/embree/unproject_in_mesh.cpp rename to src/libigl/igl/embree/unproject_in_mesh.cpp diff --git a/src/igl/embree/unproject_in_mesh.h b/src/libigl/igl/embree/unproject_in_mesh.h similarity index 100% rename from src/igl/embree/unproject_in_mesh.h rename to src/libigl/igl/embree/unproject_in_mesh.h diff --git a/src/igl/embree/unproject_onto_mesh.cpp b/src/libigl/igl/embree/unproject_onto_mesh.cpp similarity index 100% rename from src/igl/embree/unproject_onto_mesh.cpp rename to src/libigl/igl/embree/unproject_onto_mesh.cpp diff --git a/src/igl/embree/unproject_onto_mesh.h b/src/libigl/igl/embree/unproject_onto_mesh.h similarity index 100% rename from src/igl/embree/unproject_onto_mesh.h rename to src/libigl/igl/embree/unproject_onto_mesh.h diff --git a/src/igl/euler_characteristic.cpp b/src/libigl/igl/euler_characteristic.cpp similarity index 100% rename from src/igl/euler_characteristic.cpp rename to src/libigl/igl/euler_characteristic.cpp diff --git a/src/igl/euler_characteristic.h b/src/libigl/igl/euler_characteristic.h similarity index 100% rename from src/igl/euler_characteristic.h rename to src/libigl/igl/euler_characteristic.h diff --git a/src/igl/exact_geodesic.cpp b/src/libigl/igl/exact_geodesic.cpp similarity index 100% rename from src/igl/exact_geodesic.cpp rename to src/libigl/igl/exact_geodesic.cpp diff --git a/src/igl/exact_geodesic.h b/src/libigl/igl/exact_geodesic.h similarity index 100% rename from src/igl/exact_geodesic.h rename to src/libigl/igl/exact_geodesic.h diff --git a/src/igl/example_fun.cpp b/src/libigl/igl/example_fun.cpp similarity index 100% rename from src/igl/example_fun.cpp rename to src/libigl/igl/example_fun.cpp diff --git a/src/igl/example_fun.h b/src/libigl/igl/example_fun.h similarity index 100% rename from src/igl/example_fun.h rename to src/libigl/igl/example_fun.h diff --git a/src/igl/exterior_edges.cpp b/src/libigl/igl/exterior_edges.cpp similarity index 100% rename from src/igl/exterior_edges.cpp rename to src/libigl/igl/exterior_edges.cpp diff --git a/src/igl/exterior_edges.h b/src/libigl/igl/exterior_edges.h similarity index 100% rename from src/igl/exterior_edges.h rename to src/libigl/igl/exterior_edges.h diff --git a/src/igl/extract_manifold_patches.cpp b/src/libigl/igl/extract_manifold_patches.cpp similarity index 100% rename from src/igl/extract_manifold_patches.cpp rename to src/libigl/igl/extract_manifold_patches.cpp diff --git a/src/igl/extract_manifold_patches.h b/src/libigl/igl/extract_manifold_patches.h similarity index 100% rename from src/igl/extract_manifold_patches.h rename to src/libigl/igl/extract_manifold_patches.h diff --git a/src/igl/extract_non_manifold_edge_curves.cpp b/src/libigl/igl/extract_non_manifold_edge_curves.cpp similarity index 100% rename from src/igl/extract_non_manifold_edge_curves.cpp rename to src/libigl/igl/extract_non_manifold_edge_curves.cpp diff --git a/src/igl/extract_non_manifold_edge_curves.h b/src/libigl/igl/extract_non_manifold_edge_curves.h similarity index 100% rename from src/igl/extract_non_manifold_edge_curves.h rename to src/libigl/igl/extract_non_manifold_edge_curves.h diff --git a/src/igl/face_areas.cpp b/src/libigl/igl/face_areas.cpp similarity index 100% rename from src/igl/face_areas.cpp rename to src/libigl/igl/face_areas.cpp diff --git a/src/igl/face_areas.h b/src/libigl/igl/face_areas.h similarity index 100% rename from src/igl/face_areas.h rename to src/libigl/igl/face_areas.h diff --git a/src/igl/face_occurrences.cpp b/src/libigl/igl/face_occurrences.cpp similarity index 100% rename from src/igl/face_occurrences.cpp rename to src/libigl/igl/face_occurrences.cpp diff --git a/src/igl/face_occurrences.h b/src/libigl/igl/face_occurrences.h similarity index 100% rename from src/igl/face_occurrences.h rename to src/libigl/igl/face_occurrences.h diff --git a/src/igl/faces_first.cpp b/src/libigl/igl/faces_first.cpp similarity index 100% rename from src/igl/faces_first.cpp rename to src/libigl/igl/faces_first.cpp diff --git a/src/igl/faces_first.h b/src/libigl/igl/faces_first.h similarity index 100% rename from src/igl/faces_first.h rename to src/libigl/igl/faces_first.h diff --git a/src/igl/facet_components.cpp b/src/libigl/igl/facet_components.cpp similarity index 100% rename from src/igl/facet_components.cpp rename to src/libigl/igl/facet_components.cpp diff --git a/src/igl/facet_components.h b/src/libigl/igl/facet_components.h similarity index 100% rename from src/igl/facet_components.h rename to src/libigl/igl/facet_components.h diff --git a/src/igl/false_barycentric_subdivision.cpp b/src/libigl/igl/false_barycentric_subdivision.cpp similarity index 100% rename from src/igl/false_barycentric_subdivision.cpp rename to src/libigl/igl/false_barycentric_subdivision.cpp diff --git a/src/igl/false_barycentric_subdivision.h b/src/libigl/igl/false_barycentric_subdivision.h similarity index 100% rename from src/igl/false_barycentric_subdivision.h rename to src/libigl/igl/false_barycentric_subdivision.h diff --git a/src/igl/fast_winding_number.cpp b/src/libigl/igl/fast_winding_number.cpp similarity index 100% rename from src/igl/fast_winding_number.cpp rename to src/libigl/igl/fast_winding_number.cpp diff --git a/src/igl/fast_winding_number.h b/src/libigl/igl/fast_winding_number.h similarity index 100% rename from src/igl/fast_winding_number.h rename to src/libigl/igl/fast_winding_number.h diff --git a/src/igl/file_contents_as_string.cpp b/src/libigl/igl/file_contents_as_string.cpp similarity index 100% rename from src/igl/file_contents_as_string.cpp rename to src/libigl/igl/file_contents_as_string.cpp diff --git a/src/igl/file_contents_as_string.h b/src/libigl/igl/file_contents_as_string.h similarity index 100% rename from src/igl/file_contents_as_string.h rename to src/libigl/igl/file_contents_as_string.h diff --git a/src/igl/file_dialog_open.cpp b/src/libigl/igl/file_dialog_open.cpp similarity index 100% rename from src/igl/file_dialog_open.cpp rename to src/libigl/igl/file_dialog_open.cpp diff --git a/src/igl/file_dialog_open.h b/src/libigl/igl/file_dialog_open.h similarity index 100% rename from src/igl/file_dialog_open.h rename to src/libigl/igl/file_dialog_open.h diff --git a/src/igl/file_dialog_save.cpp b/src/libigl/igl/file_dialog_save.cpp similarity index 100% rename from src/igl/file_dialog_save.cpp rename to src/libigl/igl/file_dialog_save.cpp diff --git a/src/igl/file_dialog_save.h b/src/libigl/igl/file_dialog_save.h similarity index 100% rename from src/igl/file_dialog_save.h rename to src/libigl/igl/file_dialog_save.h diff --git a/src/igl/file_exists.cpp b/src/libigl/igl/file_exists.cpp similarity index 100% rename from src/igl/file_exists.cpp rename to src/libigl/igl/file_exists.cpp diff --git a/src/igl/file_exists.h b/src/libigl/igl/file_exists.h similarity index 100% rename from src/igl/file_exists.h rename to src/libigl/igl/file_exists.h diff --git a/src/igl/find.cpp b/src/libigl/igl/find.cpp similarity index 100% rename from src/igl/find.cpp rename to src/libigl/igl/find.cpp diff --git a/src/igl/find.h b/src/libigl/igl/find.h similarity index 100% rename from src/igl/find.h rename to src/libigl/igl/find.h diff --git a/src/igl/find_cross_field_singularities.cpp b/src/libigl/igl/find_cross_field_singularities.cpp similarity index 100% rename from src/igl/find_cross_field_singularities.cpp rename to src/libigl/igl/find_cross_field_singularities.cpp diff --git a/src/igl/find_cross_field_singularities.h b/src/libigl/igl/find_cross_field_singularities.h similarity index 100% rename from src/igl/find_cross_field_singularities.h rename to src/libigl/igl/find_cross_field_singularities.h diff --git a/src/igl/find_zero.cpp b/src/libigl/igl/find_zero.cpp similarity index 100% rename from src/igl/find_zero.cpp rename to src/libigl/igl/find_zero.cpp diff --git a/src/igl/find_zero.h b/src/libigl/igl/find_zero.h similarity index 100% rename from src/igl/find_zero.h rename to src/libigl/igl/find_zero.h diff --git a/src/igl/fit_plane.cpp b/src/libigl/igl/fit_plane.cpp similarity index 100% rename from src/igl/fit_plane.cpp rename to src/libigl/igl/fit_plane.cpp diff --git a/src/igl/fit_plane.h b/src/libigl/igl/fit_plane.h similarity index 100% rename from src/igl/fit_plane.h rename to src/libigl/igl/fit_plane.h diff --git a/src/igl/fit_rotations.cpp b/src/libigl/igl/fit_rotations.cpp similarity index 100% rename from src/igl/fit_rotations.cpp rename to src/libigl/igl/fit_rotations.cpp diff --git a/src/igl/fit_rotations.h b/src/libigl/igl/fit_rotations.h similarity index 100% rename from src/igl/fit_rotations.h rename to src/libigl/igl/fit_rotations.h diff --git a/src/igl/flip_avoiding_line_search.cpp b/src/libigl/igl/flip_avoiding_line_search.cpp similarity index 100% rename from src/igl/flip_avoiding_line_search.cpp rename to src/libigl/igl/flip_avoiding_line_search.cpp diff --git a/src/igl/flip_avoiding_line_search.h b/src/libigl/igl/flip_avoiding_line_search.h similarity index 100% rename from src/igl/flip_avoiding_line_search.h rename to src/libigl/igl/flip_avoiding_line_search.h diff --git a/src/igl/flip_edge.cpp b/src/libigl/igl/flip_edge.cpp similarity index 100% rename from src/igl/flip_edge.cpp rename to src/libigl/igl/flip_edge.cpp diff --git a/src/igl/flip_edge.h b/src/libigl/igl/flip_edge.h similarity index 100% rename from src/igl/flip_edge.h rename to src/libigl/igl/flip_edge.h diff --git a/src/igl/flipped_triangles.cpp b/src/libigl/igl/flipped_triangles.cpp similarity index 100% rename from src/igl/flipped_triangles.cpp rename to src/libigl/igl/flipped_triangles.cpp diff --git a/src/igl/flipped_triangles.h b/src/libigl/igl/flipped_triangles.h similarity index 100% rename from src/igl/flipped_triangles.h rename to src/libigl/igl/flipped_triangles.h diff --git a/src/igl/flood_fill.cpp b/src/libigl/igl/flood_fill.cpp similarity index 100% rename from src/igl/flood_fill.cpp rename to src/libigl/igl/flood_fill.cpp diff --git a/src/igl/flood_fill.h b/src/libigl/igl/flood_fill.h similarity index 100% rename from src/igl/flood_fill.h rename to src/libigl/igl/flood_fill.h diff --git a/src/igl/floor.cpp b/src/libigl/igl/floor.cpp similarity index 100% rename from src/igl/floor.cpp rename to src/libigl/igl/floor.cpp diff --git a/src/igl/floor.h b/src/libigl/igl/floor.h similarity index 100% rename from src/igl/floor.h rename to src/libigl/igl/floor.h diff --git a/src/igl/for_each.h b/src/libigl/igl/for_each.h similarity index 100% rename from src/igl/for_each.h rename to src/libigl/igl/for_each.h diff --git a/src/igl/forward_kinematics.cpp b/src/libigl/igl/forward_kinematics.cpp similarity index 100% rename from src/igl/forward_kinematics.cpp rename to src/libigl/igl/forward_kinematics.cpp diff --git a/src/igl/forward_kinematics.h b/src/libigl/igl/forward_kinematics.h similarity index 100% rename from src/igl/forward_kinematics.h rename to src/libigl/igl/forward_kinematics.h diff --git a/src/igl/frame_field_deformer.cpp b/src/libigl/igl/frame_field_deformer.cpp similarity index 100% rename from src/igl/frame_field_deformer.cpp rename to src/libigl/igl/frame_field_deformer.cpp diff --git a/src/igl/frame_field_deformer.h b/src/libigl/igl/frame_field_deformer.h similarity index 100% rename from src/igl/frame_field_deformer.h rename to src/libigl/igl/frame_field_deformer.h diff --git a/src/igl/frame_to_cross_field.cpp b/src/libigl/igl/frame_to_cross_field.cpp similarity index 100% rename from src/igl/frame_to_cross_field.cpp rename to src/libigl/igl/frame_to_cross_field.cpp diff --git a/src/igl/frame_to_cross_field.h b/src/libigl/igl/frame_to_cross_field.h similarity index 100% rename from src/igl/frame_to_cross_field.h rename to src/libigl/igl/frame_to_cross_field.h diff --git a/src/igl/frustum.cpp b/src/libigl/igl/frustum.cpp similarity index 100% rename from src/igl/frustum.cpp rename to src/libigl/igl/frustum.cpp diff --git a/src/igl/frustum.h b/src/libigl/igl/frustum.h similarity index 100% rename from src/igl/frustum.h rename to src/libigl/igl/frustum.h diff --git a/src/igl/gaussian_curvature.cpp b/src/libigl/igl/gaussian_curvature.cpp similarity index 100% rename from src/igl/gaussian_curvature.cpp rename to src/libigl/igl/gaussian_curvature.cpp diff --git a/src/igl/gaussian_curvature.h b/src/libigl/igl/gaussian_curvature.h similarity index 100% rename from src/igl/gaussian_curvature.h rename to src/libigl/igl/gaussian_curvature.h diff --git a/src/igl/get_seconds.cpp b/src/libigl/igl/get_seconds.cpp similarity index 100% rename from src/igl/get_seconds.cpp rename to src/libigl/igl/get_seconds.cpp diff --git a/src/igl/get_seconds.h b/src/libigl/igl/get_seconds.h similarity index 100% rename from src/igl/get_seconds.h rename to src/libigl/igl/get_seconds.h diff --git a/src/igl/get_seconds_hires.cpp b/src/libigl/igl/get_seconds_hires.cpp similarity index 100% rename from src/igl/get_seconds_hires.cpp rename to src/libigl/igl/get_seconds_hires.cpp diff --git a/src/igl/get_seconds_hires.h b/src/libigl/igl/get_seconds_hires.h similarity index 100% rename from src/igl/get_seconds_hires.h rename to src/libigl/igl/get_seconds_hires.h diff --git a/src/igl/grad.cpp b/src/libigl/igl/grad.cpp similarity index 100% rename from src/igl/grad.cpp rename to src/libigl/igl/grad.cpp diff --git a/src/igl/grad.h b/src/libigl/igl/grad.h similarity index 100% rename from src/igl/grad.h rename to src/libigl/igl/grad.h diff --git a/src/igl/grid.cpp b/src/libigl/igl/grid.cpp similarity index 100% rename from src/igl/grid.cpp rename to src/libigl/igl/grid.cpp diff --git a/src/igl/grid.h b/src/libigl/igl/grid.h similarity index 100% rename from src/igl/grid.h rename to src/libigl/igl/grid.h diff --git a/src/igl/grid_search.cpp b/src/libigl/igl/grid_search.cpp similarity index 100% rename from src/igl/grid_search.cpp rename to src/libigl/igl/grid_search.cpp diff --git a/src/igl/grid_search.h b/src/libigl/igl/grid_search.h similarity index 100% rename from src/igl/grid_search.h rename to src/libigl/igl/grid_search.h diff --git a/src/igl/group_sum_matrix.cpp b/src/libigl/igl/group_sum_matrix.cpp similarity index 100% rename from src/igl/group_sum_matrix.cpp rename to src/libigl/igl/group_sum_matrix.cpp diff --git a/src/igl/group_sum_matrix.h b/src/libigl/igl/group_sum_matrix.h similarity index 100% rename from src/igl/group_sum_matrix.h rename to src/libigl/igl/group_sum_matrix.h diff --git a/src/igl/guess_extension.cpp b/src/libigl/igl/guess_extension.cpp similarity index 100% rename from src/igl/guess_extension.cpp rename to src/libigl/igl/guess_extension.cpp diff --git a/src/igl/guess_extension.h b/src/libigl/igl/guess_extension.h similarity index 100% rename from src/igl/guess_extension.h rename to src/libigl/igl/guess_extension.h diff --git a/src/igl/harmonic.cpp b/src/libigl/igl/harmonic.cpp similarity index 100% rename from src/igl/harmonic.cpp rename to src/libigl/igl/harmonic.cpp diff --git a/src/igl/harmonic.h b/src/libigl/igl/harmonic.h similarity index 100% rename from src/igl/harmonic.h rename to src/libigl/igl/harmonic.h diff --git a/src/igl/harwell_boeing.cpp b/src/libigl/igl/harwell_boeing.cpp similarity index 100% rename from src/igl/harwell_boeing.cpp rename to src/libigl/igl/harwell_boeing.cpp diff --git a/src/igl/harwell_boeing.h b/src/libigl/igl/harwell_boeing.h similarity index 100% rename from src/igl/harwell_boeing.h rename to src/libigl/igl/harwell_boeing.h diff --git a/src/igl/hausdorff.cpp b/src/libigl/igl/hausdorff.cpp similarity index 100% rename from src/igl/hausdorff.cpp rename to src/libigl/igl/hausdorff.cpp diff --git a/src/igl/hausdorff.h b/src/libigl/igl/hausdorff.h similarity index 100% rename from src/igl/hausdorff.h rename to src/libigl/igl/hausdorff.h diff --git a/src/igl/hessian.cpp b/src/libigl/igl/hessian.cpp similarity index 100% rename from src/igl/hessian.cpp rename to src/libigl/igl/hessian.cpp diff --git a/src/igl/hessian.h b/src/libigl/igl/hessian.h similarity index 100% rename from src/igl/hessian.h rename to src/libigl/igl/hessian.h diff --git a/src/igl/hessian_energy.cpp b/src/libigl/igl/hessian_energy.cpp similarity index 100% rename from src/igl/hessian_energy.cpp rename to src/libigl/igl/hessian_energy.cpp diff --git a/src/igl/hessian_energy.h b/src/libigl/igl/hessian_energy.h similarity index 100% rename from src/igl/hessian_energy.h rename to src/libigl/igl/hessian_energy.h diff --git a/src/igl/histc.cpp b/src/libigl/igl/histc.cpp similarity index 100% rename from src/igl/histc.cpp rename to src/libigl/igl/histc.cpp diff --git a/src/igl/histc.h b/src/libigl/igl/histc.h similarity index 100% rename from src/igl/histc.h rename to src/libigl/igl/histc.h diff --git a/src/igl/hsv_to_rgb.cpp b/src/libigl/igl/hsv_to_rgb.cpp similarity index 100% rename from src/igl/hsv_to_rgb.cpp rename to src/libigl/igl/hsv_to_rgb.cpp diff --git a/src/igl/hsv_to_rgb.h b/src/libigl/igl/hsv_to_rgb.h similarity index 100% rename from src/igl/hsv_to_rgb.h rename to src/libigl/igl/hsv_to_rgb.h diff --git a/src/igl/igl_inline.h b/src/libigl/igl/igl_inline.h similarity index 100% rename from src/igl/igl_inline.h rename to src/libigl/igl/igl_inline.h diff --git a/src/igl/in_element.cpp b/src/libigl/igl/in_element.cpp similarity index 100% rename from src/igl/in_element.cpp rename to src/libigl/igl/in_element.cpp diff --git a/src/igl/in_element.h b/src/libigl/igl/in_element.h similarity index 100% rename from src/igl/in_element.h rename to src/libigl/igl/in_element.h diff --git a/src/igl/infinite_cost_stopping_condition.cpp b/src/libigl/igl/infinite_cost_stopping_condition.cpp similarity index 100% rename from src/igl/infinite_cost_stopping_condition.cpp rename to src/libigl/igl/infinite_cost_stopping_condition.cpp diff --git a/src/igl/infinite_cost_stopping_condition.h b/src/libigl/igl/infinite_cost_stopping_condition.h similarity index 100% rename from src/igl/infinite_cost_stopping_condition.h rename to src/libigl/igl/infinite_cost_stopping_condition.h diff --git a/src/igl/inradius.cpp b/src/libigl/igl/inradius.cpp similarity index 100% rename from src/igl/inradius.cpp rename to src/libigl/igl/inradius.cpp diff --git a/src/igl/inradius.h b/src/libigl/igl/inradius.h similarity index 100% rename from src/igl/inradius.h rename to src/libigl/igl/inradius.h diff --git a/src/igl/internal_angles.cpp b/src/libigl/igl/internal_angles.cpp similarity index 100% rename from src/igl/internal_angles.cpp rename to src/libigl/igl/internal_angles.cpp diff --git a/src/igl/internal_angles.h b/src/libigl/igl/internal_angles.h similarity index 100% rename from src/igl/internal_angles.h rename to src/libigl/igl/internal_angles.h diff --git a/src/igl/intersect.cpp b/src/libigl/igl/intersect.cpp similarity index 100% rename from src/igl/intersect.cpp rename to src/libigl/igl/intersect.cpp diff --git a/src/igl/intersect.h b/src/libigl/igl/intersect.h similarity index 100% rename from src/igl/intersect.h rename to src/libigl/igl/intersect.h diff --git a/src/igl/invert_diag.cpp b/src/libigl/igl/invert_diag.cpp similarity index 100% rename from src/igl/invert_diag.cpp rename to src/libigl/igl/invert_diag.cpp diff --git a/src/igl/invert_diag.h b/src/libigl/igl/invert_diag.h similarity index 100% rename from src/igl/invert_diag.h rename to src/libigl/igl/invert_diag.h diff --git a/src/igl/is_border_vertex.cpp b/src/libigl/igl/is_border_vertex.cpp similarity index 100% rename from src/igl/is_border_vertex.cpp rename to src/libigl/igl/is_border_vertex.cpp diff --git a/src/igl/is_border_vertex.h b/src/libigl/igl/is_border_vertex.h similarity index 100% rename from src/igl/is_border_vertex.h rename to src/libigl/igl/is_border_vertex.h diff --git a/src/igl/is_boundary_edge.cpp b/src/libigl/igl/is_boundary_edge.cpp similarity index 100% rename from src/igl/is_boundary_edge.cpp rename to src/libigl/igl/is_boundary_edge.cpp diff --git a/src/igl/is_boundary_edge.h b/src/libigl/igl/is_boundary_edge.h similarity index 100% rename from src/igl/is_boundary_edge.h rename to src/libigl/igl/is_boundary_edge.h diff --git a/src/igl/is_dir.cpp b/src/libigl/igl/is_dir.cpp similarity index 100% rename from src/igl/is_dir.cpp rename to src/libigl/igl/is_dir.cpp diff --git a/src/igl/is_dir.h b/src/libigl/igl/is_dir.h similarity index 100% rename from src/igl/is_dir.h rename to src/libigl/igl/is_dir.h diff --git a/src/igl/is_edge_manifold.cpp b/src/libigl/igl/is_edge_manifold.cpp similarity index 100% rename from src/igl/is_edge_manifold.cpp rename to src/libigl/igl/is_edge_manifold.cpp diff --git a/src/igl/is_edge_manifold.h b/src/libigl/igl/is_edge_manifold.h similarity index 100% rename from src/igl/is_edge_manifold.h rename to src/libigl/igl/is_edge_manifold.h diff --git a/src/igl/is_file.cpp b/src/libigl/igl/is_file.cpp similarity index 100% rename from src/igl/is_file.cpp rename to src/libigl/igl/is_file.cpp diff --git a/src/igl/is_file.h b/src/libigl/igl/is_file.h similarity index 100% rename from src/igl/is_file.h rename to src/libigl/igl/is_file.h diff --git a/src/igl/is_irregular_vertex.cpp b/src/libigl/igl/is_irregular_vertex.cpp similarity index 100% rename from src/igl/is_irregular_vertex.cpp rename to src/libigl/igl/is_irregular_vertex.cpp diff --git a/src/igl/is_irregular_vertex.h b/src/libigl/igl/is_irregular_vertex.h similarity index 100% rename from src/igl/is_irregular_vertex.h rename to src/libigl/igl/is_irregular_vertex.h diff --git a/src/igl/is_planar.cpp b/src/libigl/igl/is_planar.cpp similarity index 100% rename from src/igl/is_planar.cpp rename to src/libigl/igl/is_planar.cpp diff --git a/src/igl/is_planar.h b/src/libigl/igl/is_planar.h similarity index 100% rename from src/igl/is_planar.h rename to src/libigl/igl/is_planar.h diff --git a/src/igl/is_readable.cpp b/src/libigl/igl/is_readable.cpp similarity index 100% rename from src/igl/is_readable.cpp rename to src/libigl/igl/is_readable.cpp diff --git a/src/igl/is_readable.h b/src/libigl/igl/is_readable.h similarity index 100% rename from src/igl/is_readable.h rename to src/libigl/igl/is_readable.h diff --git a/src/igl/is_sparse.cpp b/src/libigl/igl/is_sparse.cpp similarity index 100% rename from src/igl/is_sparse.cpp rename to src/libigl/igl/is_sparse.cpp diff --git a/src/igl/is_sparse.h b/src/libigl/igl/is_sparse.h similarity index 100% rename from src/igl/is_sparse.h rename to src/libigl/igl/is_sparse.h diff --git a/src/igl/is_stl.cpp b/src/libigl/igl/is_stl.cpp similarity index 100% rename from src/igl/is_stl.cpp rename to src/libigl/igl/is_stl.cpp diff --git a/src/igl/is_stl.h b/src/libigl/igl/is_stl.h similarity index 100% rename from src/igl/is_stl.h rename to src/libigl/igl/is_stl.h diff --git a/src/igl/is_symmetric.cpp b/src/libigl/igl/is_symmetric.cpp similarity index 100% rename from src/igl/is_symmetric.cpp rename to src/libigl/igl/is_symmetric.cpp diff --git a/src/igl/is_symmetric.h b/src/libigl/igl/is_symmetric.h similarity index 100% rename from src/igl/is_symmetric.h rename to src/libigl/igl/is_symmetric.h diff --git a/src/igl/is_vertex_manifold.cpp b/src/libigl/igl/is_vertex_manifold.cpp similarity index 100% rename from src/igl/is_vertex_manifold.cpp rename to src/libigl/igl/is_vertex_manifold.cpp diff --git a/src/igl/is_vertex_manifold.h b/src/libigl/igl/is_vertex_manifold.h similarity index 100% rename from src/igl/is_vertex_manifold.h rename to src/libigl/igl/is_vertex_manifold.h diff --git a/src/igl/is_writable.cpp b/src/libigl/igl/is_writable.cpp similarity index 100% rename from src/igl/is_writable.cpp rename to src/libigl/igl/is_writable.cpp diff --git a/src/igl/is_writable.h b/src/libigl/igl/is_writable.h similarity index 100% rename from src/igl/is_writable.h rename to src/libigl/igl/is_writable.h diff --git a/src/igl/isdiag.cpp b/src/libigl/igl/isdiag.cpp similarity index 100% rename from src/igl/isdiag.cpp rename to src/libigl/igl/isdiag.cpp diff --git a/src/igl/isdiag.h b/src/libigl/igl/isdiag.h similarity index 100% rename from src/igl/isdiag.h rename to src/libigl/igl/isdiag.h diff --git a/src/igl/ismember.cpp b/src/libigl/igl/ismember.cpp similarity index 100% rename from src/igl/ismember.cpp rename to src/libigl/igl/ismember.cpp diff --git a/src/igl/ismember.h b/src/libigl/igl/ismember.h similarity index 100% rename from src/igl/ismember.h rename to src/libigl/igl/ismember.h diff --git a/src/igl/isolines.cpp b/src/libigl/igl/isolines.cpp similarity index 100% rename from src/igl/isolines.cpp rename to src/libigl/igl/isolines.cpp diff --git a/src/igl/isolines.h b/src/libigl/igl/isolines.h similarity index 100% rename from src/igl/isolines.h rename to src/libigl/igl/isolines.h diff --git a/src/igl/jet.cpp b/src/libigl/igl/jet.cpp similarity index 100% rename from src/igl/jet.cpp rename to src/libigl/igl/jet.cpp diff --git a/src/igl/jet.h b/src/libigl/igl/jet.h similarity index 100% rename from src/igl/jet.h rename to src/libigl/igl/jet.h diff --git a/src/igl/knn.cpp b/src/libigl/igl/knn.cpp similarity index 100% rename from src/igl/knn.cpp rename to src/libigl/igl/knn.cpp diff --git a/src/igl/knn.h b/src/libigl/igl/knn.h similarity index 100% rename from src/igl/knn.h rename to src/libigl/igl/knn.h diff --git a/src/igl/launch_medit.cpp b/src/libigl/igl/launch_medit.cpp similarity index 100% rename from src/igl/launch_medit.cpp rename to src/libigl/igl/launch_medit.cpp diff --git a/src/igl/launch_medit.h b/src/libigl/igl/launch_medit.h similarity index 100% rename from src/igl/launch_medit.h rename to src/libigl/igl/launch_medit.h diff --git a/src/igl/lbs_matrix.cpp b/src/libigl/igl/lbs_matrix.cpp similarity index 100% rename from src/igl/lbs_matrix.cpp rename to src/libigl/igl/lbs_matrix.cpp diff --git a/src/igl/lbs_matrix.h b/src/libigl/igl/lbs_matrix.h similarity index 100% rename from src/igl/lbs_matrix.h rename to src/libigl/igl/lbs_matrix.h diff --git a/src/igl/lexicographic_triangulation.cpp b/src/libigl/igl/lexicographic_triangulation.cpp similarity index 100% rename from src/igl/lexicographic_triangulation.cpp rename to src/libigl/igl/lexicographic_triangulation.cpp diff --git a/src/igl/lexicographic_triangulation.h b/src/libigl/igl/lexicographic_triangulation.h similarity index 100% rename from src/igl/lexicographic_triangulation.h rename to src/libigl/igl/lexicographic_triangulation.h diff --git a/src/igl/lim/lim.cpp b/src/libigl/igl/lim/lim.cpp similarity index 100% rename from src/igl/lim/lim.cpp rename to src/libigl/igl/lim/lim.cpp diff --git a/src/igl/lim/lim.h b/src/libigl/igl/lim/lim.h similarity index 100% rename from src/igl/lim/lim.h rename to src/libigl/igl/lim/lim.h diff --git a/src/igl/limit_faces.cpp b/src/libigl/igl/limit_faces.cpp similarity index 100% rename from src/igl/limit_faces.cpp rename to src/libigl/igl/limit_faces.cpp diff --git a/src/igl/limit_faces.h b/src/libigl/igl/limit_faces.h similarity index 100% rename from src/igl/limit_faces.h rename to src/libigl/igl/limit_faces.h diff --git a/src/igl/line_field_missmatch.cpp b/src/libigl/igl/line_field_missmatch.cpp similarity index 100% rename from src/igl/line_field_missmatch.cpp rename to src/libigl/igl/line_field_missmatch.cpp diff --git a/src/igl/line_field_missmatch.h b/src/libigl/igl/line_field_missmatch.h similarity index 100% rename from src/igl/line_field_missmatch.h rename to src/libigl/igl/line_field_missmatch.h diff --git a/src/igl/line_search.cpp b/src/libigl/igl/line_search.cpp similarity index 100% rename from src/igl/line_search.cpp rename to src/libigl/igl/line_search.cpp diff --git a/src/igl/line_search.h b/src/libigl/igl/line_search.h similarity index 100% rename from src/igl/line_search.h rename to src/libigl/igl/line_search.h diff --git a/src/igl/line_segment_in_rectangle.cpp b/src/libigl/igl/line_segment_in_rectangle.cpp similarity index 100% rename from src/igl/line_segment_in_rectangle.cpp rename to src/libigl/igl/line_segment_in_rectangle.cpp diff --git a/src/igl/line_segment_in_rectangle.h b/src/libigl/igl/line_segment_in_rectangle.h similarity index 100% rename from src/igl/line_segment_in_rectangle.h rename to src/libigl/igl/line_segment_in_rectangle.h diff --git a/src/igl/linprog.cpp b/src/libigl/igl/linprog.cpp similarity index 100% rename from src/igl/linprog.cpp rename to src/libigl/igl/linprog.cpp diff --git a/src/igl/linprog.h b/src/libigl/igl/linprog.h similarity index 100% rename from src/igl/linprog.h rename to src/libigl/igl/linprog.h diff --git a/src/igl/list_to_matrix.cpp b/src/libigl/igl/list_to_matrix.cpp similarity index 100% rename from src/igl/list_to_matrix.cpp rename to src/libigl/igl/list_to_matrix.cpp diff --git a/src/igl/list_to_matrix.h b/src/libigl/igl/list_to_matrix.h similarity index 100% rename from src/igl/list_to_matrix.h rename to src/libigl/igl/list_to_matrix.h diff --git a/src/igl/local_basis.cpp b/src/libigl/igl/local_basis.cpp similarity index 100% rename from src/igl/local_basis.cpp rename to src/libigl/igl/local_basis.cpp diff --git a/src/igl/local_basis.h b/src/libigl/igl/local_basis.h similarity index 100% rename from src/igl/local_basis.h rename to src/libigl/igl/local_basis.h diff --git a/src/igl/look_at.cpp b/src/libigl/igl/look_at.cpp similarity index 100% rename from src/igl/look_at.cpp rename to src/libigl/igl/look_at.cpp diff --git a/src/igl/look_at.h b/src/libigl/igl/look_at.h similarity index 100% rename from src/igl/look_at.h rename to src/libigl/igl/look_at.h diff --git a/src/igl/loop.cpp b/src/libigl/igl/loop.cpp similarity index 100% rename from src/igl/loop.cpp rename to src/libigl/igl/loop.cpp diff --git a/src/igl/loop.h b/src/libigl/igl/loop.h similarity index 100% rename from src/igl/loop.h rename to src/libigl/igl/loop.h diff --git a/src/igl/lscm.cpp b/src/libigl/igl/lscm.cpp similarity index 100% rename from src/igl/lscm.cpp rename to src/libigl/igl/lscm.cpp diff --git a/src/igl/lscm.h b/src/libigl/igl/lscm.h similarity index 100% rename from src/igl/lscm.h rename to src/libigl/igl/lscm.h diff --git a/src/igl/map_vertices_to_circle.cpp b/src/libigl/igl/map_vertices_to_circle.cpp similarity index 100% rename from src/igl/map_vertices_to_circle.cpp rename to src/libigl/igl/map_vertices_to_circle.cpp diff --git a/src/igl/map_vertices_to_circle.h b/src/libigl/igl/map_vertices_to_circle.h similarity index 100% rename from src/igl/map_vertices_to_circle.h rename to src/libigl/igl/map_vertices_to_circle.h diff --git a/src/igl/massmatrix.cpp b/src/libigl/igl/massmatrix.cpp similarity index 100% rename from src/igl/massmatrix.cpp rename to src/libigl/igl/massmatrix.cpp diff --git a/src/igl/massmatrix.h b/src/libigl/igl/massmatrix.h similarity index 100% rename from src/igl/massmatrix.h rename to src/libigl/igl/massmatrix.h diff --git a/src/igl/mat_max.cpp b/src/libigl/igl/mat_max.cpp similarity index 100% rename from src/igl/mat_max.cpp rename to src/libigl/igl/mat_max.cpp diff --git a/src/igl/mat_max.h b/src/libigl/igl/mat_max.h similarity index 100% rename from src/igl/mat_max.h rename to src/libigl/igl/mat_max.h diff --git a/src/igl/mat_min.cpp b/src/libigl/igl/mat_min.cpp similarity index 100% rename from src/igl/mat_min.cpp rename to src/libigl/igl/mat_min.cpp diff --git a/src/igl/mat_min.h b/src/libigl/igl/mat_min.h similarity index 100% rename from src/igl/mat_min.h rename to src/libigl/igl/mat_min.h diff --git a/src/igl/mat_to_quat.cpp b/src/libigl/igl/mat_to_quat.cpp similarity index 100% rename from src/igl/mat_to_quat.cpp rename to src/libigl/igl/mat_to_quat.cpp diff --git a/src/igl/mat_to_quat.h b/src/libigl/igl/mat_to_quat.h similarity index 100% rename from src/igl/mat_to_quat.h rename to src/libigl/igl/mat_to_quat.h diff --git a/src/igl/material_colors.h b/src/libigl/igl/material_colors.h similarity index 100% rename from src/igl/material_colors.h rename to src/libigl/igl/material_colors.h diff --git a/src/igl/matlab/MatlabWorkspace.h b/src/libigl/igl/matlab/MatlabWorkspace.h similarity index 100% rename from src/igl/matlab/MatlabWorkspace.h rename to src/libigl/igl/matlab/MatlabWorkspace.h diff --git a/src/igl/matlab/MexStream.h b/src/libigl/igl/matlab/MexStream.h similarity index 100% rename from src/igl/matlab/MexStream.h rename to src/libigl/igl/matlab/MexStream.h diff --git a/src/igl/matlab/matlabinterface.cpp b/src/libigl/igl/matlab/matlabinterface.cpp similarity index 100% rename from src/igl/matlab/matlabinterface.cpp rename to src/libigl/igl/matlab/matlabinterface.cpp diff --git a/src/igl/matlab/matlabinterface.h b/src/libigl/igl/matlab/matlabinterface.h similarity index 100% rename from src/igl/matlab/matlabinterface.h rename to src/libigl/igl/matlab/matlabinterface.h diff --git a/src/igl/matlab/mexErrMsgTxt.cpp b/src/libigl/igl/matlab/mexErrMsgTxt.cpp similarity index 100% rename from src/igl/matlab/mexErrMsgTxt.cpp rename to src/libigl/igl/matlab/mexErrMsgTxt.cpp diff --git a/src/igl/matlab/mexErrMsgTxt.h b/src/libigl/igl/matlab/mexErrMsgTxt.h similarity index 100% rename from src/igl/matlab/mexErrMsgTxt.h rename to src/libigl/igl/matlab/mexErrMsgTxt.h diff --git a/src/igl/matlab/parse_rhs.cpp b/src/libigl/igl/matlab/parse_rhs.cpp similarity index 100% rename from src/igl/matlab/parse_rhs.cpp rename to src/libigl/igl/matlab/parse_rhs.cpp diff --git a/src/igl/matlab/parse_rhs.h b/src/libigl/igl/matlab/parse_rhs.h similarity index 100% rename from src/igl/matlab/parse_rhs.h rename to src/libigl/igl/matlab/parse_rhs.h diff --git a/src/igl/matlab/prepare_lhs.cpp b/src/libigl/igl/matlab/prepare_lhs.cpp similarity index 100% rename from src/igl/matlab/prepare_lhs.cpp rename to src/libigl/igl/matlab/prepare_lhs.cpp diff --git a/src/igl/matlab/prepare_lhs.h b/src/libigl/igl/matlab/prepare_lhs.h similarity index 100% rename from src/igl/matlab/prepare_lhs.h rename to src/libigl/igl/matlab/prepare_lhs.h diff --git a/src/igl/matlab/requires_arg.cpp b/src/libigl/igl/matlab/requires_arg.cpp similarity index 100% rename from src/igl/matlab/requires_arg.cpp rename to src/libigl/igl/matlab/requires_arg.cpp diff --git a/src/igl/matlab/requires_arg.h b/src/libigl/igl/matlab/requires_arg.h similarity index 100% rename from src/igl/matlab/requires_arg.h rename to src/libigl/igl/matlab/requires_arg.h diff --git a/src/igl/matlab/validate_arg.cpp b/src/libigl/igl/matlab/validate_arg.cpp similarity index 100% rename from src/igl/matlab/validate_arg.cpp rename to src/libigl/igl/matlab/validate_arg.cpp diff --git a/src/igl/matlab/validate_arg.h b/src/libigl/igl/matlab/validate_arg.h similarity index 100% rename from src/igl/matlab/validate_arg.h rename to src/libigl/igl/matlab/validate_arg.h diff --git a/src/igl/matlab_format.cpp b/src/libigl/igl/matlab_format.cpp similarity index 100% rename from src/igl/matlab_format.cpp rename to src/libigl/igl/matlab_format.cpp diff --git a/src/igl/matlab_format.h b/src/libigl/igl/matlab_format.h similarity index 100% rename from src/igl/matlab_format.h rename to src/libigl/igl/matlab_format.h diff --git a/src/igl/matrix_to_list.cpp b/src/libigl/igl/matrix_to_list.cpp similarity index 100% rename from src/igl/matrix_to_list.cpp rename to src/libigl/igl/matrix_to_list.cpp diff --git a/src/igl/matrix_to_list.h b/src/libigl/igl/matrix_to_list.h similarity index 100% rename from src/igl/matrix_to_list.h rename to src/libigl/igl/matrix_to_list.h diff --git a/src/igl/max.cpp b/src/libigl/igl/max.cpp similarity index 100% rename from src/igl/max.cpp rename to src/libigl/igl/max.cpp diff --git a/src/igl/max.h b/src/libigl/igl/max.h similarity index 100% rename from src/igl/max.h rename to src/libigl/igl/max.h diff --git a/src/igl/max_faces_stopping_condition.cpp b/src/libigl/igl/max_faces_stopping_condition.cpp similarity index 100% rename from src/igl/max_faces_stopping_condition.cpp rename to src/libigl/igl/max_faces_stopping_condition.cpp diff --git a/src/igl/max_faces_stopping_condition.h b/src/libigl/igl/max_faces_stopping_condition.h similarity index 100% rename from src/igl/max_faces_stopping_condition.h rename to src/libigl/igl/max_faces_stopping_condition.h diff --git a/src/igl/max_size.cpp b/src/libigl/igl/max_size.cpp similarity index 100% rename from src/igl/max_size.cpp rename to src/libigl/igl/max_size.cpp diff --git a/src/igl/max_size.h b/src/libigl/igl/max_size.h similarity index 100% rename from src/igl/max_size.h rename to src/libigl/igl/max_size.h diff --git a/src/igl/median.cpp b/src/libigl/igl/median.cpp similarity index 100% rename from src/igl/median.cpp rename to src/libigl/igl/median.cpp diff --git a/src/igl/median.h b/src/libigl/igl/median.h similarity index 100% rename from src/igl/median.h rename to src/libigl/igl/median.h diff --git a/src/igl/min.cpp b/src/libigl/igl/min.cpp similarity index 100% rename from src/igl/min.cpp rename to src/libigl/igl/min.cpp diff --git a/src/igl/min.h b/src/libigl/igl/min.h similarity index 100% rename from src/igl/min.h rename to src/libigl/igl/min.h diff --git a/src/igl/min_quad_dense.cpp b/src/libigl/igl/min_quad_dense.cpp similarity index 100% rename from src/igl/min_quad_dense.cpp rename to src/libigl/igl/min_quad_dense.cpp diff --git a/src/igl/min_quad_dense.h b/src/libigl/igl/min_quad_dense.h similarity index 100% rename from src/igl/min_quad_dense.h rename to src/libigl/igl/min_quad_dense.h diff --git a/src/igl/min_quad_with_fixed.cpp b/src/libigl/igl/min_quad_with_fixed.cpp similarity index 100% rename from src/igl/min_quad_with_fixed.cpp rename to src/libigl/igl/min_quad_with_fixed.cpp diff --git a/src/igl/min_quad_with_fixed.h b/src/libigl/igl/min_quad_with_fixed.h similarity index 100% rename from src/igl/min_quad_with_fixed.h rename to src/libigl/igl/min_quad_with_fixed.h diff --git a/src/igl/min_size.cpp b/src/libigl/igl/min_size.cpp similarity index 100% rename from src/igl/min_size.cpp rename to src/libigl/igl/min_size.cpp diff --git a/src/igl/min_size.h b/src/libigl/igl/min_size.h similarity index 100% rename from src/igl/min_size.h rename to src/libigl/igl/min_size.h diff --git a/src/igl/mod.cpp b/src/libigl/igl/mod.cpp similarity index 100% rename from src/igl/mod.cpp rename to src/libigl/igl/mod.cpp diff --git a/src/igl/mod.h b/src/libigl/igl/mod.h similarity index 100% rename from src/igl/mod.h rename to src/libigl/igl/mod.h diff --git a/src/igl/mode.cpp b/src/libigl/igl/mode.cpp similarity index 100% rename from src/igl/mode.cpp rename to src/libigl/igl/mode.cpp diff --git a/src/igl/mode.h b/src/libigl/igl/mode.h similarity index 100% rename from src/igl/mode.h rename to src/libigl/igl/mode.h diff --git a/src/igl/mosek/bbw.cpp b/src/libigl/igl/mosek/bbw.cpp similarity index 100% rename from src/igl/mosek/bbw.cpp rename to src/libigl/igl/mosek/bbw.cpp diff --git a/src/igl/mosek/bbw.h b/src/libigl/igl/mosek/bbw.h similarity index 100% rename from src/igl/mosek/bbw.h rename to src/libigl/igl/mosek/bbw.h diff --git a/src/igl/mosek/mosek_guarded.cpp b/src/libigl/igl/mosek/mosek_guarded.cpp similarity index 100% rename from src/igl/mosek/mosek_guarded.cpp rename to src/libigl/igl/mosek/mosek_guarded.cpp diff --git a/src/igl/mosek/mosek_guarded.h b/src/libigl/igl/mosek/mosek_guarded.h similarity index 100% rename from src/igl/mosek/mosek_guarded.h rename to src/libigl/igl/mosek/mosek_guarded.h diff --git a/src/igl/mosek/mosek_linprog.cpp b/src/libigl/igl/mosek/mosek_linprog.cpp similarity index 100% rename from src/igl/mosek/mosek_linprog.cpp rename to src/libigl/igl/mosek/mosek_linprog.cpp diff --git a/src/igl/mosek/mosek_linprog.h b/src/libigl/igl/mosek/mosek_linprog.h similarity index 100% rename from src/igl/mosek/mosek_linprog.h rename to src/libigl/igl/mosek/mosek_linprog.h diff --git a/src/igl/mosek/mosek_quadprog.cpp b/src/libigl/igl/mosek/mosek_quadprog.cpp similarity index 100% rename from src/igl/mosek/mosek_quadprog.cpp rename to src/libigl/igl/mosek/mosek_quadprog.cpp diff --git a/src/igl/mosek/mosek_quadprog.h b/src/libigl/igl/mosek/mosek_quadprog.h similarity index 100% rename from src/igl/mosek/mosek_quadprog.h rename to src/libigl/igl/mosek/mosek_quadprog.h diff --git a/src/igl/mvc.cpp b/src/libigl/igl/mvc.cpp similarity index 100% rename from src/igl/mvc.cpp rename to src/libigl/igl/mvc.cpp diff --git a/src/igl/mvc.h b/src/libigl/igl/mvc.h similarity index 100% rename from src/igl/mvc.h rename to src/libigl/igl/mvc.h diff --git a/src/igl/nchoosek.cpp b/src/libigl/igl/nchoosek.cpp similarity index 100% rename from src/igl/nchoosek.cpp rename to src/libigl/igl/nchoosek.cpp diff --git a/src/igl/nchoosek.h b/src/libigl/igl/nchoosek.h similarity index 100% rename from src/igl/nchoosek.h rename to src/libigl/igl/nchoosek.h diff --git a/src/igl/next_filename.cpp b/src/libigl/igl/next_filename.cpp similarity index 100% rename from src/igl/next_filename.cpp rename to src/libigl/igl/next_filename.cpp diff --git a/src/igl/next_filename.h b/src/libigl/igl/next_filename.h similarity index 100% rename from src/igl/next_filename.h rename to src/libigl/igl/next_filename.h diff --git a/src/igl/normal_derivative.cpp b/src/libigl/igl/normal_derivative.cpp similarity index 100% rename from src/igl/normal_derivative.cpp rename to src/libigl/igl/normal_derivative.cpp diff --git a/src/igl/normal_derivative.h b/src/libigl/igl/normal_derivative.h similarity index 100% rename from src/igl/normal_derivative.h rename to src/libigl/igl/normal_derivative.h diff --git a/src/igl/normalize_quat.cpp b/src/libigl/igl/normalize_quat.cpp similarity index 100% rename from src/igl/normalize_quat.cpp rename to src/libigl/igl/normalize_quat.cpp diff --git a/src/igl/normalize_quat.h b/src/libigl/igl/normalize_quat.h similarity index 100% rename from src/igl/normalize_quat.h rename to src/libigl/igl/normalize_quat.h diff --git a/src/igl/normalize_row_lengths.cpp b/src/libigl/igl/normalize_row_lengths.cpp similarity index 100% rename from src/igl/normalize_row_lengths.cpp rename to src/libigl/igl/normalize_row_lengths.cpp diff --git a/src/igl/normalize_row_lengths.h b/src/libigl/igl/normalize_row_lengths.h similarity index 100% rename from src/igl/normalize_row_lengths.h rename to src/libigl/igl/normalize_row_lengths.h diff --git a/src/igl/normalize_row_sums.cpp b/src/libigl/igl/normalize_row_sums.cpp similarity index 100% rename from src/igl/normalize_row_sums.cpp rename to src/libigl/igl/normalize_row_sums.cpp diff --git a/src/igl/normalize_row_sums.h b/src/libigl/igl/normalize_row_sums.h similarity index 100% rename from src/igl/normalize_row_sums.h rename to src/libigl/igl/normalize_row_sums.h diff --git a/src/igl/null.cpp b/src/libigl/igl/null.cpp similarity index 100% rename from src/igl/null.cpp rename to src/libigl/igl/null.cpp diff --git a/src/igl/null.h b/src/libigl/igl/null.h similarity index 100% rename from src/igl/null.h rename to src/libigl/igl/null.h diff --git a/src/igl/octree.cpp b/src/libigl/igl/octree.cpp similarity index 100% rename from src/igl/octree.cpp rename to src/libigl/igl/octree.cpp diff --git a/src/igl/octree.h b/src/libigl/igl/octree.h similarity index 100% rename from src/igl/octree.h rename to src/libigl/igl/octree.h diff --git a/src/igl/on_boundary.cpp b/src/libigl/igl/on_boundary.cpp similarity index 100% rename from src/igl/on_boundary.cpp rename to src/libigl/igl/on_boundary.cpp diff --git a/src/igl/on_boundary.h b/src/libigl/igl/on_boundary.h similarity index 100% rename from src/igl/on_boundary.h rename to src/libigl/igl/on_boundary.h diff --git a/src/igl/opengl/MeshGL.cpp b/src/libigl/igl/opengl/MeshGL.cpp similarity index 100% rename from src/igl/opengl/MeshGL.cpp rename to src/libigl/igl/opengl/MeshGL.cpp diff --git a/src/igl/opengl/MeshGL.h b/src/libigl/igl/opengl/MeshGL.h similarity index 100% rename from src/igl/opengl/MeshGL.h rename to src/libigl/igl/opengl/MeshGL.h diff --git a/src/igl/opengl/ViewerCore.cpp b/src/libigl/igl/opengl/ViewerCore.cpp similarity index 100% rename from src/igl/opengl/ViewerCore.cpp rename to src/libigl/igl/opengl/ViewerCore.cpp diff --git a/src/igl/opengl/ViewerCore.h b/src/libigl/igl/opengl/ViewerCore.h similarity index 100% rename from src/igl/opengl/ViewerCore.h rename to src/libigl/igl/opengl/ViewerCore.h diff --git a/src/igl/opengl/ViewerData.cpp b/src/libigl/igl/opengl/ViewerData.cpp similarity index 100% rename from src/igl/opengl/ViewerData.cpp rename to src/libigl/igl/opengl/ViewerData.cpp diff --git a/src/igl/opengl/ViewerData.h b/src/libigl/igl/opengl/ViewerData.h similarity index 100% rename from src/igl/opengl/ViewerData.h rename to src/libigl/igl/opengl/ViewerData.h diff --git a/src/igl/opengl/bind_vertex_attrib_array.cpp b/src/libigl/igl/opengl/bind_vertex_attrib_array.cpp similarity index 100% rename from src/igl/opengl/bind_vertex_attrib_array.cpp rename to src/libigl/igl/opengl/bind_vertex_attrib_array.cpp diff --git a/src/igl/opengl/bind_vertex_attrib_array.h b/src/libigl/igl/opengl/bind_vertex_attrib_array.h similarity index 100% rename from src/igl/opengl/bind_vertex_attrib_array.h rename to src/libigl/igl/opengl/bind_vertex_attrib_array.h diff --git a/src/igl/opengl/create_index_vbo.cpp b/src/libigl/igl/opengl/create_index_vbo.cpp similarity index 100% rename from src/igl/opengl/create_index_vbo.cpp rename to src/libigl/igl/opengl/create_index_vbo.cpp diff --git a/src/igl/opengl/create_index_vbo.h b/src/libigl/igl/opengl/create_index_vbo.h similarity index 100% rename from src/igl/opengl/create_index_vbo.h rename to src/libigl/igl/opengl/create_index_vbo.h diff --git a/src/igl/opengl/create_mesh_vbo.cpp b/src/libigl/igl/opengl/create_mesh_vbo.cpp similarity index 100% rename from src/igl/opengl/create_mesh_vbo.cpp rename to src/libigl/igl/opengl/create_mesh_vbo.cpp diff --git a/src/igl/opengl/create_mesh_vbo.h b/src/libigl/igl/opengl/create_mesh_vbo.h similarity index 100% rename from src/igl/opengl/create_mesh_vbo.h rename to src/libigl/igl/opengl/create_mesh_vbo.h diff --git a/src/igl/opengl/create_shader_program.cpp b/src/libigl/igl/opengl/create_shader_program.cpp similarity index 100% rename from src/igl/opengl/create_shader_program.cpp rename to src/libigl/igl/opengl/create_shader_program.cpp diff --git a/src/igl/opengl/create_shader_program.h b/src/libigl/igl/opengl/create_shader_program.h similarity index 100% rename from src/igl/opengl/create_shader_program.h rename to src/libigl/igl/opengl/create_shader_program.h diff --git a/src/igl/opengl/create_vector_vbo.cpp b/src/libigl/igl/opengl/create_vector_vbo.cpp similarity index 100% rename from src/igl/opengl/create_vector_vbo.cpp rename to src/libigl/igl/opengl/create_vector_vbo.cpp diff --git a/src/igl/opengl/create_vector_vbo.h b/src/libigl/igl/opengl/create_vector_vbo.h similarity index 100% rename from src/igl/opengl/create_vector_vbo.h rename to src/libigl/igl/opengl/create_vector_vbo.h diff --git a/src/igl/opengl/destroy_shader_program.cpp b/src/libigl/igl/opengl/destroy_shader_program.cpp similarity index 100% rename from src/igl/opengl/destroy_shader_program.cpp rename to src/libigl/igl/opengl/destroy_shader_program.cpp diff --git a/src/igl/opengl/destroy_shader_program.h b/src/libigl/igl/opengl/destroy_shader_program.h similarity index 100% rename from src/igl/opengl/destroy_shader_program.h rename to src/libigl/igl/opengl/destroy_shader_program.h diff --git a/src/igl/opengl/gl.h b/src/libigl/igl/opengl/gl.h similarity index 100% rename from src/igl/opengl/gl.h rename to src/libigl/igl/opengl/gl.h diff --git a/src/igl/opengl/gl_type_size.cpp b/src/libigl/igl/opengl/gl_type_size.cpp similarity index 100% rename from src/igl/opengl/gl_type_size.cpp rename to src/libigl/igl/opengl/gl_type_size.cpp diff --git a/src/igl/opengl/gl_type_size.h b/src/libigl/igl/opengl/gl_type_size.h similarity index 100% rename from src/igl/opengl/gl_type_size.h rename to src/libigl/igl/opengl/gl_type_size.h diff --git a/src/igl/opengl/glfw/Viewer.cpp b/src/libigl/igl/opengl/glfw/Viewer.cpp similarity index 100% rename from src/igl/opengl/glfw/Viewer.cpp rename to src/libigl/igl/opengl/glfw/Viewer.cpp diff --git a/src/igl/opengl/glfw/Viewer.h b/src/libigl/igl/opengl/glfw/Viewer.h similarity index 100% rename from src/igl/opengl/glfw/Viewer.h rename to src/libigl/igl/opengl/glfw/Viewer.h diff --git a/src/igl/opengl/glfw/ViewerPlugin.h b/src/libigl/igl/opengl/glfw/ViewerPlugin.h similarity index 100% rename from src/igl/opengl/glfw/ViewerPlugin.h rename to src/libigl/igl/opengl/glfw/ViewerPlugin.h diff --git a/src/igl/opengl/glfw/background_window.cpp b/src/libigl/igl/opengl/glfw/background_window.cpp similarity index 100% rename from src/igl/opengl/glfw/background_window.cpp rename to src/libigl/igl/opengl/glfw/background_window.cpp diff --git a/src/igl/opengl/glfw/background_window.h b/src/libigl/igl/opengl/glfw/background_window.h similarity index 100% rename from src/igl/opengl/glfw/background_window.h rename to src/libigl/igl/opengl/glfw/background_window.h diff --git a/src/igl/opengl/glfw/imgui/ImGuiHelpers.h b/src/libigl/igl/opengl/glfw/imgui/ImGuiHelpers.h similarity index 100% rename from src/igl/opengl/glfw/imgui/ImGuiHelpers.h rename to src/libigl/igl/opengl/glfw/imgui/ImGuiHelpers.h diff --git a/src/igl/opengl/glfw/imgui/ImGuiMenu.cpp b/src/libigl/igl/opengl/glfw/imgui/ImGuiMenu.cpp similarity index 100% rename from src/igl/opengl/glfw/imgui/ImGuiMenu.cpp rename to src/libigl/igl/opengl/glfw/imgui/ImGuiMenu.cpp diff --git a/src/igl/opengl/glfw/imgui/ImGuiMenu.h b/src/libigl/igl/opengl/glfw/imgui/ImGuiMenu.h similarity index 100% rename from src/igl/opengl/glfw/imgui/ImGuiMenu.h rename to src/libigl/igl/opengl/glfw/imgui/ImGuiMenu.h diff --git a/src/igl/opengl/glfw/map_texture.cpp b/src/libigl/igl/opengl/glfw/map_texture.cpp similarity index 100% rename from src/igl/opengl/glfw/map_texture.cpp rename to src/libigl/igl/opengl/glfw/map_texture.cpp diff --git a/src/igl/opengl/glfw/map_texture.h b/src/libigl/igl/opengl/glfw/map_texture.h similarity index 100% rename from src/igl/opengl/glfw/map_texture.h rename to src/libigl/igl/opengl/glfw/map_texture.h diff --git a/src/igl/opengl/init_render_to_texture.cpp b/src/libigl/igl/opengl/init_render_to_texture.cpp similarity index 100% rename from src/igl/opengl/init_render_to_texture.cpp rename to src/libigl/igl/opengl/init_render_to_texture.cpp diff --git a/src/igl/opengl/init_render_to_texture.h b/src/libigl/igl/opengl/init_render_to_texture.h similarity index 100% rename from src/igl/opengl/init_render_to_texture.h rename to src/libigl/igl/opengl/init_render_to_texture.h diff --git a/src/igl/opengl/load_shader.cpp b/src/libigl/igl/opengl/load_shader.cpp similarity index 100% rename from src/igl/opengl/load_shader.cpp rename to src/libigl/igl/opengl/load_shader.cpp diff --git a/src/igl/opengl/load_shader.h b/src/libigl/igl/opengl/load_shader.h similarity index 100% rename from src/igl/opengl/load_shader.h rename to src/libigl/igl/opengl/load_shader.h diff --git a/src/igl/opengl/print_program_info_log.cpp b/src/libigl/igl/opengl/print_program_info_log.cpp similarity index 100% rename from src/igl/opengl/print_program_info_log.cpp rename to src/libigl/igl/opengl/print_program_info_log.cpp diff --git a/src/igl/opengl/print_program_info_log.h b/src/libigl/igl/opengl/print_program_info_log.h similarity index 100% rename from src/igl/opengl/print_program_info_log.h rename to src/libigl/igl/opengl/print_program_info_log.h diff --git a/src/igl/opengl/print_shader_info_log.cpp b/src/libigl/igl/opengl/print_shader_info_log.cpp similarity index 100% rename from src/igl/opengl/print_shader_info_log.cpp rename to src/libigl/igl/opengl/print_shader_info_log.cpp diff --git a/src/igl/opengl/print_shader_info_log.h b/src/libigl/igl/opengl/print_shader_info_log.h similarity index 100% rename from src/igl/opengl/print_shader_info_log.h rename to src/libigl/igl/opengl/print_shader_info_log.h diff --git a/src/igl/opengl/report_gl_error.cpp b/src/libigl/igl/opengl/report_gl_error.cpp similarity index 100% rename from src/igl/opengl/report_gl_error.cpp rename to src/libigl/igl/opengl/report_gl_error.cpp diff --git a/src/igl/opengl/report_gl_error.h b/src/libigl/igl/opengl/report_gl_error.h similarity index 100% rename from src/igl/opengl/report_gl_error.h rename to src/libigl/igl/opengl/report_gl_error.h diff --git a/src/igl/opengl/uniform_type_to_string.cpp b/src/libigl/igl/opengl/uniform_type_to_string.cpp similarity index 100% rename from src/igl/opengl/uniform_type_to_string.cpp rename to src/libigl/igl/opengl/uniform_type_to_string.cpp diff --git a/src/igl/opengl/uniform_type_to_string.h b/src/libigl/igl/opengl/uniform_type_to_string.h similarity index 100% rename from src/igl/opengl/uniform_type_to_string.h rename to src/libigl/igl/opengl/uniform_type_to_string.h diff --git a/src/igl/opengl/vertex_array.cpp b/src/libigl/igl/opengl/vertex_array.cpp similarity index 100% rename from src/igl/opengl/vertex_array.cpp rename to src/libigl/igl/opengl/vertex_array.cpp diff --git a/src/igl/opengl/vertex_array.h b/src/libigl/igl/opengl/vertex_array.h similarity index 100% rename from src/igl/opengl/vertex_array.h rename to src/libigl/igl/opengl/vertex_array.h diff --git a/src/igl/opengl2/MouseController.h b/src/libigl/igl/opengl2/MouseController.h similarity index 100% rename from src/igl/opengl2/MouseController.h rename to src/libigl/igl/opengl2/MouseController.h diff --git a/src/igl/opengl2/RotateWidget.h b/src/libigl/igl/opengl2/RotateWidget.h similarity index 100% rename from src/igl/opengl2/RotateWidget.h rename to src/libigl/igl/opengl2/RotateWidget.h diff --git a/src/igl/opengl2/TranslateWidget.h b/src/libigl/igl/opengl2/TranslateWidget.h similarity index 100% rename from src/igl/opengl2/TranslateWidget.h rename to src/libigl/igl/opengl2/TranslateWidget.h diff --git a/src/igl/opengl2/draw_beach_ball.cpp b/src/libigl/igl/opengl2/draw_beach_ball.cpp similarity index 100% rename from src/igl/opengl2/draw_beach_ball.cpp rename to src/libigl/igl/opengl2/draw_beach_ball.cpp diff --git a/src/igl/opengl2/draw_beach_ball.h b/src/libigl/igl/opengl2/draw_beach_ball.h similarity index 100% rename from src/igl/opengl2/draw_beach_ball.h rename to src/libigl/igl/opengl2/draw_beach_ball.h diff --git a/src/igl/opengl2/draw_floor.cpp b/src/libigl/igl/opengl2/draw_floor.cpp similarity index 100% rename from src/igl/opengl2/draw_floor.cpp rename to src/libigl/igl/opengl2/draw_floor.cpp diff --git a/src/igl/opengl2/draw_floor.h b/src/libigl/igl/opengl2/draw_floor.h similarity index 100% rename from src/igl/opengl2/draw_floor.h rename to src/libigl/igl/opengl2/draw_floor.h diff --git a/src/igl/opengl2/draw_mesh.cpp b/src/libigl/igl/opengl2/draw_mesh.cpp similarity index 100% rename from src/igl/opengl2/draw_mesh.cpp rename to src/libigl/igl/opengl2/draw_mesh.cpp diff --git a/src/igl/opengl2/draw_mesh.h b/src/libigl/igl/opengl2/draw_mesh.h similarity index 100% rename from src/igl/opengl2/draw_mesh.h rename to src/libigl/igl/opengl2/draw_mesh.h diff --git a/src/igl/opengl2/draw_point.cpp b/src/libigl/igl/opengl2/draw_point.cpp similarity index 100% rename from src/igl/opengl2/draw_point.cpp rename to src/libigl/igl/opengl2/draw_point.cpp diff --git a/src/igl/opengl2/draw_point.h b/src/libigl/igl/opengl2/draw_point.h similarity index 100% rename from src/igl/opengl2/draw_point.h rename to src/libigl/igl/opengl2/draw_point.h diff --git a/src/igl/opengl2/draw_rectangular_marquee.cpp b/src/libigl/igl/opengl2/draw_rectangular_marquee.cpp similarity index 100% rename from src/igl/opengl2/draw_rectangular_marquee.cpp rename to src/libigl/igl/opengl2/draw_rectangular_marquee.cpp diff --git a/src/igl/opengl2/draw_rectangular_marquee.h b/src/libigl/igl/opengl2/draw_rectangular_marquee.h similarity index 100% rename from src/igl/opengl2/draw_rectangular_marquee.h rename to src/libigl/igl/opengl2/draw_rectangular_marquee.h diff --git a/src/igl/opengl2/draw_skeleton_3d.cpp b/src/libigl/igl/opengl2/draw_skeleton_3d.cpp similarity index 100% rename from src/igl/opengl2/draw_skeleton_3d.cpp rename to src/libigl/igl/opengl2/draw_skeleton_3d.cpp diff --git a/src/igl/opengl2/draw_skeleton_3d.h b/src/libigl/igl/opengl2/draw_skeleton_3d.h similarity index 100% rename from src/igl/opengl2/draw_skeleton_3d.h rename to src/libigl/igl/opengl2/draw_skeleton_3d.h diff --git a/src/igl/opengl2/draw_skeleton_vector_graphics.cpp b/src/libigl/igl/opengl2/draw_skeleton_vector_graphics.cpp similarity index 100% rename from src/igl/opengl2/draw_skeleton_vector_graphics.cpp rename to src/libigl/igl/opengl2/draw_skeleton_vector_graphics.cpp diff --git a/src/igl/opengl2/draw_skeleton_vector_graphics.h b/src/libigl/igl/opengl2/draw_skeleton_vector_graphics.h similarity index 100% rename from src/igl/opengl2/draw_skeleton_vector_graphics.h rename to src/libigl/igl/opengl2/draw_skeleton_vector_graphics.h diff --git a/src/igl/opengl2/flare_textures.h b/src/libigl/igl/opengl2/flare_textures.h similarity index 100% rename from src/igl/opengl2/flare_textures.h rename to src/libigl/igl/opengl2/flare_textures.h diff --git a/src/igl/opengl2/gl.h b/src/libigl/igl/opengl2/gl.h similarity index 100% rename from src/igl/opengl2/gl.h rename to src/libigl/igl/opengl2/gl.h diff --git a/src/igl/opengl2/glext.h b/src/libigl/igl/opengl2/glext.h similarity index 100% rename from src/igl/opengl2/glext.h rename to src/libigl/igl/opengl2/glext.h diff --git a/src/igl/opengl2/glu.h b/src/libigl/igl/opengl2/glu.h similarity index 100% rename from src/igl/opengl2/glu.h rename to src/libigl/igl/opengl2/glu.h diff --git a/src/igl/opengl2/lens_flare.cpp b/src/libigl/igl/opengl2/lens_flare.cpp similarity index 100% rename from src/igl/opengl2/lens_flare.cpp rename to src/libigl/igl/opengl2/lens_flare.cpp diff --git a/src/igl/opengl2/lens_flare.h b/src/libigl/igl/opengl2/lens_flare.h similarity index 100% rename from src/igl/opengl2/lens_flare.h rename to src/libigl/igl/opengl2/lens_flare.h diff --git a/src/igl/opengl2/model_proj_viewport.cpp b/src/libigl/igl/opengl2/model_proj_viewport.cpp similarity index 100% rename from src/igl/opengl2/model_proj_viewport.cpp rename to src/libigl/igl/opengl2/model_proj_viewport.cpp diff --git a/src/igl/opengl2/model_proj_viewport.h b/src/libigl/igl/opengl2/model_proj_viewport.h similarity index 100% rename from src/igl/opengl2/model_proj_viewport.h rename to src/libigl/igl/opengl2/model_proj_viewport.h diff --git a/src/igl/opengl2/print_gl_get.cpp b/src/libigl/igl/opengl2/print_gl_get.cpp similarity index 100% rename from src/igl/opengl2/print_gl_get.cpp rename to src/libigl/igl/opengl2/print_gl_get.cpp diff --git a/src/igl/opengl2/print_gl_get.h b/src/libigl/igl/opengl2/print_gl_get.h similarity index 100% rename from src/igl/opengl2/print_gl_get.h rename to src/libigl/igl/opengl2/print_gl_get.h diff --git a/src/igl/opengl2/project.cpp b/src/libigl/igl/opengl2/project.cpp similarity index 100% rename from src/igl/opengl2/project.cpp rename to src/libigl/igl/opengl2/project.cpp diff --git a/src/igl/opengl2/project.h b/src/libigl/igl/opengl2/project.h similarity index 100% rename from src/igl/opengl2/project.h rename to src/libigl/igl/opengl2/project.h diff --git a/src/igl/opengl2/right_axis.cpp b/src/libigl/igl/opengl2/right_axis.cpp similarity index 100% rename from src/igl/opengl2/right_axis.cpp rename to src/libigl/igl/opengl2/right_axis.cpp diff --git a/src/igl/opengl2/right_axis.h b/src/libigl/igl/opengl2/right_axis.h similarity index 100% rename from src/igl/opengl2/right_axis.h rename to src/libigl/igl/opengl2/right_axis.h diff --git a/src/igl/opengl2/shine_textures.h b/src/libigl/igl/opengl2/shine_textures.h similarity index 100% rename from src/igl/opengl2/shine_textures.h rename to src/libigl/igl/opengl2/shine_textures.h diff --git a/src/igl/opengl2/sort_triangles.cpp b/src/libigl/igl/opengl2/sort_triangles.cpp similarity index 100% rename from src/igl/opengl2/sort_triangles.cpp rename to src/libigl/igl/opengl2/sort_triangles.cpp diff --git a/src/igl/opengl2/sort_triangles.h b/src/libigl/igl/opengl2/sort_triangles.h similarity index 100% rename from src/igl/opengl2/sort_triangles.h rename to src/libigl/igl/opengl2/sort_triangles.h diff --git a/src/igl/opengl2/unproject.cpp b/src/libigl/igl/opengl2/unproject.cpp similarity index 100% rename from src/igl/opengl2/unproject.cpp rename to src/libigl/igl/opengl2/unproject.cpp diff --git a/src/igl/opengl2/unproject.h b/src/libigl/igl/opengl2/unproject.h similarity index 100% rename from src/igl/opengl2/unproject.h rename to src/libigl/igl/opengl2/unproject.h diff --git a/src/igl/opengl2/unproject_to_zero_plane.cpp b/src/libigl/igl/opengl2/unproject_to_zero_plane.cpp similarity index 100% rename from src/igl/opengl2/unproject_to_zero_plane.cpp rename to src/libigl/igl/opengl2/unproject_to_zero_plane.cpp diff --git a/src/igl/opengl2/unproject_to_zero_plane.h b/src/libigl/igl/opengl2/unproject_to_zero_plane.h similarity index 100% rename from src/igl/opengl2/unproject_to_zero_plane.h rename to src/libigl/igl/opengl2/unproject_to_zero_plane.h diff --git a/src/igl/opengl2/up_axis.cpp b/src/libigl/igl/opengl2/up_axis.cpp similarity index 100% rename from src/igl/opengl2/up_axis.cpp rename to src/libigl/igl/opengl2/up_axis.cpp diff --git a/src/igl/opengl2/up_axis.h b/src/libigl/igl/opengl2/up_axis.h similarity index 100% rename from src/igl/opengl2/up_axis.h rename to src/libigl/igl/opengl2/up_axis.h diff --git a/src/igl/opengl2/view_axis.cpp b/src/libigl/igl/opengl2/view_axis.cpp similarity index 100% rename from src/igl/opengl2/view_axis.cpp rename to src/libigl/igl/opengl2/view_axis.cpp diff --git a/src/igl/opengl2/view_axis.h b/src/libigl/igl/opengl2/view_axis.h similarity index 100% rename from src/igl/opengl2/view_axis.h rename to src/libigl/igl/opengl2/view_axis.h diff --git a/src/igl/orient_outward.cpp b/src/libigl/igl/orient_outward.cpp similarity index 100% rename from src/igl/orient_outward.cpp rename to src/libigl/igl/orient_outward.cpp diff --git a/src/igl/orient_outward.h b/src/libigl/igl/orient_outward.h similarity index 100% rename from src/igl/orient_outward.h rename to src/libigl/igl/orient_outward.h diff --git a/src/igl/orientable_patches.cpp b/src/libigl/igl/orientable_patches.cpp similarity index 100% rename from src/igl/orientable_patches.cpp rename to src/libigl/igl/orientable_patches.cpp diff --git a/src/igl/orientable_patches.h b/src/libigl/igl/orientable_patches.h similarity index 100% rename from src/igl/orientable_patches.h rename to src/libigl/igl/orientable_patches.h diff --git a/src/igl/oriented_facets.cpp b/src/libigl/igl/oriented_facets.cpp similarity index 100% rename from src/igl/oriented_facets.cpp rename to src/libigl/igl/oriented_facets.cpp diff --git a/src/igl/oriented_facets.h b/src/libigl/igl/oriented_facets.h similarity index 100% rename from src/igl/oriented_facets.h rename to src/libigl/igl/oriented_facets.h diff --git a/src/igl/orth.cpp b/src/libigl/igl/orth.cpp similarity index 100% rename from src/igl/orth.cpp rename to src/libigl/igl/orth.cpp diff --git a/src/igl/orth.h b/src/libigl/igl/orth.h similarity index 100% rename from src/igl/orth.h rename to src/libigl/igl/orth.h diff --git a/src/igl/ortho.cpp b/src/libigl/igl/ortho.cpp similarity index 100% rename from src/igl/ortho.cpp rename to src/libigl/igl/ortho.cpp diff --git a/src/igl/ortho.h b/src/libigl/igl/ortho.h similarity index 100% rename from src/igl/ortho.h rename to src/libigl/igl/ortho.h diff --git a/src/igl/outer_element.cpp b/src/libigl/igl/outer_element.cpp similarity index 100% rename from src/igl/outer_element.cpp rename to src/libigl/igl/outer_element.cpp diff --git a/src/igl/outer_element.h b/src/libigl/igl/outer_element.h similarity index 100% rename from src/igl/outer_element.h rename to src/libigl/igl/outer_element.h diff --git a/src/igl/parallel_for.h b/src/libigl/igl/parallel_for.h similarity index 100% rename from src/igl/parallel_for.h rename to src/libigl/igl/parallel_for.h diff --git a/src/igl/parallel_transport_angles.cpp b/src/libigl/igl/parallel_transport_angles.cpp similarity index 100% rename from src/igl/parallel_transport_angles.cpp rename to src/libigl/igl/parallel_transport_angles.cpp diff --git a/src/igl/parallel_transport_angles.h b/src/libigl/igl/parallel_transport_angles.h similarity index 100% rename from src/igl/parallel_transport_angles.h rename to src/libigl/igl/parallel_transport_angles.h diff --git a/src/igl/partition.cpp b/src/libigl/igl/partition.cpp similarity index 100% rename from src/igl/partition.cpp rename to src/libigl/igl/partition.cpp diff --git a/src/igl/partition.h b/src/libigl/igl/partition.h similarity index 100% rename from src/igl/partition.h rename to src/libigl/igl/partition.h diff --git a/src/igl/parula.cpp b/src/libigl/igl/parula.cpp similarity index 100% rename from src/igl/parula.cpp rename to src/libigl/igl/parula.cpp diff --git a/src/igl/parula.h b/src/libigl/igl/parula.h similarity index 100% rename from src/igl/parula.h rename to src/libigl/igl/parula.h diff --git a/src/igl/path_to_executable.cpp b/src/libigl/igl/path_to_executable.cpp similarity index 100% rename from src/igl/path_to_executable.cpp rename to src/libigl/igl/path_to_executable.cpp diff --git a/src/igl/path_to_executable.h b/src/libigl/igl/path_to_executable.h similarity index 100% rename from src/igl/path_to_executable.h rename to src/libigl/igl/path_to_executable.h diff --git a/src/igl/pathinfo.cpp b/src/libigl/igl/pathinfo.cpp similarity index 100% rename from src/igl/pathinfo.cpp rename to src/libigl/igl/pathinfo.cpp diff --git a/src/igl/pathinfo.h b/src/libigl/igl/pathinfo.h similarity index 100% rename from src/igl/pathinfo.h rename to src/libigl/igl/pathinfo.h diff --git a/src/igl/per_corner_normals.cpp b/src/libigl/igl/per_corner_normals.cpp similarity index 100% rename from src/igl/per_corner_normals.cpp rename to src/libigl/igl/per_corner_normals.cpp diff --git a/src/igl/per_corner_normals.h b/src/libigl/igl/per_corner_normals.h similarity index 100% rename from src/igl/per_corner_normals.h rename to src/libigl/igl/per_corner_normals.h diff --git a/src/igl/per_edge_normals.cpp b/src/libigl/igl/per_edge_normals.cpp similarity index 100% rename from src/igl/per_edge_normals.cpp rename to src/libigl/igl/per_edge_normals.cpp diff --git a/src/igl/per_edge_normals.h b/src/libigl/igl/per_edge_normals.h similarity index 100% rename from src/igl/per_edge_normals.h rename to src/libigl/igl/per_edge_normals.h diff --git a/src/igl/per_face_normals.cpp b/src/libigl/igl/per_face_normals.cpp similarity index 100% rename from src/igl/per_face_normals.cpp rename to src/libigl/igl/per_face_normals.cpp diff --git a/src/igl/per_face_normals.h b/src/libigl/igl/per_face_normals.h similarity index 100% rename from src/igl/per_face_normals.h rename to src/libigl/igl/per_face_normals.h diff --git a/src/igl/per_vertex_attribute_smoothing.cpp b/src/libigl/igl/per_vertex_attribute_smoothing.cpp similarity index 100% rename from src/igl/per_vertex_attribute_smoothing.cpp rename to src/libigl/igl/per_vertex_attribute_smoothing.cpp diff --git a/src/igl/per_vertex_attribute_smoothing.h b/src/libigl/igl/per_vertex_attribute_smoothing.h similarity index 100% rename from src/igl/per_vertex_attribute_smoothing.h rename to src/libigl/igl/per_vertex_attribute_smoothing.h diff --git a/src/igl/per_vertex_normals.cpp b/src/libigl/igl/per_vertex_normals.cpp similarity index 100% rename from src/igl/per_vertex_normals.cpp rename to src/libigl/igl/per_vertex_normals.cpp diff --git a/src/igl/per_vertex_normals.h b/src/libigl/igl/per_vertex_normals.h similarity index 100% rename from src/igl/per_vertex_normals.h rename to src/libigl/igl/per_vertex_normals.h diff --git a/src/igl/per_vertex_point_to_plane_quadrics.cpp b/src/libigl/igl/per_vertex_point_to_plane_quadrics.cpp similarity index 100% rename from src/igl/per_vertex_point_to_plane_quadrics.cpp rename to src/libigl/igl/per_vertex_point_to_plane_quadrics.cpp diff --git a/src/igl/per_vertex_point_to_plane_quadrics.h b/src/libigl/igl/per_vertex_point_to_plane_quadrics.h similarity index 100% rename from src/igl/per_vertex_point_to_plane_quadrics.h rename to src/libigl/igl/per_vertex_point_to_plane_quadrics.h diff --git a/src/igl/piecewise_constant_winding_number.cpp b/src/libigl/igl/piecewise_constant_winding_number.cpp similarity index 100% rename from src/igl/piecewise_constant_winding_number.cpp rename to src/libigl/igl/piecewise_constant_winding_number.cpp diff --git a/src/igl/piecewise_constant_winding_number.h b/src/libigl/igl/piecewise_constant_winding_number.h similarity index 100% rename from src/igl/piecewise_constant_winding_number.h rename to src/libigl/igl/piecewise_constant_winding_number.h diff --git a/src/igl/pinv.cpp b/src/libigl/igl/pinv.cpp similarity index 100% rename from src/igl/pinv.cpp rename to src/libigl/igl/pinv.cpp diff --git a/src/igl/pinv.h b/src/libigl/igl/pinv.h similarity index 100% rename from src/igl/pinv.h rename to src/libigl/igl/pinv.h diff --git a/src/igl/planarize_quad_mesh.cpp b/src/libigl/igl/planarize_quad_mesh.cpp similarity index 100% rename from src/igl/planarize_quad_mesh.cpp rename to src/libigl/igl/planarize_quad_mesh.cpp diff --git a/src/igl/planarize_quad_mesh.h b/src/libigl/igl/planarize_quad_mesh.h similarity index 100% rename from src/igl/planarize_quad_mesh.h rename to src/libigl/igl/planarize_quad_mesh.h diff --git a/src/igl/ply.h b/src/libigl/igl/ply.h similarity index 100% rename from src/igl/ply.h rename to src/libigl/igl/ply.h diff --git a/src/igl/png/readPNG.cpp b/src/libigl/igl/png/readPNG.cpp similarity index 100% rename from src/igl/png/readPNG.cpp rename to src/libigl/igl/png/readPNG.cpp diff --git a/src/igl/png/readPNG.h b/src/libigl/igl/png/readPNG.h similarity index 100% rename from src/igl/png/readPNG.h rename to src/libigl/igl/png/readPNG.h diff --git a/src/igl/png/render_to_png.cpp b/src/libigl/igl/png/render_to_png.cpp similarity index 100% rename from src/igl/png/render_to_png.cpp rename to src/libigl/igl/png/render_to_png.cpp diff --git a/src/igl/png/render_to_png.h b/src/libigl/igl/png/render_to_png.h similarity index 100% rename from src/igl/png/render_to_png.h rename to src/libigl/igl/png/render_to_png.h diff --git a/src/igl/png/render_to_png_async.cpp b/src/libigl/igl/png/render_to_png_async.cpp similarity index 100% rename from src/igl/png/render_to_png_async.cpp rename to src/libigl/igl/png/render_to_png_async.cpp diff --git a/src/igl/png/render_to_png_async.h b/src/libigl/igl/png/render_to_png_async.h similarity index 100% rename from src/igl/png/render_to_png_async.h rename to src/libigl/igl/png/render_to_png_async.h diff --git a/src/igl/png/texture_from_file.cpp b/src/libigl/igl/png/texture_from_file.cpp similarity index 100% rename from src/igl/png/texture_from_file.cpp rename to src/libigl/igl/png/texture_from_file.cpp diff --git a/src/igl/png/texture_from_file.h b/src/libigl/igl/png/texture_from_file.h similarity index 100% rename from src/igl/png/texture_from_file.h rename to src/libigl/igl/png/texture_from_file.h diff --git a/src/igl/png/texture_from_png.cpp b/src/libigl/igl/png/texture_from_png.cpp similarity index 100% rename from src/igl/png/texture_from_png.cpp rename to src/libigl/igl/png/texture_from_png.cpp diff --git a/src/igl/png/texture_from_png.h b/src/libigl/igl/png/texture_from_png.h similarity index 100% rename from src/igl/png/texture_from_png.h rename to src/libigl/igl/png/texture_from_png.h diff --git a/src/igl/png/writePNG.cpp b/src/libigl/igl/png/writePNG.cpp similarity index 100% rename from src/igl/png/writePNG.cpp rename to src/libigl/igl/png/writePNG.cpp diff --git a/src/igl/png/writePNG.h b/src/libigl/igl/png/writePNG.h similarity index 100% rename from src/igl/png/writePNG.h rename to src/libigl/igl/png/writePNG.h diff --git a/src/igl/point_in_circle.cpp b/src/libigl/igl/point_in_circle.cpp similarity index 100% rename from src/igl/point_in_circle.cpp rename to src/libigl/igl/point_in_circle.cpp diff --git a/src/igl/point_in_circle.h b/src/libigl/igl/point_in_circle.h similarity index 100% rename from src/igl/point_in_circle.h rename to src/libigl/igl/point_in_circle.h diff --git a/src/igl/point_in_poly.cpp b/src/libigl/igl/point_in_poly.cpp similarity index 100% rename from src/igl/point_in_poly.cpp rename to src/libigl/igl/point_in_poly.cpp diff --git a/src/igl/point_in_poly.h b/src/libigl/igl/point_in_poly.h similarity index 100% rename from src/igl/point_in_poly.h rename to src/libigl/igl/point_in_poly.h diff --git a/src/igl/point_mesh_squared_distance.cpp b/src/libigl/igl/point_mesh_squared_distance.cpp similarity index 100% rename from src/igl/point_mesh_squared_distance.cpp rename to src/libigl/igl/point_mesh_squared_distance.cpp diff --git a/src/igl/point_mesh_squared_distance.h b/src/libigl/igl/point_mesh_squared_distance.h similarity index 100% rename from src/igl/point_mesh_squared_distance.h rename to src/libigl/igl/point_mesh_squared_distance.h diff --git a/src/igl/point_simplex_squared_distance.cpp b/src/libigl/igl/point_simplex_squared_distance.cpp similarity index 100% rename from src/igl/point_simplex_squared_distance.cpp rename to src/libigl/igl/point_simplex_squared_distance.cpp diff --git a/src/igl/point_simplex_squared_distance.h b/src/libigl/igl/point_simplex_squared_distance.h similarity index 100% rename from src/igl/point_simplex_squared_distance.h rename to src/libigl/igl/point_simplex_squared_distance.h diff --git a/src/igl/polar_dec.cpp b/src/libigl/igl/polar_dec.cpp similarity index 100% rename from src/igl/polar_dec.cpp rename to src/libigl/igl/polar_dec.cpp diff --git a/src/igl/polar_dec.h b/src/libigl/igl/polar_dec.h similarity index 100% rename from src/igl/polar_dec.h rename to src/libigl/igl/polar_dec.h diff --git a/src/igl/polar_svd.cpp b/src/libigl/igl/polar_svd.cpp similarity index 100% rename from src/igl/polar_svd.cpp rename to src/libigl/igl/polar_svd.cpp diff --git a/src/igl/polar_svd.h b/src/libigl/igl/polar_svd.h similarity index 100% rename from src/igl/polar_svd.h rename to src/libigl/igl/polar_svd.h diff --git a/src/igl/polar_svd3x3.cpp b/src/libigl/igl/polar_svd3x3.cpp similarity index 100% rename from src/igl/polar_svd3x3.cpp rename to src/libigl/igl/polar_svd3x3.cpp diff --git a/src/igl/polar_svd3x3.h b/src/libigl/igl/polar_svd3x3.h similarity index 100% rename from src/igl/polar_svd3x3.h rename to src/libigl/igl/polar_svd3x3.h diff --git a/src/igl/polygon_mesh_to_triangle_mesh.cpp b/src/libigl/igl/polygon_mesh_to_triangle_mesh.cpp similarity index 100% rename from src/igl/polygon_mesh_to_triangle_mesh.cpp rename to src/libigl/igl/polygon_mesh_to_triangle_mesh.cpp diff --git a/src/igl/polygon_mesh_to_triangle_mesh.h b/src/libigl/igl/polygon_mesh_to_triangle_mesh.h similarity index 100% rename from src/igl/polygon_mesh_to_triangle_mesh.h rename to src/libigl/igl/polygon_mesh_to_triangle_mesh.h diff --git a/src/igl/principal_curvature.cpp b/src/libigl/igl/principal_curvature.cpp similarity index 100% rename from src/igl/principal_curvature.cpp rename to src/libigl/igl/principal_curvature.cpp diff --git a/src/igl/principal_curvature.h b/src/libigl/igl/principal_curvature.h similarity index 100% rename from src/igl/principal_curvature.h rename to src/libigl/igl/principal_curvature.h diff --git a/src/igl/print_ijv.cpp b/src/libigl/igl/print_ijv.cpp similarity index 100% rename from src/igl/print_ijv.cpp rename to src/libigl/igl/print_ijv.cpp diff --git a/src/igl/print_ijv.h b/src/libigl/igl/print_ijv.h similarity index 100% rename from src/igl/print_ijv.h rename to src/libigl/igl/print_ijv.h diff --git a/src/igl/print_vector.cpp b/src/libigl/igl/print_vector.cpp similarity index 100% rename from src/igl/print_vector.cpp rename to src/libigl/igl/print_vector.cpp diff --git a/src/igl/print_vector.h b/src/libigl/igl/print_vector.h similarity index 100% rename from src/igl/print_vector.h rename to src/libigl/igl/print_vector.h diff --git a/src/igl/procrustes.cpp b/src/libigl/igl/procrustes.cpp similarity index 100% rename from src/igl/procrustes.cpp rename to src/libigl/igl/procrustes.cpp diff --git a/src/igl/procrustes.h b/src/libigl/igl/procrustes.h similarity index 100% rename from src/igl/procrustes.h rename to src/libigl/igl/procrustes.h diff --git a/src/igl/project.cpp b/src/libigl/igl/project.cpp similarity index 100% rename from src/igl/project.cpp rename to src/libigl/igl/project.cpp diff --git a/src/igl/project.h b/src/libigl/igl/project.h similarity index 100% rename from src/igl/project.h rename to src/libigl/igl/project.h diff --git a/src/igl/project_isometrically_to_plane.cpp b/src/libigl/igl/project_isometrically_to_plane.cpp similarity index 100% rename from src/igl/project_isometrically_to_plane.cpp rename to src/libigl/igl/project_isometrically_to_plane.cpp diff --git a/src/igl/project_isometrically_to_plane.h b/src/libigl/igl/project_isometrically_to_plane.h similarity index 100% rename from src/igl/project_isometrically_to_plane.h rename to src/libigl/igl/project_isometrically_to_plane.h diff --git a/src/igl/project_to_line.cpp b/src/libigl/igl/project_to_line.cpp similarity index 100% rename from src/igl/project_to_line.cpp rename to src/libigl/igl/project_to_line.cpp diff --git a/src/igl/project_to_line.h b/src/libigl/igl/project_to_line.h similarity index 100% rename from src/igl/project_to_line.h rename to src/libigl/igl/project_to_line.h diff --git a/src/igl/project_to_line_segment.cpp b/src/libigl/igl/project_to_line_segment.cpp similarity index 100% rename from src/igl/project_to_line_segment.cpp rename to src/libigl/igl/project_to_line_segment.cpp diff --git a/src/igl/project_to_line_segment.h b/src/libigl/igl/project_to_line_segment.h similarity index 100% rename from src/igl/project_to_line_segment.h rename to src/libigl/igl/project_to_line_segment.h diff --git a/src/igl/pseudonormal_test.cpp b/src/libigl/igl/pseudonormal_test.cpp similarity index 100% rename from src/igl/pseudonormal_test.cpp rename to src/libigl/igl/pseudonormal_test.cpp diff --git a/src/igl/pseudonormal_test.h b/src/libigl/igl/pseudonormal_test.h similarity index 100% rename from src/igl/pseudonormal_test.h rename to src/libigl/igl/pseudonormal_test.h diff --git a/src/igl/pso.cpp b/src/libigl/igl/pso.cpp similarity index 100% rename from src/igl/pso.cpp rename to src/libigl/igl/pso.cpp diff --git a/src/igl/pso.h b/src/libigl/igl/pso.h similarity index 100% rename from src/igl/pso.h rename to src/libigl/igl/pso.h diff --git a/src/igl/qslim.cpp b/src/libigl/igl/qslim.cpp similarity index 100% rename from src/igl/qslim.cpp rename to src/libigl/igl/qslim.cpp diff --git a/src/igl/qslim.h b/src/libigl/igl/qslim.h similarity index 100% rename from src/igl/qslim.h rename to src/libigl/igl/qslim.h diff --git a/src/igl/qslim_optimal_collapse_edge_callbacks.cpp b/src/libigl/igl/qslim_optimal_collapse_edge_callbacks.cpp similarity index 100% rename from src/igl/qslim_optimal_collapse_edge_callbacks.cpp rename to src/libigl/igl/qslim_optimal_collapse_edge_callbacks.cpp diff --git a/src/igl/qslim_optimal_collapse_edge_callbacks.h b/src/libigl/igl/qslim_optimal_collapse_edge_callbacks.h similarity index 100% rename from src/igl/qslim_optimal_collapse_edge_callbacks.h rename to src/libigl/igl/qslim_optimal_collapse_edge_callbacks.h diff --git a/src/igl/quad_planarity.cpp b/src/libigl/igl/quad_planarity.cpp similarity index 100% rename from src/igl/quad_planarity.cpp rename to src/libigl/igl/quad_planarity.cpp diff --git a/src/igl/quad_planarity.h b/src/libigl/igl/quad_planarity.h similarity index 100% rename from src/igl/quad_planarity.h rename to src/libigl/igl/quad_planarity.h diff --git a/src/igl/quadric_binary_plus_operator.cpp b/src/libigl/igl/quadric_binary_plus_operator.cpp similarity index 100% rename from src/igl/quadric_binary_plus_operator.cpp rename to src/libigl/igl/quadric_binary_plus_operator.cpp diff --git a/src/igl/quadric_binary_plus_operator.h b/src/libigl/igl/quadric_binary_plus_operator.h similarity index 100% rename from src/igl/quadric_binary_plus_operator.h rename to src/libigl/igl/quadric_binary_plus_operator.h diff --git a/src/igl/quat_conjugate.cpp b/src/libigl/igl/quat_conjugate.cpp similarity index 100% rename from src/igl/quat_conjugate.cpp rename to src/libigl/igl/quat_conjugate.cpp diff --git a/src/igl/quat_conjugate.h b/src/libigl/igl/quat_conjugate.h similarity index 100% rename from src/igl/quat_conjugate.h rename to src/libigl/igl/quat_conjugate.h diff --git a/src/igl/quat_mult.cpp b/src/libigl/igl/quat_mult.cpp similarity index 100% rename from src/igl/quat_mult.cpp rename to src/libigl/igl/quat_mult.cpp diff --git a/src/igl/quat_mult.h b/src/libigl/igl/quat_mult.h similarity index 100% rename from src/igl/quat_mult.h rename to src/libigl/igl/quat_mult.h diff --git a/src/igl/quat_to_axis_angle.cpp b/src/libigl/igl/quat_to_axis_angle.cpp similarity index 100% rename from src/igl/quat_to_axis_angle.cpp rename to src/libigl/igl/quat_to_axis_angle.cpp diff --git a/src/igl/quat_to_axis_angle.h b/src/libigl/igl/quat_to_axis_angle.h similarity index 100% rename from src/igl/quat_to_axis_angle.h rename to src/libigl/igl/quat_to_axis_angle.h diff --git a/src/igl/quat_to_mat.cpp b/src/libigl/igl/quat_to_mat.cpp similarity index 100% rename from src/igl/quat_to_mat.cpp rename to src/libigl/igl/quat_to_mat.cpp diff --git a/src/igl/quat_to_mat.h b/src/libigl/igl/quat_to_mat.h similarity index 100% rename from src/igl/quat_to_mat.h rename to src/libigl/igl/quat_to_mat.h diff --git a/src/igl/quats_to_column.cpp b/src/libigl/igl/quats_to_column.cpp similarity index 100% rename from src/igl/quats_to_column.cpp rename to src/libigl/igl/quats_to_column.cpp diff --git a/src/igl/quats_to_column.h b/src/libigl/igl/quats_to_column.h similarity index 100% rename from src/igl/quats_to_column.h rename to src/libigl/igl/quats_to_column.h diff --git a/src/igl/ramer_douglas_peucker.cpp b/src/libigl/igl/ramer_douglas_peucker.cpp similarity index 100% rename from src/igl/ramer_douglas_peucker.cpp rename to src/libigl/igl/ramer_douglas_peucker.cpp diff --git a/src/igl/ramer_douglas_peucker.h b/src/libigl/igl/ramer_douglas_peucker.h similarity index 100% rename from src/igl/ramer_douglas_peucker.h rename to src/libigl/igl/ramer_douglas_peucker.h diff --git a/src/igl/random_dir.cpp b/src/libigl/igl/random_dir.cpp similarity index 100% rename from src/igl/random_dir.cpp rename to src/libigl/igl/random_dir.cpp diff --git a/src/igl/random_dir.h b/src/libigl/igl/random_dir.h similarity index 100% rename from src/igl/random_dir.h rename to src/libigl/igl/random_dir.h diff --git a/src/igl/random_points_on_mesh.cpp b/src/libigl/igl/random_points_on_mesh.cpp similarity index 100% rename from src/igl/random_points_on_mesh.cpp rename to src/libigl/igl/random_points_on_mesh.cpp diff --git a/src/igl/random_points_on_mesh.h b/src/libigl/igl/random_points_on_mesh.h similarity index 100% rename from src/igl/random_points_on_mesh.h rename to src/libigl/igl/random_points_on_mesh.h diff --git a/src/igl/random_quaternion.cpp b/src/libigl/igl/random_quaternion.cpp similarity index 100% rename from src/igl/random_quaternion.cpp rename to src/libigl/igl/random_quaternion.cpp diff --git a/src/igl/random_quaternion.h b/src/libigl/igl/random_quaternion.h similarity index 100% rename from src/igl/random_quaternion.h rename to src/libigl/igl/random_quaternion.h diff --git a/src/igl/random_search.cpp b/src/libigl/igl/random_search.cpp similarity index 100% rename from src/igl/random_search.cpp rename to src/libigl/igl/random_search.cpp diff --git a/src/igl/random_search.h b/src/libigl/igl/random_search.h similarity index 100% rename from src/igl/random_search.h rename to src/libigl/igl/random_search.h diff --git a/src/igl/randperm.cpp b/src/libigl/igl/randperm.cpp similarity index 100% rename from src/igl/randperm.cpp rename to src/libigl/igl/randperm.cpp diff --git a/src/igl/randperm.h b/src/libigl/igl/randperm.h similarity index 100% rename from src/igl/randperm.h rename to src/libigl/igl/randperm.h diff --git a/src/igl/ray_box_intersect.cpp b/src/libigl/igl/ray_box_intersect.cpp similarity index 100% rename from src/igl/ray_box_intersect.cpp rename to src/libigl/igl/ray_box_intersect.cpp diff --git a/src/igl/ray_box_intersect.h b/src/libigl/igl/ray_box_intersect.h similarity index 100% rename from src/igl/ray_box_intersect.h rename to src/libigl/igl/ray_box_intersect.h diff --git a/src/igl/ray_mesh_intersect.cpp b/src/libigl/igl/ray_mesh_intersect.cpp similarity index 100% rename from src/igl/ray_mesh_intersect.cpp rename to src/libigl/igl/ray_mesh_intersect.cpp diff --git a/src/igl/ray_mesh_intersect.h b/src/libigl/igl/ray_mesh_intersect.h similarity index 100% rename from src/igl/ray_mesh_intersect.h rename to src/libigl/igl/ray_mesh_intersect.h diff --git a/src/igl/ray_sphere_intersect.cpp b/src/libigl/igl/ray_sphere_intersect.cpp similarity index 100% rename from src/igl/ray_sphere_intersect.cpp rename to src/libigl/igl/ray_sphere_intersect.cpp diff --git a/src/igl/ray_sphere_intersect.h b/src/libigl/igl/ray_sphere_intersect.h similarity index 100% rename from src/igl/ray_sphere_intersect.h rename to src/libigl/igl/ray_sphere_intersect.h diff --git a/src/igl/raytri.c b/src/libigl/igl/raytri.c similarity index 100% rename from src/igl/raytri.c rename to src/libigl/igl/raytri.c diff --git a/src/igl/readBF.cpp b/src/libigl/igl/readBF.cpp similarity index 100% rename from src/igl/readBF.cpp rename to src/libigl/igl/readBF.cpp diff --git a/src/igl/readBF.h b/src/libigl/igl/readBF.h similarity index 100% rename from src/igl/readBF.h rename to src/libigl/igl/readBF.h diff --git a/src/igl/readCSV.cpp b/src/libigl/igl/readCSV.cpp similarity index 100% rename from src/igl/readCSV.cpp rename to src/libigl/igl/readCSV.cpp diff --git a/src/igl/readCSV.h b/src/libigl/igl/readCSV.h similarity index 100% rename from src/igl/readCSV.h rename to src/libigl/igl/readCSV.h diff --git a/src/igl/readDMAT.cpp b/src/libigl/igl/readDMAT.cpp similarity index 100% rename from src/igl/readDMAT.cpp rename to src/libigl/igl/readDMAT.cpp diff --git a/src/igl/readDMAT.h b/src/libigl/igl/readDMAT.h similarity index 100% rename from src/igl/readDMAT.h rename to src/libigl/igl/readDMAT.h diff --git a/src/igl/readMESH.cpp b/src/libigl/igl/readMESH.cpp similarity index 100% rename from src/igl/readMESH.cpp rename to src/libigl/igl/readMESH.cpp diff --git a/src/igl/readMESH.h b/src/libigl/igl/readMESH.h similarity index 100% rename from src/igl/readMESH.h rename to src/libigl/igl/readMESH.h diff --git a/src/igl/readMSH.cpp b/src/libigl/igl/readMSH.cpp similarity index 100% rename from src/igl/readMSH.cpp rename to src/libigl/igl/readMSH.cpp diff --git a/src/igl/readMSH.h b/src/libigl/igl/readMSH.h similarity index 100% rename from src/igl/readMSH.h rename to src/libigl/igl/readMSH.h diff --git a/src/igl/readNODE.cpp b/src/libigl/igl/readNODE.cpp similarity index 100% rename from src/igl/readNODE.cpp rename to src/libigl/igl/readNODE.cpp diff --git a/src/igl/readNODE.h b/src/libigl/igl/readNODE.h similarity index 100% rename from src/igl/readNODE.h rename to src/libigl/igl/readNODE.h diff --git a/src/igl/readOBJ.cpp b/src/libigl/igl/readOBJ.cpp similarity index 100% rename from src/igl/readOBJ.cpp rename to src/libigl/igl/readOBJ.cpp diff --git a/src/igl/readOBJ.h b/src/libigl/igl/readOBJ.h similarity index 100% rename from src/igl/readOBJ.h rename to src/libigl/igl/readOBJ.h diff --git a/src/igl/readOFF.cpp b/src/libigl/igl/readOFF.cpp similarity index 100% rename from src/igl/readOFF.cpp rename to src/libigl/igl/readOFF.cpp diff --git a/src/igl/readOFF.h b/src/libigl/igl/readOFF.h similarity index 100% rename from src/igl/readOFF.h rename to src/libigl/igl/readOFF.h diff --git a/src/igl/readPLY.cpp b/src/libigl/igl/readPLY.cpp similarity index 100% rename from src/igl/readPLY.cpp rename to src/libigl/igl/readPLY.cpp diff --git a/src/igl/readPLY.h b/src/libigl/igl/readPLY.h similarity index 100% rename from src/igl/readPLY.h rename to src/libigl/igl/readPLY.h diff --git a/src/igl/readSTL.cpp b/src/libigl/igl/readSTL.cpp similarity index 100% rename from src/igl/readSTL.cpp rename to src/libigl/igl/readSTL.cpp diff --git a/src/igl/readSTL.h b/src/libigl/igl/readSTL.h similarity index 100% rename from src/igl/readSTL.h rename to src/libigl/igl/readSTL.h diff --git a/src/igl/readTGF.cpp b/src/libigl/igl/readTGF.cpp similarity index 100% rename from src/igl/readTGF.cpp rename to src/libigl/igl/readTGF.cpp diff --git a/src/igl/readTGF.h b/src/libigl/igl/readTGF.h similarity index 100% rename from src/igl/readTGF.h rename to src/libigl/igl/readTGF.h diff --git a/src/igl/readWRL.cpp b/src/libigl/igl/readWRL.cpp similarity index 100% rename from src/igl/readWRL.cpp rename to src/libigl/igl/readWRL.cpp diff --git a/src/igl/readWRL.h b/src/libigl/igl/readWRL.h similarity index 100% rename from src/igl/readWRL.h rename to src/libigl/igl/readWRL.h diff --git a/src/igl/read_triangle_mesh.cpp b/src/libigl/igl/read_triangle_mesh.cpp similarity index 100% rename from src/igl/read_triangle_mesh.cpp rename to src/libigl/igl/read_triangle_mesh.cpp diff --git a/src/igl/read_triangle_mesh.h b/src/libigl/igl/read_triangle_mesh.h similarity index 100% rename from src/igl/read_triangle_mesh.h rename to src/libigl/igl/read_triangle_mesh.h diff --git a/src/igl/redux.h b/src/libigl/igl/redux.h similarity index 100% rename from src/igl/redux.h rename to src/libigl/igl/redux.h diff --git a/src/igl/remesh_along_isoline.cpp b/src/libigl/igl/remesh_along_isoline.cpp similarity index 100% rename from src/igl/remesh_along_isoline.cpp rename to src/libigl/igl/remesh_along_isoline.cpp diff --git a/src/igl/remesh_along_isoline.h b/src/libigl/igl/remesh_along_isoline.h similarity index 100% rename from src/igl/remesh_along_isoline.h rename to src/libigl/igl/remesh_along_isoline.h diff --git a/src/igl/remove_duplicate_vertices.cpp b/src/libigl/igl/remove_duplicate_vertices.cpp similarity index 100% rename from src/igl/remove_duplicate_vertices.cpp rename to src/libigl/igl/remove_duplicate_vertices.cpp diff --git a/src/igl/remove_duplicate_vertices.h b/src/libigl/igl/remove_duplicate_vertices.h similarity index 100% rename from src/igl/remove_duplicate_vertices.h rename to src/libigl/igl/remove_duplicate_vertices.h diff --git a/src/igl/remove_duplicates.cpp b/src/libigl/igl/remove_duplicates.cpp similarity index 100% rename from src/igl/remove_duplicates.cpp rename to src/libigl/igl/remove_duplicates.cpp diff --git a/src/igl/remove_duplicates.h b/src/libigl/igl/remove_duplicates.h similarity index 100% rename from src/igl/remove_duplicates.h rename to src/libigl/igl/remove_duplicates.h diff --git a/src/igl/remove_unreferenced.cpp b/src/libigl/igl/remove_unreferenced.cpp similarity index 100% rename from src/igl/remove_unreferenced.cpp rename to src/libigl/igl/remove_unreferenced.cpp diff --git a/src/igl/remove_unreferenced.h b/src/libigl/igl/remove_unreferenced.h similarity index 100% rename from src/igl/remove_unreferenced.h rename to src/libigl/igl/remove_unreferenced.h diff --git a/src/igl/reorder.cpp b/src/libigl/igl/reorder.cpp similarity index 100% rename from src/igl/reorder.cpp rename to src/libigl/igl/reorder.cpp diff --git a/src/igl/reorder.h b/src/libigl/igl/reorder.h similarity index 100% rename from src/igl/reorder.h rename to src/libigl/igl/reorder.h diff --git a/src/igl/repdiag.cpp b/src/libigl/igl/repdiag.cpp similarity index 100% rename from src/igl/repdiag.cpp rename to src/libigl/igl/repdiag.cpp diff --git a/src/igl/repdiag.h b/src/libigl/igl/repdiag.h similarity index 100% rename from src/igl/repdiag.h rename to src/libigl/igl/repdiag.h diff --git a/src/igl/repmat.cpp b/src/libigl/igl/repmat.cpp similarity index 100% rename from src/igl/repmat.cpp rename to src/libigl/igl/repmat.cpp diff --git a/src/igl/repmat.h b/src/libigl/igl/repmat.h similarity index 100% rename from src/igl/repmat.h rename to src/libigl/igl/repmat.h diff --git a/src/igl/resolve_duplicated_faces.cpp b/src/libigl/igl/resolve_duplicated_faces.cpp similarity index 100% rename from src/igl/resolve_duplicated_faces.cpp rename to src/libigl/igl/resolve_duplicated_faces.cpp diff --git a/src/igl/resolve_duplicated_faces.h b/src/libigl/igl/resolve_duplicated_faces.h similarity index 100% rename from src/igl/resolve_duplicated_faces.h rename to src/libigl/igl/resolve_duplicated_faces.h diff --git a/src/igl/rgb_to_hsv.cpp b/src/libigl/igl/rgb_to_hsv.cpp similarity index 100% rename from src/igl/rgb_to_hsv.cpp rename to src/libigl/igl/rgb_to_hsv.cpp diff --git a/src/igl/rgb_to_hsv.h b/src/libigl/igl/rgb_to_hsv.h similarity index 100% rename from src/igl/rgb_to_hsv.h rename to src/libigl/igl/rgb_to_hsv.h diff --git a/src/igl/rotate_by_quat.cpp b/src/libigl/igl/rotate_by_quat.cpp similarity index 100% rename from src/igl/rotate_by_quat.cpp rename to src/libigl/igl/rotate_by_quat.cpp diff --git a/src/igl/rotate_by_quat.h b/src/libigl/igl/rotate_by_quat.h similarity index 100% rename from src/igl/rotate_by_quat.h rename to src/libigl/igl/rotate_by_quat.h diff --git a/src/igl/rotate_vectors.cpp b/src/libigl/igl/rotate_vectors.cpp similarity index 100% rename from src/igl/rotate_vectors.cpp rename to src/libigl/igl/rotate_vectors.cpp diff --git a/src/igl/rotate_vectors.h b/src/libigl/igl/rotate_vectors.h similarity index 100% rename from src/igl/rotate_vectors.h rename to src/libigl/igl/rotate_vectors.h diff --git a/src/igl/rotation_matrix_from_directions.cpp b/src/libigl/igl/rotation_matrix_from_directions.cpp similarity index 100% rename from src/igl/rotation_matrix_from_directions.cpp rename to src/libigl/igl/rotation_matrix_from_directions.cpp diff --git a/src/igl/rotation_matrix_from_directions.h b/src/libigl/igl/rotation_matrix_from_directions.h similarity index 100% rename from src/igl/rotation_matrix_from_directions.h rename to src/libigl/igl/rotation_matrix_from_directions.h diff --git a/src/igl/round.cpp b/src/libigl/igl/round.cpp similarity index 100% rename from src/igl/round.cpp rename to src/libigl/igl/round.cpp diff --git a/src/igl/round.h b/src/libigl/igl/round.h similarity index 100% rename from src/igl/round.h rename to src/libigl/igl/round.h diff --git a/src/igl/rows_to_matrix.cpp b/src/libigl/igl/rows_to_matrix.cpp similarity index 100% rename from src/igl/rows_to_matrix.cpp rename to src/libigl/igl/rows_to_matrix.cpp diff --git a/src/igl/rows_to_matrix.h b/src/libigl/igl/rows_to_matrix.h similarity index 100% rename from src/igl/rows_to_matrix.h rename to src/libigl/igl/rows_to_matrix.h diff --git a/src/igl/sample_edges.cpp b/src/libigl/igl/sample_edges.cpp similarity index 100% rename from src/igl/sample_edges.cpp rename to src/libigl/igl/sample_edges.cpp diff --git a/src/igl/sample_edges.h b/src/libigl/igl/sample_edges.h similarity index 100% rename from src/igl/sample_edges.h rename to src/libigl/igl/sample_edges.h diff --git a/src/igl/seam_edges.cpp b/src/libigl/igl/seam_edges.cpp similarity index 100% rename from src/igl/seam_edges.cpp rename to src/libigl/igl/seam_edges.cpp diff --git a/src/igl/seam_edges.h b/src/libigl/igl/seam_edges.h similarity index 100% rename from src/igl/seam_edges.h rename to src/libigl/igl/seam_edges.h diff --git a/src/igl/segment_segment_intersect.cpp b/src/libigl/igl/segment_segment_intersect.cpp similarity index 100% rename from src/igl/segment_segment_intersect.cpp rename to src/libigl/igl/segment_segment_intersect.cpp diff --git a/src/igl/segment_segment_intersect.h b/src/libigl/igl/segment_segment_intersect.h similarity index 100% rename from src/igl/segment_segment_intersect.h rename to src/libigl/igl/segment_segment_intersect.h diff --git a/src/igl/serialize.h b/src/libigl/igl/serialize.h similarity index 100% rename from src/igl/serialize.h rename to src/libigl/igl/serialize.h diff --git a/src/igl/setdiff.cpp b/src/libigl/igl/setdiff.cpp similarity index 100% rename from src/igl/setdiff.cpp rename to src/libigl/igl/setdiff.cpp diff --git a/src/igl/setdiff.h b/src/libigl/igl/setdiff.h similarity index 100% rename from src/igl/setdiff.h rename to src/libigl/igl/setdiff.h diff --git a/src/igl/setunion.cpp b/src/libigl/igl/setunion.cpp similarity index 100% rename from src/igl/setunion.cpp rename to src/libigl/igl/setunion.cpp diff --git a/src/igl/setunion.h b/src/libigl/igl/setunion.h similarity index 100% rename from src/igl/setunion.h rename to src/libigl/igl/setunion.h diff --git a/src/igl/setxor.cpp b/src/libigl/igl/setxor.cpp similarity index 100% rename from src/igl/setxor.cpp rename to src/libigl/igl/setxor.cpp diff --git a/src/igl/setxor.h b/src/libigl/igl/setxor.h similarity index 100% rename from src/igl/setxor.h rename to src/libigl/igl/setxor.h diff --git a/src/igl/shape_diameter_function.cpp b/src/libigl/igl/shape_diameter_function.cpp similarity index 100% rename from src/igl/shape_diameter_function.cpp rename to src/libigl/igl/shape_diameter_function.cpp diff --git a/src/igl/shape_diameter_function.h b/src/libigl/igl/shape_diameter_function.h similarity index 100% rename from src/igl/shape_diameter_function.h rename to src/libigl/igl/shape_diameter_function.h diff --git a/src/igl/shapeup.cpp b/src/libigl/igl/shapeup.cpp similarity index 100% rename from src/igl/shapeup.cpp rename to src/libigl/igl/shapeup.cpp diff --git a/src/igl/shapeup.h b/src/libigl/igl/shapeup.h similarity index 100% rename from src/igl/shapeup.h rename to src/libigl/igl/shapeup.h diff --git a/src/igl/shortest_edge_and_midpoint.cpp b/src/libigl/igl/shortest_edge_and_midpoint.cpp similarity index 100% rename from src/igl/shortest_edge_and_midpoint.cpp rename to src/libigl/igl/shortest_edge_and_midpoint.cpp diff --git a/src/igl/shortest_edge_and_midpoint.h b/src/libigl/igl/shortest_edge_and_midpoint.h similarity index 100% rename from src/igl/shortest_edge_and_midpoint.h rename to src/libigl/igl/shortest_edge_and_midpoint.h diff --git a/src/igl/signed_angle.cpp b/src/libigl/igl/signed_angle.cpp similarity index 100% rename from src/igl/signed_angle.cpp rename to src/libigl/igl/signed_angle.cpp diff --git a/src/igl/signed_angle.h b/src/libigl/igl/signed_angle.h similarity index 100% rename from src/igl/signed_angle.h rename to src/libigl/igl/signed_angle.h diff --git a/src/igl/signed_distance.cpp b/src/libigl/igl/signed_distance.cpp similarity index 100% rename from src/igl/signed_distance.cpp rename to src/libigl/igl/signed_distance.cpp diff --git a/src/igl/signed_distance.h b/src/libigl/igl/signed_distance.h similarity index 100% rename from src/igl/signed_distance.h rename to src/libigl/igl/signed_distance.h diff --git a/src/igl/simplify_polyhedron.cpp b/src/libigl/igl/simplify_polyhedron.cpp similarity index 100% rename from src/igl/simplify_polyhedron.cpp rename to src/libigl/igl/simplify_polyhedron.cpp diff --git a/src/igl/simplify_polyhedron.h b/src/libigl/igl/simplify_polyhedron.h similarity index 100% rename from src/igl/simplify_polyhedron.h rename to src/libigl/igl/simplify_polyhedron.h diff --git a/src/igl/slice.cpp b/src/libigl/igl/slice.cpp similarity index 100% rename from src/igl/slice.cpp rename to src/libigl/igl/slice.cpp diff --git a/src/igl/slice.h b/src/libigl/igl/slice.h similarity index 100% rename from src/igl/slice.h rename to src/libigl/igl/slice.h diff --git a/src/igl/slice_cached.cpp b/src/libigl/igl/slice_cached.cpp similarity index 100% rename from src/igl/slice_cached.cpp rename to src/libigl/igl/slice_cached.cpp diff --git a/src/igl/slice_cached.h b/src/libigl/igl/slice_cached.h similarity index 100% rename from src/igl/slice_cached.h rename to src/libigl/igl/slice_cached.h diff --git a/src/igl/slice_into.cpp b/src/libigl/igl/slice_into.cpp similarity index 100% rename from src/igl/slice_into.cpp rename to src/libigl/igl/slice_into.cpp diff --git a/src/igl/slice_into.h b/src/libigl/igl/slice_into.h similarity index 100% rename from src/igl/slice_into.h rename to src/libigl/igl/slice_into.h diff --git a/src/igl/slice_mask.cpp b/src/libigl/igl/slice_mask.cpp similarity index 100% rename from src/igl/slice_mask.cpp rename to src/libigl/igl/slice_mask.cpp diff --git a/src/igl/slice_mask.h b/src/libigl/igl/slice_mask.h similarity index 100% rename from src/igl/slice_mask.h rename to src/libigl/igl/slice_mask.h diff --git a/src/igl/slice_tets.cpp b/src/libigl/igl/slice_tets.cpp similarity index 100% rename from src/igl/slice_tets.cpp rename to src/libigl/igl/slice_tets.cpp diff --git a/src/igl/slice_tets.h b/src/libigl/igl/slice_tets.h similarity index 100% rename from src/igl/slice_tets.h rename to src/libigl/igl/slice_tets.h diff --git a/src/igl/slim.cpp b/src/libigl/igl/slim.cpp similarity index 100% rename from src/igl/slim.cpp rename to src/libigl/igl/slim.cpp diff --git a/src/igl/slim.h b/src/libigl/igl/slim.h similarity index 100% rename from src/igl/slim.h rename to src/libigl/igl/slim.h diff --git a/src/igl/snap_points.cpp b/src/libigl/igl/snap_points.cpp similarity index 100% rename from src/igl/snap_points.cpp rename to src/libigl/igl/snap_points.cpp diff --git a/src/igl/snap_points.h b/src/libigl/igl/snap_points.h similarity index 100% rename from src/igl/snap_points.h rename to src/libigl/igl/snap_points.h diff --git a/src/igl/snap_to_canonical_view_quat.cpp b/src/libigl/igl/snap_to_canonical_view_quat.cpp similarity index 100% rename from src/igl/snap_to_canonical_view_quat.cpp rename to src/libigl/igl/snap_to_canonical_view_quat.cpp diff --git a/src/igl/snap_to_canonical_view_quat.h b/src/libigl/igl/snap_to_canonical_view_quat.h similarity index 100% rename from src/igl/snap_to_canonical_view_quat.h rename to src/libigl/igl/snap_to_canonical_view_quat.h diff --git a/src/igl/snap_to_fixed_up.cpp b/src/libigl/igl/snap_to_fixed_up.cpp similarity index 100% rename from src/igl/snap_to_fixed_up.cpp rename to src/libigl/igl/snap_to_fixed_up.cpp diff --git a/src/igl/snap_to_fixed_up.h b/src/libigl/igl/snap_to_fixed_up.h similarity index 100% rename from src/igl/snap_to_fixed_up.h rename to src/libigl/igl/snap_to_fixed_up.h diff --git a/src/igl/solid_angle.cpp b/src/libigl/igl/solid_angle.cpp similarity index 100% rename from src/igl/solid_angle.cpp rename to src/libigl/igl/solid_angle.cpp diff --git a/src/igl/solid_angle.h b/src/libigl/igl/solid_angle.h similarity index 100% rename from src/igl/solid_angle.h rename to src/libigl/igl/solid_angle.h diff --git a/src/igl/sort.cpp b/src/libigl/igl/sort.cpp similarity index 100% rename from src/igl/sort.cpp rename to src/libigl/igl/sort.cpp diff --git a/src/igl/sort.h b/src/libigl/igl/sort.h similarity index 100% rename from src/igl/sort.h rename to src/libigl/igl/sort.h diff --git a/src/igl/sort_angles.cpp b/src/libigl/igl/sort_angles.cpp similarity index 100% rename from src/igl/sort_angles.cpp rename to src/libigl/igl/sort_angles.cpp diff --git a/src/igl/sort_angles.h b/src/libigl/igl/sort_angles.h similarity index 100% rename from src/igl/sort_angles.h rename to src/libigl/igl/sort_angles.h diff --git a/src/igl/sort_triangles.cpp b/src/libigl/igl/sort_triangles.cpp similarity index 100% rename from src/igl/sort_triangles.cpp rename to src/libigl/igl/sort_triangles.cpp diff --git a/src/igl/sort_triangles.h b/src/libigl/igl/sort_triangles.h similarity index 100% rename from src/igl/sort_triangles.h rename to src/libigl/igl/sort_triangles.h diff --git a/src/igl/sort_vectors_ccw.cpp b/src/libigl/igl/sort_vectors_ccw.cpp similarity index 100% rename from src/igl/sort_vectors_ccw.cpp rename to src/libigl/igl/sort_vectors_ccw.cpp diff --git a/src/igl/sort_vectors_ccw.h b/src/libigl/igl/sort_vectors_ccw.h similarity index 100% rename from src/igl/sort_vectors_ccw.h rename to src/libigl/igl/sort_vectors_ccw.h diff --git a/src/igl/sortrows.cpp b/src/libigl/igl/sortrows.cpp similarity index 100% rename from src/igl/sortrows.cpp rename to src/libigl/igl/sortrows.cpp diff --git a/src/igl/sortrows.h b/src/libigl/igl/sortrows.h similarity index 100% rename from src/igl/sortrows.h rename to src/libigl/igl/sortrows.h diff --git a/src/igl/sparse.cpp b/src/libigl/igl/sparse.cpp similarity index 100% rename from src/igl/sparse.cpp rename to src/libigl/igl/sparse.cpp diff --git a/src/igl/sparse.h b/src/libigl/igl/sparse.h similarity index 100% rename from src/igl/sparse.h rename to src/libigl/igl/sparse.h diff --git a/src/igl/sparse_cached.cpp b/src/libigl/igl/sparse_cached.cpp similarity index 100% rename from src/igl/sparse_cached.cpp rename to src/libigl/igl/sparse_cached.cpp diff --git a/src/igl/sparse_cached.h b/src/libigl/igl/sparse_cached.h similarity index 100% rename from src/igl/sparse_cached.h rename to src/libigl/igl/sparse_cached.h diff --git a/src/igl/speye.cpp b/src/libigl/igl/speye.cpp similarity index 100% rename from src/igl/speye.cpp rename to src/libigl/igl/speye.cpp diff --git a/src/igl/speye.h b/src/libigl/igl/speye.h similarity index 100% rename from src/igl/speye.h rename to src/libigl/igl/speye.h diff --git a/src/igl/squared_edge_lengths.cpp b/src/libigl/igl/squared_edge_lengths.cpp similarity index 100% rename from src/igl/squared_edge_lengths.cpp rename to src/libigl/igl/squared_edge_lengths.cpp diff --git a/src/igl/squared_edge_lengths.h b/src/libigl/igl/squared_edge_lengths.h similarity index 100% rename from src/igl/squared_edge_lengths.h rename to src/libigl/igl/squared_edge_lengths.h diff --git a/src/igl/stdin_to_temp.cpp b/src/libigl/igl/stdin_to_temp.cpp similarity index 100% rename from src/igl/stdin_to_temp.cpp rename to src/libigl/igl/stdin_to_temp.cpp diff --git a/src/igl/stdin_to_temp.h b/src/libigl/igl/stdin_to_temp.h similarity index 100% rename from src/igl/stdin_to_temp.h rename to src/libigl/igl/stdin_to_temp.h diff --git a/src/igl/straighten_seams.cpp b/src/libigl/igl/straighten_seams.cpp similarity index 100% rename from src/igl/straighten_seams.cpp rename to src/libigl/igl/straighten_seams.cpp diff --git a/src/igl/straighten_seams.h b/src/libigl/igl/straighten_seams.h similarity index 100% rename from src/igl/straighten_seams.h rename to src/libigl/igl/straighten_seams.h diff --git a/src/igl/sum.cpp b/src/libigl/igl/sum.cpp similarity index 100% rename from src/igl/sum.cpp rename to src/libigl/igl/sum.cpp diff --git a/src/igl/sum.h b/src/libigl/igl/sum.h similarity index 100% rename from src/igl/sum.h rename to src/libigl/igl/sum.h diff --git a/src/igl/svd3x3.cpp b/src/libigl/igl/svd3x3.cpp similarity index 100% rename from src/igl/svd3x3.cpp rename to src/libigl/igl/svd3x3.cpp diff --git a/src/igl/svd3x3.h b/src/libigl/igl/svd3x3.h similarity index 100% rename from src/igl/svd3x3.h rename to src/libigl/igl/svd3x3.h diff --git a/src/igl/svd3x3_avx.cpp b/src/libigl/igl/svd3x3_avx.cpp similarity index 100% rename from src/igl/svd3x3_avx.cpp rename to src/libigl/igl/svd3x3_avx.cpp diff --git a/src/igl/svd3x3_avx.h b/src/libigl/igl/svd3x3_avx.h similarity index 100% rename from src/igl/svd3x3_avx.h rename to src/libigl/igl/svd3x3_avx.h diff --git a/src/igl/svd3x3_sse.cpp b/src/libigl/igl/svd3x3_sse.cpp similarity index 100% rename from src/igl/svd3x3_sse.cpp rename to src/libigl/igl/svd3x3_sse.cpp diff --git a/src/igl/svd3x3_sse.h b/src/libigl/igl/svd3x3_sse.h similarity index 100% rename from src/igl/svd3x3_sse.h rename to src/libigl/igl/svd3x3_sse.h diff --git a/src/igl/swept_volume_bounding_box.cpp b/src/libigl/igl/swept_volume_bounding_box.cpp similarity index 100% rename from src/igl/swept_volume_bounding_box.cpp rename to src/libigl/igl/swept_volume_bounding_box.cpp diff --git a/src/igl/swept_volume_bounding_box.h b/src/libigl/igl/swept_volume_bounding_box.h similarity index 100% rename from src/igl/swept_volume_bounding_box.h rename to src/libigl/igl/swept_volume_bounding_box.h diff --git a/src/igl/swept_volume_signed_distance.cpp b/src/libigl/igl/swept_volume_signed_distance.cpp similarity index 100% rename from src/igl/swept_volume_signed_distance.cpp rename to src/libigl/igl/swept_volume_signed_distance.cpp diff --git a/src/igl/swept_volume_signed_distance.h b/src/libigl/igl/swept_volume_signed_distance.h similarity index 100% rename from src/igl/swept_volume_signed_distance.h rename to src/libigl/igl/swept_volume_signed_distance.h diff --git a/src/igl/trackball.cpp b/src/libigl/igl/trackball.cpp similarity index 100% rename from src/igl/trackball.cpp rename to src/libigl/igl/trackball.cpp diff --git a/src/igl/trackball.h b/src/libigl/igl/trackball.h similarity index 100% rename from src/igl/trackball.h rename to src/libigl/igl/trackball.h diff --git a/src/igl/transpose_blocks.cpp b/src/libigl/igl/transpose_blocks.cpp similarity index 100% rename from src/igl/transpose_blocks.cpp rename to src/libigl/igl/transpose_blocks.cpp diff --git a/src/igl/transpose_blocks.h b/src/libigl/igl/transpose_blocks.h similarity index 100% rename from src/igl/transpose_blocks.h rename to src/libigl/igl/transpose_blocks.h diff --git a/src/igl/triangle/cdt.cpp b/src/libigl/igl/triangle/cdt.cpp similarity index 100% rename from src/igl/triangle/cdt.cpp rename to src/libigl/igl/triangle/cdt.cpp diff --git a/src/igl/triangle/cdt.h b/src/libigl/igl/triangle/cdt.h similarity index 100% rename from src/igl/triangle/cdt.h rename to src/libigl/igl/triangle/cdt.h diff --git a/src/igl/triangle/triangulate.cpp b/src/libigl/igl/triangle/triangulate.cpp similarity index 100% rename from src/igl/triangle/triangulate.cpp rename to src/libigl/igl/triangle/triangulate.cpp diff --git a/src/igl/triangle/triangulate.h b/src/libigl/igl/triangle/triangulate.h similarity index 100% rename from src/igl/triangle/triangulate.h rename to src/libigl/igl/triangle/triangulate.h diff --git a/src/igl/triangle_fan.cpp b/src/libigl/igl/triangle_fan.cpp similarity index 100% rename from src/igl/triangle_fan.cpp rename to src/libigl/igl/triangle_fan.cpp diff --git a/src/igl/triangle_fan.h b/src/libigl/igl/triangle_fan.h similarity index 100% rename from src/igl/triangle_fan.h rename to src/libigl/igl/triangle_fan.h diff --git a/src/igl/triangle_triangle_adjacency.cpp b/src/libigl/igl/triangle_triangle_adjacency.cpp similarity index 100% rename from src/igl/triangle_triangle_adjacency.cpp rename to src/libigl/igl/triangle_triangle_adjacency.cpp diff --git a/src/igl/triangle_triangle_adjacency.h b/src/libigl/igl/triangle_triangle_adjacency.h similarity index 100% rename from src/igl/triangle_triangle_adjacency.h rename to src/libigl/igl/triangle_triangle_adjacency.h diff --git a/src/igl/triangles_from_strip.cpp b/src/libigl/igl/triangles_from_strip.cpp similarity index 100% rename from src/igl/triangles_from_strip.cpp rename to src/libigl/igl/triangles_from_strip.cpp diff --git a/src/igl/triangles_from_strip.h b/src/libigl/igl/triangles_from_strip.h similarity index 100% rename from src/igl/triangles_from_strip.h rename to src/libigl/igl/triangles_from_strip.h diff --git a/src/igl/two_axis_valuator_fixed_up.cpp b/src/libigl/igl/two_axis_valuator_fixed_up.cpp similarity index 100% rename from src/igl/two_axis_valuator_fixed_up.cpp rename to src/libigl/igl/two_axis_valuator_fixed_up.cpp diff --git a/src/igl/two_axis_valuator_fixed_up.h b/src/libigl/igl/two_axis_valuator_fixed_up.h similarity index 100% rename from src/igl/two_axis_valuator_fixed_up.h rename to src/libigl/igl/two_axis_valuator_fixed_up.h diff --git a/src/igl/uniformly_sample_two_manifold.cpp b/src/libigl/igl/uniformly_sample_two_manifold.cpp similarity index 100% rename from src/igl/uniformly_sample_two_manifold.cpp rename to src/libigl/igl/uniformly_sample_two_manifold.cpp diff --git a/src/igl/uniformly_sample_two_manifold.h b/src/libigl/igl/uniformly_sample_two_manifold.h similarity index 100% rename from src/igl/uniformly_sample_two_manifold.h rename to src/libigl/igl/uniformly_sample_two_manifold.h diff --git a/src/igl/unique.cpp b/src/libigl/igl/unique.cpp similarity index 100% rename from src/igl/unique.cpp rename to src/libigl/igl/unique.cpp diff --git a/src/igl/unique.h b/src/libigl/igl/unique.h similarity index 100% rename from src/igl/unique.h rename to src/libigl/igl/unique.h diff --git a/src/igl/unique_edge_map.cpp b/src/libigl/igl/unique_edge_map.cpp similarity index 100% rename from src/igl/unique_edge_map.cpp rename to src/libigl/igl/unique_edge_map.cpp diff --git a/src/igl/unique_edge_map.h b/src/libigl/igl/unique_edge_map.h similarity index 100% rename from src/igl/unique_edge_map.h rename to src/libigl/igl/unique_edge_map.h diff --git a/src/igl/unique_rows.cpp b/src/libigl/igl/unique_rows.cpp similarity index 100% rename from src/igl/unique_rows.cpp rename to src/libigl/igl/unique_rows.cpp diff --git a/src/igl/unique_rows.h b/src/libigl/igl/unique_rows.h similarity index 100% rename from src/igl/unique_rows.h rename to src/libigl/igl/unique_rows.h diff --git a/src/igl/unique_simplices.cpp b/src/libigl/igl/unique_simplices.cpp similarity index 100% rename from src/igl/unique_simplices.cpp rename to src/libigl/igl/unique_simplices.cpp diff --git a/src/igl/unique_simplices.h b/src/libigl/igl/unique_simplices.h similarity index 100% rename from src/igl/unique_simplices.h rename to src/libigl/igl/unique_simplices.h diff --git a/src/igl/unproject.cpp b/src/libigl/igl/unproject.cpp similarity index 100% rename from src/igl/unproject.cpp rename to src/libigl/igl/unproject.cpp diff --git a/src/igl/unproject.h b/src/libigl/igl/unproject.h similarity index 100% rename from src/igl/unproject.h rename to src/libigl/igl/unproject.h diff --git a/src/igl/unproject_in_mesh.cpp b/src/libigl/igl/unproject_in_mesh.cpp similarity index 100% rename from src/igl/unproject_in_mesh.cpp rename to src/libigl/igl/unproject_in_mesh.cpp diff --git a/src/igl/unproject_in_mesh.h b/src/libigl/igl/unproject_in_mesh.h similarity index 100% rename from src/igl/unproject_in_mesh.h rename to src/libigl/igl/unproject_in_mesh.h diff --git a/src/igl/unproject_onto_mesh.cpp b/src/libigl/igl/unproject_onto_mesh.cpp similarity index 100% rename from src/igl/unproject_onto_mesh.cpp rename to src/libigl/igl/unproject_onto_mesh.cpp diff --git a/src/igl/unproject_onto_mesh.h b/src/libigl/igl/unproject_onto_mesh.h similarity index 100% rename from src/igl/unproject_onto_mesh.h rename to src/libigl/igl/unproject_onto_mesh.h diff --git a/src/igl/unproject_ray.cpp b/src/libigl/igl/unproject_ray.cpp similarity index 100% rename from src/igl/unproject_ray.cpp rename to src/libigl/igl/unproject_ray.cpp diff --git a/src/igl/unproject_ray.h b/src/libigl/igl/unproject_ray.h similarity index 100% rename from src/igl/unproject_ray.h rename to src/libigl/igl/unproject_ray.h diff --git a/src/igl/unzip_corners.cpp b/src/libigl/igl/unzip_corners.cpp similarity index 100% rename from src/igl/unzip_corners.cpp rename to src/libigl/igl/unzip_corners.cpp diff --git a/src/igl/unzip_corners.h b/src/libigl/igl/unzip_corners.h similarity index 100% rename from src/igl/unzip_corners.h rename to src/libigl/igl/unzip_corners.h diff --git a/src/igl/upsample.cpp b/src/libigl/igl/upsample.cpp similarity index 100% rename from src/igl/upsample.cpp rename to src/libigl/igl/upsample.cpp diff --git a/src/igl/upsample.h b/src/libigl/igl/upsample.h similarity index 100% rename from src/igl/upsample.h rename to src/libigl/igl/upsample.h diff --git a/src/igl/vector_area_matrix.cpp b/src/libigl/igl/vector_area_matrix.cpp similarity index 100% rename from src/igl/vector_area_matrix.cpp rename to src/libigl/igl/vector_area_matrix.cpp diff --git a/src/igl/vector_area_matrix.h b/src/libigl/igl/vector_area_matrix.h similarity index 100% rename from src/igl/vector_area_matrix.h rename to src/libigl/igl/vector_area_matrix.h diff --git a/src/igl/verbose.h b/src/libigl/igl/verbose.h similarity index 100% rename from src/igl/verbose.h rename to src/libigl/igl/verbose.h diff --git a/src/igl/vertex_triangle_adjacency.cpp b/src/libigl/igl/vertex_triangle_adjacency.cpp similarity index 100% rename from src/igl/vertex_triangle_adjacency.cpp rename to src/libigl/igl/vertex_triangle_adjacency.cpp diff --git a/src/igl/vertex_triangle_adjacency.h b/src/libigl/igl/vertex_triangle_adjacency.h similarity index 100% rename from src/igl/vertex_triangle_adjacency.h rename to src/libigl/igl/vertex_triangle_adjacency.h diff --git a/src/igl/volume.cpp b/src/libigl/igl/volume.cpp similarity index 100% rename from src/igl/volume.cpp rename to src/libigl/igl/volume.cpp diff --git a/src/igl/volume.h b/src/libigl/igl/volume.h similarity index 100% rename from src/igl/volume.h rename to src/libigl/igl/volume.h diff --git a/src/igl/voxel_grid.cpp b/src/libigl/igl/voxel_grid.cpp similarity index 100% rename from src/igl/voxel_grid.cpp rename to src/libigl/igl/voxel_grid.cpp diff --git a/src/igl/voxel_grid.h b/src/libigl/igl/voxel_grid.h similarity index 100% rename from src/igl/voxel_grid.h rename to src/libigl/igl/voxel_grid.h diff --git a/src/igl/winding_number.cpp b/src/libigl/igl/winding_number.cpp similarity index 100% rename from src/igl/winding_number.cpp rename to src/libigl/igl/winding_number.cpp diff --git a/src/igl/winding_number.h b/src/libigl/igl/winding_number.h similarity index 100% rename from src/igl/winding_number.h rename to src/libigl/igl/winding_number.h diff --git a/src/igl/writeBF.cpp b/src/libigl/igl/writeBF.cpp similarity index 100% rename from src/igl/writeBF.cpp rename to src/libigl/igl/writeBF.cpp diff --git a/src/igl/writeBF.h b/src/libigl/igl/writeBF.h similarity index 100% rename from src/igl/writeBF.h rename to src/libigl/igl/writeBF.h diff --git a/src/igl/writeDMAT.cpp b/src/libigl/igl/writeDMAT.cpp similarity index 100% rename from src/igl/writeDMAT.cpp rename to src/libigl/igl/writeDMAT.cpp diff --git a/src/igl/writeDMAT.h b/src/libigl/igl/writeDMAT.h similarity index 100% rename from src/igl/writeDMAT.h rename to src/libigl/igl/writeDMAT.h diff --git a/src/igl/writeMESH.cpp b/src/libigl/igl/writeMESH.cpp similarity index 100% rename from src/igl/writeMESH.cpp rename to src/libigl/igl/writeMESH.cpp diff --git a/src/igl/writeMESH.h b/src/libigl/igl/writeMESH.h similarity index 100% rename from src/igl/writeMESH.h rename to src/libigl/igl/writeMESH.h diff --git a/src/igl/writeOBJ.cpp b/src/libigl/igl/writeOBJ.cpp similarity index 100% rename from src/igl/writeOBJ.cpp rename to src/libigl/igl/writeOBJ.cpp diff --git a/src/igl/writeOBJ.h b/src/libigl/igl/writeOBJ.h similarity index 100% rename from src/igl/writeOBJ.h rename to src/libigl/igl/writeOBJ.h diff --git a/src/igl/writeOFF.cpp b/src/libigl/igl/writeOFF.cpp similarity index 100% rename from src/igl/writeOFF.cpp rename to src/libigl/igl/writeOFF.cpp diff --git a/src/igl/writeOFF.h b/src/libigl/igl/writeOFF.h similarity index 100% rename from src/igl/writeOFF.h rename to src/libigl/igl/writeOFF.h diff --git a/src/igl/writePLY.cpp b/src/libigl/igl/writePLY.cpp similarity index 100% rename from src/igl/writePLY.cpp rename to src/libigl/igl/writePLY.cpp diff --git a/src/igl/writePLY.h b/src/libigl/igl/writePLY.h similarity index 100% rename from src/igl/writePLY.h rename to src/libigl/igl/writePLY.h diff --git a/src/igl/writeSTL.cpp b/src/libigl/igl/writeSTL.cpp similarity index 100% rename from src/igl/writeSTL.cpp rename to src/libigl/igl/writeSTL.cpp diff --git a/src/igl/writeSTL.h b/src/libigl/igl/writeSTL.h similarity index 100% rename from src/igl/writeSTL.h rename to src/libigl/igl/writeSTL.h diff --git a/src/igl/writeTGF.cpp b/src/libigl/igl/writeTGF.cpp similarity index 100% rename from src/igl/writeTGF.cpp rename to src/libigl/igl/writeTGF.cpp diff --git a/src/igl/writeTGF.h b/src/libigl/igl/writeTGF.h similarity index 100% rename from src/igl/writeTGF.h rename to src/libigl/igl/writeTGF.h diff --git a/src/igl/writeWRL.cpp b/src/libigl/igl/writeWRL.cpp similarity index 100% rename from src/igl/writeWRL.cpp rename to src/libigl/igl/writeWRL.cpp diff --git a/src/igl/writeWRL.h b/src/libigl/igl/writeWRL.h similarity index 100% rename from src/igl/writeWRL.h rename to src/libigl/igl/writeWRL.h diff --git a/src/igl/write_triangle_mesh.cpp b/src/libigl/igl/write_triangle_mesh.cpp similarity index 100% rename from src/igl/write_triangle_mesh.cpp rename to src/libigl/igl/write_triangle_mesh.cpp diff --git a/src/igl/write_triangle_mesh.h b/src/libigl/igl/write_triangle_mesh.h similarity index 100% rename from src/igl/write_triangle_mesh.h rename to src/libigl/igl/write_triangle_mesh.h diff --git a/src/igl/xml/ReAntTweakBarXMLSerialization.h b/src/libigl/igl/xml/ReAntTweakBarXMLSerialization.h similarity index 100% rename from src/igl/xml/ReAntTweakBarXMLSerialization.h rename to src/libigl/igl/xml/ReAntTweakBarXMLSerialization.h diff --git a/src/igl/xml/XMLSerializable.h b/src/libigl/igl/xml/XMLSerializable.h similarity index 100% rename from src/igl/xml/XMLSerializable.h rename to src/libigl/igl/xml/XMLSerializable.h diff --git a/src/igl/xml/serialization_test.skip b/src/libigl/igl/xml/serialization_test.skip similarity index 100% rename from src/igl/xml/serialization_test.skip rename to src/libigl/igl/xml/serialization_test.skip diff --git a/src/igl/xml/serialize_xml.cpp b/src/libigl/igl/xml/serialize_xml.cpp similarity index 100% rename from src/igl/xml/serialize_xml.cpp rename to src/libigl/igl/xml/serialize_xml.cpp diff --git a/src/igl/xml/serialize_xml.h b/src/libigl/igl/xml/serialize_xml.h similarity index 100% rename from src/igl/xml/serialize_xml.h rename to src/libigl/igl/xml/serialize_xml.h diff --git a/src/igl/xml/writeDAE.cpp b/src/libigl/igl/xml/writeDAE.cpp similarity index 100% rename from src/igl/xml/writeDAE.cpp rename to src/libigl/igl/xml/writeDAE.cpp diff --git a/src/igl/xml/writeDAE.h b/src/libigl/igl/xml/writeDAE.h similarity index 100% rename from src/igl/xml/writeDAE.h rename to src/libigl/igl/xml/writeDAE.h diff --git a/src/igl/xml/write_triangle_mesh.cpp b/src/libigl/igl/xml/write_triangle_mesh.cpp similarity index 100% rename from src/igl/xml/write_triangle_mesh.cpp rename to src/libigl/igl/xml/write_triangle_mesh.cpp diff --git a/src/igl/xml/write_triangle_mesh.h b/src/libigl/igl/xml/write_triangle_mesh.h similarity index 100% rename from src/igl/xml/write_triangle_mesh.h rename to src/libigl/igl/xml/write_triangle_mesh.h diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 38e6636043..dc52257aa2 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -189,6 +189,7 @@ target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNE target_link_libraries(libslic3r libnest2d admesh + libigl miniz boost_libs clipper From 8ffd79cdd84c1df8f0d3d0b3adc9b7e3ec65152a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 19 Jun 2019 16:23:53 +0200 Subject: [PATCH 159/627] Fix for debug build on Visual Studio --- src/libnest2d/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index d50c78b171..587b814b24 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -74,7 +74,10 @@ if(TBB_FOUND) target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0) find_package(Threads REQUIRED) - target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS} + target_link_libraries(libnest2d INTERFACE + tbb # VS debug mode needs linking this way: + # ${TBB_LIBRARIES} + ${CMAKE_DL_LIBS} Threads::Threads ) else() From 7f8bb94a55fab885b32f17bd469d929f5df34908 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 19 Jun 2019 16:28:56 +0200 Subject: [PATCH 160/627] Fixed wxEVT_CHAR handling for DoubleSlider --- src/slic3r/GUI/wxExtensions.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index fc34497606..7b7178a824 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1587,6 +1587,7 @@ DoubleSlider::DoubleSlider( wxWindow *parent, // slider events Bind(wxEVT_PAINT, &DoubleSlider::OnPaint, this); + Bind(wxEVT_CHAR, &DoubleSlider::OnChar, this); Bind(wxEVT_LEFT_DOWN, &DoubleSlider::OnLeftDown, this); Bind(wxEVT_MOTION, &DoubleSlider::OnMotion, this); Bind(wxEVT_LEFT_UP, &DoubleSlider::OnLeftUp, this); @@ -2387,6 +2388,8 @@ void DoubleSlider::OnKeyDown(wxKeyEvent &event) else if (key == WXK_UP || key == WXK_DOWN) move_current_thumb(key == WXK_UP); } + + event.Skip(); // !Needed to have EVT_CHAR generated as well } void DoubleSlider::OnKeyUp(wxKeyEvent &event) From b7f67369c99cf204d8071b1d42abc3c2704f1d82 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 20 Jun 2019 10:02:52 +0200 Subject: [PATCH 161/627] Serialized camera type and fixed Mac build --- src/slic3r/GUI/AppConfig.cpp | 3 +++ src/slic3r/GUI/Camera.cpp | 28 ++++++++++++++++++++++++---- src/slic3r/GUI/Camera.hpp | 8 ++++++-- src/slic3r/GUI/GLCanvas3D.cpp | 7 +++---- src/slic3r/GUI/Plater.cpp | 3 +++ 5 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index d4970880b5..edc9845a13 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -73,6 +73,9 @@ void AppConfig::set_defaults() if (get("custom_toolbar_size").empty()) set("custom_toolbar_size", "100"); + if (get("camera_type").empty()) + set("camera_type", "1"); + // Remove legacy window positions/sizes erase("", "main_frame_maximized"); erase("", "main_frame_pos"); diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index a3ecb5e541..b8c182ef4a 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -2,9 +2,8 @@ #include "Camera.hpp" #include "3DScene.hpp" -#if ENABLE_CAMERA_STATISTICS #include "GUI_App.hpp" -#endif // ENABLE_CAMERA_STATISTICS +#include "AppConfig.hpp" #include @@ -44,23 +43,44 @@ std::string Camera::get_type_as_string() const { switch (m_type) { - default: case Unknown: return "unknown"; case Perspective: return "perspective"; + default: case Ortho: return "orthographic"; }; } +void Camera::set_type(EType type) +{ + if (m_type != type) + { + m_type = type; + + wxGetApp().app_config->set("camera_type", std::to_string(m_type)); + wxGetApp().app_config->save(); + } +} + +void Camera::set_type(const std::string& type) +{ + if (!type.empty() && (type != "1")) + { + unsigned char type_id = atoi(type.c_str()); + if (((unsigned char)Ortho < type_id) && (type_id < (unsigned char)Num_types)) + set_type((Camera::EType)type_id); + } +} + void Camera::select_next_type() { unsigned char next = (unsigned char)m_type + 1; if (next == (unsigned char)Num_types) next = 1; - m_type = (EType)next; + set_type((EType)next); } void Camera::set_target(const Vec3d& target) diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index fe6571b056..a64b194f3a 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -16,8 +16,8 @@ struct Camera enum EType : unsigned char { Unknown, - Perspective, Ortho, + Perspective, Num_types }; @@ -45,7 +45,8 @@ public: EType get_type() const { return m_type; } std::string get_type_as_string() const; - void set_type(EType type) { m_type = type; } + void set_type(EType type); + void set_type(const std::string& type); void select_next_type(); const Vec3d& get_target() const { return m_target; } @@ -56,6 +57,9 @@ public: double get_zoom() const { return m_zoom; } void set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h); +#if ENABLE_RETINA_GL + void set_zoom(double zoom) { m_zoom = zoom; } +#endif // ENABLE_RETINA_GL const BoundingBoxf3& get_scene_box() const { return m_scene_box; } void set_scene_box(const BoundingBoxf3& box) { m_scene_box = box; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9216eff944..d9a8a870ce 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1593,8 +1593,8 @@ void GLCanvas3D::render() if (m_camera.requires_zoom_to_bed) { zoom_to_bed(); -// const Size& cnv_size = get_canvas_size(); -// _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); + const Size& cnv_size = get_canvas_size(); + _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); m_camera.requires_zoom_to_bed = false; } @@ -3304,8 +3304,7 @@ void GLCanvas3D::update_ui_from_settings() if (new_scaling != orig_scaling) { BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Scaling factor: " << new_scaling; - m_camera.zoom /= orig_scaling; - m_camera.zoom *= new_scaling; + m_camera.set_zoom(m_camera.get_zoom() * new_scaling / orig_scaling); _refresh_if_shown_on_screen(); } #endif diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f8385c0b58..c2456db800 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1772,6 +1772,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) q->Layout(); set_current_panel(view3D); + + // updates camera type from .ini file + camera.set_type(get_config("camera_type")); } void Plater::priv::update(bool force_full_scene_refresh) From e5be8adadf72e0f283a602a412c31eda564d8b2e Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 20 Jun 2019 11:05:05 +0200 Subject: [PATCH 162/627] Fixed build on MacOS --- src/slic3r/GUI/Camera.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index a64b194f3a..6a2f65a30e 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -57,9 +57,9 @@ public: double get_zoom() const { return m_zoom; } void set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h); -#if ENABLE_RETINA_GL + // this method does not check if the given zoom is valid, use instead the other set_zoom() method + // called only by: void GLCanvas3D::update_ui_from_settings() void set_zoom(double zoom) { m_zoom = zoom; } -#endif // ENABLE_RETINA_GL const BoundingBoxf3& get_scene_box() const { return m_scene_box; } void set_scene_box(const BoundingBoxf3& box) { m_scene_box = box; } From 09da7a84b5c1e338b81c1435f7cb69b96fc785e1 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 7 Jun 2019 17:01:19 +0200 Subject: [PATCH 163/627] Firmware updater: Prevent empty flashing jobs --- src/slic3r/GUI/FirmwareDialog.cpp | 57 ++++++++++++++++++++++------ src/slic3r/GUI/ProgressStatusBar.hpp | 1 + src/slic3r/Utils/Serial.hpp | 3 ++ 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index b400e27eae..9e209faedd 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -5,11 +5,14 @@ #include #include #include -#include -#include +#include #include #include +#if _WIN32 + #include +#endif + #include "libslic3r/Utils.hpp" #include "avrdude/avrdude-slic3r.hpp" #include "GUI.hpp" @@ -159,6 +162,7 @@ struct FirmwareDialog::priv void flashing_start(unsigned tasks); void flashing_done(AvrDudeComplete complete); void enable_port_picker(bool enable); + void update_flash_enabled(); void load_hex_file(const wxString &path); void queue_event(AvrdudeEvent aevt, wxString message); @@ -171,6 +175,7 @@ struct FirmwareDialog::priv void prepare_mk2(); void prepare_mk3(); void prepare_avr109(Avr109Pid usb_pid); + bool get_serial_port(); void perform_upload(); void user_cancel(); @@ -286,6 +291,14 @@ void FirmwareDialog::priv::enable_port_picker(bool enable) fit_no_shrink(); } +void FirmwareDialog::priv::update_flash_enabled() +{ + const bool hex_exists = wxFileExists(hex_picker->GetPath()); + const bool port_valid = get_serial_port(); + + btn_flash->Enable(hex_exists && port_valid); +} + void FirmwareDialog::priv::load_hex_file(const wxString &path) { hex_file = HexFile(path.wx_str()); @@ -553,6 +566,31 @@ void FirmwareDialog::priv::prepare_avr109(Avr109Pid usb_pid) } +bool FirmwareDialog::priv::get_serial_port() +{ + const int selection = port_picker->GetSelection(); + if (selection != wxNOT_FOUND) { + port = this->ports[selection]; + } else { + // User has supplied a custom filename + + std::string path_u8 = GUI::into_u8(port_picker->GetValue()); +#ifdef _WIN32 + static const std::regex com_pattern("COM[0-9]+", std::regex::icase); + std::smatch matches; + if (std::regex_match(path_u8, matches, com_pattern)) { +#else + if (fs::is_other(fs::path(path_u8))) { +#endif + port = SerialPortInfo(std::move(path_u8)); + } else { + port = boost::none; + } + } + + return !!port; +} + void FirmwareDialog::priv::perform_upload() { auto filename = hex_picker->GetPath(); @@ -560,14 +598,8 @@ void FirmwareDialog::priv::perform_upload() load_hex_file(filename); // Might already be loaded, but we want to make sure it's fresh - int selection = port_picker->GetSelection(); - if (selection != wxNOT_FOUND) { - port = this->ports[selection]; - - // Verify whether the combo box list selection equals to the combo box edit value. - if (wxString::FromUTF8(port->friendly_name.data()) != port_picker->GetValue()) { - return; - } + if (! get_serial_port()) { + return; } const bool extra_verbose = false; // For debugging @@ -836,10 +868,13 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) { if (wxFileExists(evt.GetPath())) { this->p->load_hex_file(evt.GetPath()); - this->p->btn_flash->Enable(); } + p->update_flash_enabled(); }); + p->port_picker->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { p->update_flash_enabled(); }); + p->port_picker->Bind(wxEVT_TEXT, [this](wxCommandEvent &) { p->update_flash_enabled(); }); + p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [=](wxCollapsiblePaneEvent &evt) { if (evt.GetCollapsed()) { this->SetMinSize(wxSize(p->min_width, p->min_height)); diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index 7d624af90b..413c6ffee5 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -2,6 +2,7 @@ #define PROGRESSSTATUSBAR_HPP #include +#include #include #include diff --git a/src/slic3r/Utils/Serial.hpp b/src/slic3r/Utils/Serial.hpp index e4a28de09d..67d64b4ec1 100644 --- a/src/slic3r/Utils/Serial.hpp +++ b/src/slic3r/Utils/Serial.hpp @@ -17,6 +17,9 @@ struct SerialPortInfo { std::string friendly_name; bool is_printer = false; + SerialPortInfo() {} + SerialPortInfo(std::string port) : port(port), friendly_name(std::move(port)) {} + bool id_match(unsigned id_vendor, unsigned id_product) const { return id_vendor == this->id_vendor && id_product == this->id_product; } }; From 8208da0a7b844dd74775c5029b2f097363db5233 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 11 Jun 2019 12:44:12 +0200 Subject: [PATCH 164/627] Firmware updater: Fix MMU flashing in GUI --- src/slic3r/GUI/FirmwareDialog.cpp | 34 ++++++++++++++++++------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index 9e209faedd..15a09aa716 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -107,7 +107,7 @@ struct FirmwareDialog::priv // GUI elements wxComboBox *port_picker; - wxStaticText *port_autodetect; + wxStaticText *txt_port_autodetect; wxFilePickerCtrl *hex_picker; wxStaticText *txt_status; wxGauge *progressbar; @@ -134,6 +134,7 @@ struct FirmwareDialog::priv // Data std::vector ports; optional port; + bool port_autodetect; HexFile hex_file; // This is a shared pointer holding the background AvrDude task @@ -150,6 +151,7 @@ struct FirmwareDialog::priv btn_flash_label_flashing(_(L("Cancel"))), label_status_flashing(_(L("Flashing in progress. Please do not disconnect the printer!"))), timer_pulse(q), + port_autodetect(false), progress_tasks_done(0), progress_tasks_bar(0), user_cancelled(false), @@ -161,7 +163,7 @@ struct FirmwareDialog::priv void set_txt_status(const wxString &label); void flashing_start(unsigned tasks); void flashing_done(AvrDudeComplete complete); - void enable_port_picker(bool enable); + void set_autodetect(bool autodetect); void update_flash_enabled(); void load_hex_file(const wxString &path); void queue_event(AvrdudeEvent aevt, wxString message); @@ -216,8 +218,10 @@ void FirmwareDialog::priv::find_serial_ports() idx = i; break; } - if (idx != -1) + if (idx != -1) { port_picker->SetSelection(idx); + update_flash_enabled(); + } } } } @@ -282,11 +286,13 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete) } } -void FirmwareDialog::priv::enable_port_picker(bool enable) +void FirmwareDialog::priv::set_autodetect(bool autodetect) { - port_picker->Show(enable); - btn_rescan->Show(enable); - port_autodetect->Show(! enable); + port_autodetect = autodetect; + + port_picker->Show(!autodetect); + btn_rescan->Show(!autodetect); + txt_port_autodetect->Show(autodetect); q->Layout(); fit_no_shrink(); } @@ -294,7 +300,7 @@ void FirmwareDialog::priv::enable_port_picker(bool enable) void FirmwareDialog::priv::update_flash_enabled() { const bool hex_exists = wxFileExists(hex_picker->GetPath()); - const bool port_valid = get_serial_port(); + const bool port_valid = port_autodetect || get_serial_port(); btn_flash->Enable(hex_exists && port_valid); } @@ -302,8 +308,8 @@ void FirmwareDialog::priv::update_flash_enabled() void FirmwareDialog::priv::load_hex_file(const wxString &path) { hex_file = HexFile(path.wx_str()); - const bool auto_lookup = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1; - enable_port_picker(! auto_lookup); + const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1; + set_autodetect(autodetect); } void FirmwareDialog::priv::queue_event(AvrdudeEvent aevt, wxString message) @@ -598,7 +604,7 @@ void FirmwareDialog::priv::perform_upload() load_hex_file(filename); // Might already be loaded, but we want to make sure it's fresh - if (! get_serial_port()) { + if (!port_autodetect && !get_serial_port()) { return; } @@ -801,13 +807,13 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:"))); p->port_picker = new wxComboBox(panel, wxID_ANY); - p->port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected"))); + p->txt_port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected"))); p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan"))); auto *port_sizer = new wxBoxSizer(wxHORIZONTAL); port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING); port_sizer->Add(p->btn_rescan, 0); - port_sizer->Add(p->port_autodetect, 1, wxEXPAND); - p->enable_port_picker(true); + port_sizer->Add(p->txt_port_autodetect, 1, wxEXPAND); + p->set_autodetect(false); auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:"))); p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH); From bb58d0fb6953c8782831f230e50eaf7bb07ae80c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 20 Jun 2019 14:15:36 +0200 Subject: [PATCH 165/627] Add some missing methods into the igl patch. Also, make the static igl dep build the default. --- deps/CMakeLists.txt | 2 +- deps/igl-fixes.patch | 49 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index c98941b5af..cbf4f3a540 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -35,7 +35,7 @@ set(DESTDIR "${CMAKE_CURRENT_BINARY_DIR}/destdir" CACHE PATH "Destination direct option(DEP_DEBUG "Build debug variants (only applicable on Windows)" ON) option(DEP_WX_STABLE "Build against wxWidgets stable 3.0 as opposed to default 3.1 (Linux only)" OFF) -option(DEP_BUILD_IGL_STATIC "Build IGL as a static library. Might cause link errors." OFF) +option(DEP_BUILD_IGL_STATIC "Build IGL as a static library. Might cause link errors." ON) message(STATUS "PrusaSlicer deps DESTDIR: ${DESTDIR}") message(STATUS "PrusaSlicer deps debug build: ${DEP_DEBUG}") diff --git a/deps/igl-fixes.patch b/deps/igl-fixes.patch index 2b50e200b9..b0ff9205d0 100644 --- a/deps/igl-fixes.patch +++ b/deps/igl-fixes.patch @@ -66,22 +66,63 @@ index 4b11007a..47e6c395 100644 endif() list(APPEND files_to_install ${public_sources}) diff --git a/include/igl/AABB.cpp b/include/igl/AABB.cpp -index 09537335..31594314 100644 +index 09537335..92e90cb7 100644 --- a/include/igl/AABB.cpp +++ b/include/igl/AABB.cpp -@@ -1072,4 +1072,5 @@ template void igl::AABB, 3>::init, 3>::init, 2>::init >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&); template double igl::AABB, 3>::squared_distance >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, double, int&, Eigen::PlainObjectBase >&) const; ++template float igl::AABB const, 0, Eigen::Stride<0, 0> >, 3>::squared_distance const, 0, Eigen::Stride<0, 0> > >(Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::Matrix const&, int&, Eigen::PlainObjectBase >&) const; template bool igl::AABB, 3>::intersect_ray >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, igl::Hit&) const; +template bool igl::AABB, 3>::intersect_ray >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix const&, Eigen::Matrix const&, std::vector&) const; ++ ++template void igl::AABB const, 0, Eigen::Stride<0, 0> >, 3>::init const, 0, Eigen::Stride<0, 0> > >(Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&); ++ ++template bool igl::AABB const, 0, Eigen::Stride<0, 0> >, 3>::intersect_ray const, 0, Eigen::Stride<0, 0> > >(Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::Matrix const&, Eigen::Matrix const&, std::vector >&) const; + #endif +diff --git a/include/igl/barycenter.cpp b/include/igl/barycenter.cpp +index 065f82aa..ec2d96cd 100644 +--- a/include/igl/barycenter.cpp ++++ b/include/igl/barycenter.cpp +@@ -54,4 +54,6 @@ template void igl::barycenter, Eigen::M + template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); + template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); + template void igl::barycenter, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::PlainObjectBase >&); ++ ++template void igl::barycenter const, 0, Eigen::Stride<0, 0> >, Eigen::Map const, 0, Eigen::Stride<0, 0> >, Eigen::Matrix >(Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::PlainObjectBase >&); + #endif +diff --git a/include/igl/point_simplex_squared_distance.cpp b/include/igl/point_simplex_squared_distance.cpp +index 2b98bd28..c66d9ae1 100644 +--- a/include/igl/point_simplex_squared_distance.cpp ++++ b/include/igl/point_simplex_squared_distance.cpp +@@ -178,4 +178,6 @@ template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); + template void igl::point_simplex_squared_distance<2, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); + template void igl::point_simplex_squared_distance<2, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix, double, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::Matrix::Index, double&, Eigen::MatrixBase >&, Eigen::PlainObjectBase >&); ++ ++template void igl::point_simplex_squared_distance<3, Eigen::Matrix, Eigen::Map const, 0, Eigen::Stride<0, 0> >, Eigen::Map const, 0, Eigen::Stride<0, 0> >, float, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::Map const, 0, Eigen::Stride<0, 0> >::Index, float&, Eigen::MatrixBase >&); + #endif +diff --git a/include/igl/ray_box_intersect.cpp b/include/igl/ray_box_intersect.cpp +index 4a88b89e..b547f8f8 100644 +--- a/include/igl/ray_box_intersect.cpp ++++ b/include/igl/ray_box_intersect.cpp +@@ -147,4 +147,6 @@ IGL_INLINE bool igl::ray_box_intersect( + #ifdef IGL_STATIC_LIBRARY + // Explicit template instantiation + template bool igl::ray_box_intersect, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::AlignedBox const&, double const&, double const&, double&, double&); ++ ++template bool igl::ray_box_intersect, Eigen::Matrix, float>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::AlignedBox const&, float const&, float const&, float&, float&); #endif diff --git a/include/igl/ray_mesh_intersect.cpp b/include/igl/ray_mesh_intersect.cpp -index 9a70a22b..dda1654b 100644 +index 9a70a22b..4233e722 100644 --- a/include/igl/ray_mesh_intersect.cpp +++ b/include/igl/ray_mesh_intersect.cpp -@@ -83,4 +83,5 @@ IGL_INLINE bool igl::ray_mesh_intersect( +@@ -83,4 +83,7 @@ IGL_INLINE bool igl::ray_mesh_intersect( template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, std::vector >&); template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Matrix >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, igl::Hit&); template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Block const, 1, -1, false> >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase const, 1, -1, false> > const&, igl::Hit&); +template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Matrix, Eigen::Block const, 1, -1, false> >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase const, 1, -1, false> > const&, std::vector >&); ++ ++template bool igl::ray_mesh_intersect, Eigen::Matrix, Eigen::Map const, 0, Eigen::Stride<0, 0> >, Eigen::Block const, 0, Eigen::Stride<0, 0> > const, 1, -1, true> >(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > > const&, Eigen::MatrixBase const, 0, Eigen::Stride<0, 0> > const, 1, -1, true> > const&, std::vector >&); #endif From b8f9d2e2d8b312f3dbf55aed05291a70d4e92d30 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 20 Jun 2019 12:54:43 +0200 Subject: [PATCH 166/627] Update default igl dependency to header-only. --- deps/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index cbf4f3a540..5bc33c896d 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -35,7 +35,11 @@ set(DESTDIR "${CMAKE_CURRENT_BINARY_DIR}/destdir" CACHE PATH "Destination direct option(DEP_DEBUG "Build debug variants (only applicable on Windows)" ON) option(DEP_WX_STABLE "Build against wxWidgets stable 3.0 as opposed to default 3.1 (Linux only)" OFF) -option(DEP_BUILD_IGL_STATIC "Build IGL as a static library. Might cause link errors." ON) + +# IGL static library in release mode produces 50MB binary. On the build server, it should be +# disabled and used in header-only mode. On developer machines, it can be enabled to speed +# up conpilation and suppress warnings coming from IGL. +option(DEP_BUILD_IGL_STATIC "Build IGL as a static library. Might cause link errors and increase binary size." OFF) message(STATUS "PrusaSlicer deps DESTDIR: ${DESTDIR}") message(STATUS "PrusaSlicer deps debug build: ${DEP_DEBUG}") From fe395546f0cec0aa6fd6c5333f71c73b812ea56c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 20 Jun 2019 12:56:23 +0200 Subject: [PATCH 167/627] Added "info" tooltip for a LockButton on Manipulation panel (#2539) --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 6 ++++-- src/slic3r/GUI/wxExtensions.cpp | 3 +++ src/slic3r/GUI/wxExtensions.hpp | 7 ++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 310000ecc3..372cd79efb 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -499,11 +499,13 @@ void ObjectManipulation::update_if_dirty() if (selection.requires_uniform_scale()) { m_lock_bnt->SetLock(true); - m_lock_bnt->Disable(); + m_lock_bnt->SetToolTip(_(L("You cann't use non-uniform scaling mode for multiple objects/parts selection"))); + m_lock_bnt->disable(); } else { m_lock_bnt->SetLock(m_uniform_scale); - m_lock_bnt->Enable(); + m_lock_bnt->SetToolTip(wxEmptyString); + m_lock_bnt->enable(); } { diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 7b7178a824..406daccf55 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -2472,6 +2472,9 @@ LockButton::LockButton( wxWindow *parent, void LockButton::OnButton(wxCommandEvent& event) { + if (m_disabled) + return; + m_is_pushed = !m_is_pushed; enter_button(true); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index c496c28a0c..dd035690ad 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -838,9 +838,13 @@ public: void OnEnterBtn(wxMouseEvent& event) { enter_button(true); event.Skip(); } void OnLeaveBtn(wxMouseEvent& event) { enter_button(false); event.Skip(); } - bool IsLocked() const { return m_is_pushed; } + bool IsLocked() const { return m_is_pushed; } void SetLock(bool lock); + // create its own Enable/Disable functions to not really disabled button because of tooltip enabling + void enable() { m_disabled = false; } + void disable() { m_disabled = true; } + void msw_rescale(); protected: @@ -848,6 +852,7 @@ protected: private: bool m_is_pushed = false; + bool m_disabled = false; ScalableBitmap m_bmp_lock_on; ScalableBitmap m_bmp_lock_off; From 3b0e0aaed43389c66bf3eebed035353b94da2dc7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 20 Jun 2019 13:01:48 +0200 Subject: [PATCH 168/627] Fixes for degenerate cases. --- src/libslic3r/SLA/SLABasePool.cpp | 75 ++++++++++++++-------------- src/libslic3r/SLA/SLACommon.hpp | 1 + src/libslic3r/SLA/SLASupportTree.cpp | 59 +++++++++++++++------- src/libslic3r/SLAPrint.cpp | 8 --- 4 files changed, 80 insertions(+), 63 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 48d615a29d..ace9cdd85a 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -239,48 +239,49 @@ void offset(ExPolygon& sh, coord_t distance, bool edgerounding = true) { } } - void offset(Polygon& sh, coord_t distance, bool edgerounding = true) { - using ClipperLib::ClipperOffset; - using ClipperLib::jtRound; - using ClipperLib::jtMiter; - using ClipperLib::etClosedPolygon; - using ClipperLib::Paths; - using ClipperLib::Path; +void offset(Polygon &sh, coord_t distance, bool edgerounding = true) +{ + using ClipperLib::ClipperOffset; + using ClipperLib::jtRound; + using ClipperLib::jtMiter; + using ClipperLib::etClosedPolygon; + using ClipperLib::Paths; + using ClipperLib::Path; - auto&& ctour = Slic3rMultiPoint_to_ClipperPath(sh); + auto &&ctour = Slic3rMultiPoint_to_ClipperPath(sh); - // If the input is not at least a triangle, we can not do this algorithm - if(ctour.size() < 3) { - BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; - return; - } + // If the input is not at least a triangle, we can not do this algorithm + if (ctour.size() < 3) { + BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; + return; + } - ClipperOffset offs; - offs.ArcTolerance = 0.01*scaled(1.); - Paths result; - offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon); - offs.Execute(result, static_cast(distance)); + ClipperOffset offs; + offs.ArcTolerance = 0.01 * scaled(1.); + Paths result; + offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon); + offs.Execute(result, static_cast(distance)); - // Offsetting reverts the orientation and also removes the last vertex - // so boost will not have a closed polygon. + // Offsetting reverts the orientation and also removes the last vertex + // so boost will not have a closed polygon. - bool found_the_contour = false; - for(auto& r : result) { - if(ClipperLib::Orientation(r)) { - // We don't like if the offsetting generates more than one contour - // but throwing would be an overkill. Instead, we should warn the - // caller about the inability to create correct geometries - if(!found_the_contour) { - auto rr = ClipperPath_to_Slic3rPolygon(r); - sh.points.swap(rr.points); - found_the_contour = true; - } else { - BOOST_LOG_TRIVIAL(warning) - << "Warning: offsetting result is invalid!"; - } - } - } - } + bool found_the_contour = false; + for (auto &r : result) { + if (ClipperLib::Orientation(r)) { + // We don't like if the offsetting generates more than one contour + // but throwing would be an overkill. Instead, we should warn the + // caller about the inability to create correct geometries + if (!found_the_contour) { + auto rr = ClipperPath_to_Slic3rPolygon(r); + sh.points.swap(rr.points); + found_the_contour = true; + } else { + BOOST_LOG_TRIVIAL(warning) + << "Warning: offsetting result is invalid!"; + } + } + } +} /// Unification of polygons (with clipper) preserving holes as well. ExPolygons unify(const ExPolygons& shapes) { diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index 2666f2a9c3..eb986a259b 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -73,6 +73,7 @@ public: inline double ground_level() const { return m_ground_level + m_gnd_offset; } inline void ground_level_offset(double o) { m_gnd_offset = o; } + inline double ground_level_offset() const { return m_gnd_offset; } inline const Eigen::MatrixXd& V() const { return m_V; } inline const Eigen::MatrixXi& F() const { return m_F; } diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index cfb5c2e743..4de5c4c594 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -530,6 +530,7 @@ struct CompactBridge { const Vec3d& ep, const Vec3d& n, double r, + bool endball = true, size_t steps = 45) { Vec3d startp = sp + r * n; @@ -543,12 +544,14 @@ struct CompactBridge { double fa = 2*PI/steps; auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); for(auto& p : upperball.points) p += startp; - - auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); - for(auto& p : lowerball.points) p += endp; - + + if(endball) { + auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); + for(auto& p : lowerball.points) p += endp; + mesh.merge(lowerball); + } + mesh.merge(upperball); - mesh.merge(lowerball); } }; @@ -594,13 +597,17 @@ struct Pad { // base silhouette. Create the offsetted version and punch the // breaksticks across its perimeter. - ExPolygons modelbase_sticks = modelbase; - + ExPolygons modelbase_offs = modelbase; + if (pcfg.embed_object.object_gap_mm > 0.0) - modelbase_sticks - = offset_ex(modelbase_sticks, + modelbase_offs + = offset_ex(modelbase_offs, float(scaled(pcfg.embed_object.object_gap_mm))); + // Create a spatial index of the support silhouette polygons. + // This will be used to check for intersections with the model + // silhouette polygons. If there is no intersection, then a certain + // part of the pad is redundant as it does not host any supports. BoxIndex bindex; { unsigned idx = 0; @@ -611,14 +618,27 @@ struct Pad { } } + // Punching the breaksticks across the offsetted polygon perimeters ExPolygons pad_stickholes; pad_stickholes.reserve(modelbase.size()); - for(auto& poly : modelbase_sticks) { + for(auto& poly : modelbase_offs) { - if (!bindex.query(poly.contour.bounding_box(), - BoxIndex::qtIntersects).empty()) { + std::vector qres = + bindex.query(poly.contour.bounding_box(), + BoxIndex::qtIntersects); + + if (!qres.empty()) { + + // The model silhouette polygon 'poly' HAS an intersection + // with the support silhouettes. Include this polygon + // in the pad holes with the breaksticks and merge the + // original (offsetted) version with the rest of the pad + // base plate. basep.emplace_back(poly.contour); + // The holes of 'poly' will become positive parts of the + // pad, so they has to be checked for intersections as well + // and erased if there is no intersection with the supports auto it = poly.holes.begin(); while(it != poly.holes.end()) { if (bindex.query(it->bounding_box(), @@ -627,7 +647,8 @@ struct Pad { else ++it; } - + + // Punch the breaksticks sla::breakstick_holes( poly, pcfg.embed_object.object_gap_mm, // padding @@ -638,7 +659,7 @@ struct Pad { pad_stickholes.emplace_back(poly); } } - + create_base_pool(basep, tmesh, pad_stickholes, cfg); } else { for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); @@ -1495,12 +1516,13 @@ class SLASupportTree::Algorithm { Vec3d pgnd = {endp(X), endp(Y), gndlvl}; can_add_base = result.score > min_dist; + double gnd_offs = m_mesh.ground_level_offset(); auto abort_in_shame = - [&normal_mode, &can_add_base, &endp, jp, gndlvl]() + [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]() { normal_mode = true; can_add_base = false; // Nothing left to do, hope for the best - endp = {jp(X), jp(Y), gndlvl}; + endp = {jp(X), jp(Y), gndlvl - gnd_offs }; }; // We have to check if the bridge is feasible. @@ -2317,7 +2339,8 @@ public: double idist = bridge_mesh_intersect(sph, dir, R, true); double dist = ray_mesh_intersect(sj, dir); if (std::isinf(dist)) - dist = sph(Z) - m_result.ground_level - HWIDTH_MM; + dist = sph(Z) - m_mesh.ground_level() + + m_mesh.ground_level_offset(); if(std::isnan(idist) || idist < 2*R || std::isnan(dist) || dist < 2*R) @@ -2329,7 +2352,7 @@ public: } Vec3d ej = sj + (dist + HWIDTH_MM)* dir; - m_result.add_compact_bridge(sp, ej, n, R); + m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist)); } } }; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 789a8120f6..9e8b4bea25 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1084,9 +1084,6 @@ void SLAPrint::process() using ClipperPolygons = std::vector; namespace sl = libnest2d::shapelike; // For algorithms - // If the raster has vertical orientation, we will flip the coordinates -// bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait; - // Set up custom union and diff functions for clipper polygons auto polyunion = [] (const ClipperPolygons& subjects) { @@ -1194,11 +1191,6 @@ void SLAPrint::process() sl::translate(poly, ClipperPoint{instances[i].shift(X), instances[i].shift(Y)}); -// if (flpXY) { -// for(auto& p : poly.Contour) std::swap(p.X, p.Y); -// for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y); -// } - polygons.emplace_back(std::move(poly)); } } From 503212181c93545f68bbdec69085d11264943f92 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 3 Jun 2019 11:31:32 +0200 Subject: [PATCH 169/627] Updating: Index installation Prevents cancelled updates from popping up repeatedly on each application startup --- src/slic3r/Config/Version.cpp | 1 + src/slic3r/Config/Version.hpp | 4 ++ src/slic3r/GUI/GUI_App.cpp | 6 +-- src/slic3r/Utils/PresetUpdater.cpp | 86 ++++++++++++++++++------------ src/slic3r/Utils/Semver.hpp | 5 ++ 5 files changed, 65 insertions(+), 37 deletions(-) diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp index fe3adfd7f1..865884c6fe 100644 --- a/src/slic3r/Config/Version.cpp +++ b/src/slic3r/Config/Version.cpp @@ -192,6 +192,7 @@ size_t Index::load(const boost::filesystem::path &path) { m_configs.clear(); m_vendor = path.stem().string(); + m_path = path; boost::nowide::ifstream ifs(path.string()); std::string line; diff --git a/src/slic3r/Config/Version.hpp b/src/slic3r/Config/Version.hpp index e689286af8..560bc29c21 100644 --- a/src/slic3r/Config/Version.hpp +++ b/src/slic3r/Config/Version.hpp @@ -72,6 +72,9 @@ public: // if the index is valid. const_iterator recommended() const; + // Returns the filesystem path from which this index has originally been loaded + const boost::filesystem::path& path() const { return m_path; } + // Load all vendor specific indices. // Throws Slic3r::file_parser_error and the standard std file access exceptions. static std::vector load_db(); @@ -79,6 +82,7 @@ public: private: std::string m_vendor; std::vector m_configs; + boost::filesystem::path m_path; }; } // namespace Config diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3aada45f39..4f1c3adc8b 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -265,10 +265,8 @@ bool GUI_App::on_init_inner() } CallAfter([this] { - if (!config_wizard_startup(app_conf_exists)) { - // Only notify if there was no wizard so as not to bother too much ... - preset_updater->slic3r_update_notify(); - } + config_wizard_startup(app_conf_exists); + preset_updater->slic3r_update_notify(); preset_updater->sync(preset_bundle); }); } diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index f34cd8db19..8c3ced31a2 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -45,10 +46,25 @@ static const char *INDEX_FILENAME = "index.idx"; static const char *TMP_EXTENSION = ".download"; +void copy_file_fix(const fs::path &source, const fs::path &target) +{ + static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644 + + BOOST_LOG_TRIVIAL(debug) << boost::format("PresetUpdater: Copying %1% -> %2%") % source % target; + + // Make sure the file has correct permission both before and after we copy over it + if (fs::exists(target)) { + fs::permissions(target, perms); + } + fs::copy_file(source, target, fs::copy_option::overwrite_if_exists); + fs::permissions(target, perms); +} + struct Update { fs::path source; fs::path target; + Version version; std::string vendor; std::string changelog_url; @@ -61,7 +77,13 @@ struct Update , changelog_url(std::move(changelog_url)) {} - friend std::ostream& operator<<(std::ostream& os , const Update &self) { + void install() const + { + copy_file_fix(source, target); + } + + friend std::ostream& operator<<(std::ostream& os, const Update &self) + { os << "Update(" << self.source.string() << " -> " << self.target.string() << ')'; return os; } @@ -115,7 +137,6 @@ struct PresetUpdater::priv bool enabled_version_check; bool enabled_config_update; std::string version_check_url; - bool had_config_update; fs::path cache_path; fs::path rsrc_path; @@ -135,13 +156,10 @@ struct PresetUpdater::priv void check_install_indices() const; Updates get_config_updates() const; void perform_updates(Updates &&updates, bool snapshot = true) const; - - static void copy_file(const fs::path &from, const fs::path &to); }; PresetUpdater::priv::priv() : ver_slic3r(get_slic3r_version()) - , had_config_update(false) , cache_path(fs::path(Slic3r::data_dir()) / "cache") , rsrc_path(fs::path(resources_dir()) / "profiles") , vendor_path(fs::path(Slic3r::data_dir()) / "vendor") @@ -273,7 +291,7 @@ void PresetUpdater::priv::sync_config(const std::set vendors) try { new_index.load(idx_path_temp); } catch (const std::exception & /* err */) { - BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading a downloaded index %1% for vendor %2%: invalid index?") % idx_path_temp % vendor.name; + BOOST_LOG_TRIVIAL(error) << boost::format("Could not load downloaded index %1% for vendor %2%: invalid index?") % idx_path_temp % vendor.name; continue; } if (new_index.version() < index.version()) { @@ -323,7 +341,7 @@ void PresetUpdater::priv::check_install_indices() const if (! fs::exists(path_in_cache)) { BOOST_LOG_TRIVIAL(info) << "Install index from resources: " << path.filename(); - copy_file(path, path_in_cache); + copy_file_fix(path, path_in_cache); } else { Index idx_rsrc, idx_cache; idx_rsrc.load(path); @@ -331,7 +349,7 @@ void PresetUpdater::priv::check_install_indices() const if (idx_cache.version() < idx_rsrc.version()) { BOOST_LOG_TRIVIAL(info) << "Update index from resources: " << path.filename(); - copy_file(path, path_in_cache); + copy_file_fix(path, path_in_cache); } } } @@ -346,6 +364,7 @@ Updates PresetUpdater::priv::get_config_updates() const for (const auto idx : index_db) { auto bundle_path = vendor_path / (idx.vendor() + ".ini"); + auto bundle_path_idx = vendor_path / idx.path().filename(); if (! fs::exists(bundle_path)) { BOOST_LOG_TRIVIAL(info) << "Bundle not present for index, skipping: " << idx.vendor(); @@ -360,8 +379,31 @@ Updates PresetUpdater::priv::get_config_updates() const const auto recommended = idx.recommended(); if (recommended == idx.end()) { BOOST_LOG_TRIVIAL(error) << boost::format("No recommended version for vendor: %1%, invalid index?") % idx.vendor(); + // XXX: what should be done here? + continue; } + // Load 'installed' idx, if any. + // 'Installed' indices are kept alongside the bundle in the `vendor` subdir + // for bookkeeping to remember a cancelled update and not offer it again. + if (fs::exists(bundle_path_idx)) { + Index existing_idx; + try { + existing_idx.load(bundle_path_idx); + + const auto existing_recommended = existing_idx.recommended(); + if (existing_recommended != existing_idx.end() && recommended->config_version == existing_recommended->config_version) { + // The user has already seen (and presumably rejected) this update + BOOST_LOG_TRIVIAL(info) << boost::format("Downloaded index for `%1%` is the same as installed one, not offering an update.") % idx.vendor(); + continue; + } + } catch (const std::exception & /* err */) { + BOOST_LOG_TRIVIAL(error) << boost::format("Could nto load installed index %1%") % bundle_path_idx; + } + } + + copy_file_fix(idx.path(), bundle_path_idx); + const auto ver_current = idx.find(vp.config_version); const bool ver_current_found = ver_current != idx.end(); if (! ver_current_found) { @@ -453,10 +495,10 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &update : updates.updates) { BOOST_LOG_TRIVIAL(info) << '\t' << update; - copy_file(update.source, update.target); + update.install(); PresetBundle bundle; - bundle.load_configbundle(update.target.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); + bundle.load_configbundle(update.source.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% conflicting presets") % (bundle.prints.size() + bundle.filaments.size() + bundle.printers.size()); @@ -491,19 +533,6 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons } } -void PresetUpdater::priv::copy_file(const fs::path &source, const fs::path &target) -{ - static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644 - - // Make sure the file has correct permission both before and after we copy over it - if (fs::exists(target)) { - fs::permissions(target, perms); - } - fs::copy_file(source, target, fs::copy_option::overwrite_if_exists); - fs::permissions(target, perms); -} - - PresetUpdater::PresetUpdater() : p(new priv()) {} @@ -542,11 +571,6 @@ void PresetUpdater::slic3r_update_notify() { if (! p->enabled_version_check) { return; } - if (p->had_config_update) { - BOOST_LOG_TRIVIAL(info) << "New Slic3r version available, but there was a configuration update, notification won't be displayed"; - return; - } - auto* app_config = GUI::wxGetApp().app_config; const auto ver_online_str = app_config->get("version_online"); const auto ver_online = Semver::parse(ver_online_str); @@ -594,8 +618,6 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const incompats_map.emplace(std::make_pair(incompat.vendor, std::move(restrictions))); } - p->had_config_update = true; // This needs to be done before a dialog is shown because of OnIdle() + CallAfter() in Perl - GUI::MsgDataIncompatible dlg(std::move(incompats_map)); const auto res = dlg.ShowModal(); if (res == wxID_REPLACE) { @@ -620,8 +642,6 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url)); } - p->had_config_update = true; // Ditto, see above - GUI::MsgUpdateConfig dlg(updates_msg); const auto res = dlg.ShowModal(); @@ -631,7 +651,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const // Reload global configuration auto *app_config = GUI::wxGetApp().app_config; - GUI::wxGetApp().preset_bundle->load_presets(*app_config); + GUI::wxGetApp().preset_bundle->load_presets(*app_config); GUI::wxGetApp().load_current_presets(); return R_UPDATE_INSTALLED; } else { diff --git a/src/slic3r/Utils/Semver.hpp b/src/slic3r/Utils/Semver.hpp index 2fb4e3f4bf..a755becaa5 100644 --- a/src/slic3r/Utils/Semver.hpp +++ b/src/slic3r/Utils/Semver.hpp @@ -137,6 +137,11 @@ public: Semver operator-(const Minor &b) const { Semver res(*this); return res -= b; } Semver operator-(const Patch &b) const { Semver res(*this); return res -= b; } + // Stream output + friend std::ostream& operator<<(std::ostream& os, const Semver &self) { + os << self.to_string(); + return os; + } private: semver_t ver; From 35b3fd317623b98153a0b91d3f51c784d2cc0a20 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 20 Jun 2019 16:15:09 +0200 Subject: [PATCH 170/627] Integrated the new layer height spans with configs into the backend. Fixed some compiler warnings. --- src/libslic3r/Fill/Fill.cpp | 12 +- src/libslic3r/Format/3mf.cpp | 4 +- src/libslic3r/Format/AMF.cpp | 12 +- src/libslic3r/Model.cpp | 4 +- src/libslic3r/Model.hpp | 6 +- src/libslic3r/Print.cpp | 310 ++++++++++++++++----------- src/libslic3r/Print.hpp | 26 +-- src/libslic3r/PrintObject.cpp | 337 ++++++++++++++++++++++-------- src/libslic3r/Slicing.cpp | 22 +- src/libslic3r/Slicing.hpp | 6 +- src/libslic3r/SupportMaterial.cpp | 6 +- src/slic3r/GUI/3DBed.cpp | 4 +- src/slic3r/GUI/Plater.cpp | 3 - src/slic3r/GUI/Selection.cpp | 1 - t/print.t | 8 +- xs/xsp/Print.xsp | 2 - 16 files changed, 491 insertions(+), 272 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 75952e4c23..fbdef29b98 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -126,7 +126,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) Polygons surfaces_polygons = to_polygons(surfaces); Polygons collapsed = diff( surfaces_polygons, - offset2(surfaces_polygons, -distance_between_surfaces/2, +distance_between_surfaces/2), + offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2), true); Polygons to_subtract; to_subtract.reserve(collapsed.size() + number_polygons(surfaces)); @@ -137,7 +137,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) surfaces_append( surfaces, intersection_ex( - offset(collapsed, distance_between_surfaces), + offset(collapsed, (float)distance_between_surfaces), to_subtract, true), stInternalSolid); @@ -219,14 +219,14 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) f->z = layerm.layer()->print_z; f->angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value)); // Maximum length of the perimeter segment linking two infill lines. - f->link_max_length = scale_(link_max_length); + f->link_max_length = (coord_t)scale_(link_max_length); // Used by the concentric infill pattern to clip the loops to create extrusion paths. - f->loop_clipping = scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER; + f->loop_clipping = coord_t(scale_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER); // f->layer_height = h; // apply half spacing using this flow's own spacing and generate infill FillParams params; - params.density = 0.01 * density; + params.density = float(0.01 * density); // params.dont_adjust = true; params.dont_adjust = false; Polylines polylines = f->fill_surface(&surface, params); @@ -240,7 +240,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) // so we can safely ignore the slight variation that might have // been applied to $f->flow_spacing } else { - flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, h, is_bridge || f->use_bridge_flow()); + flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, (float)h, is_bridge || f->use_bridge_flow()); } // Save into layer. diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 002e125c73..45b8fe52fe 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -2031,7 +2031,7 @@ namespace Slic3r { return false; } - vertices_count += its.vertices.size(); + vertices_count += (int)its.vertices.size(); const Transform3d& matrix = volume->get_matrix(); @@ -2061,7 +2061,7 @@ namespace Slic3r { // updates triangle offsets volume_it->second.first_triangle_id = triangles_count; - triangles_count += its.indices.size(); + triangles_count += (int)its.indices.size(); volume_it->second.last_triangle_id = triangles_count - 1; for (size_t i = 0; i < its.indices.size(); ++ i) diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 4f0a402ea5..90a538731f 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -192,7 +192,7 @@ struct AMFParserContext }; // Version of the amf file - unsigned int m_version; + unsigned int m_version; // Current Expat XML parser instance. XML_Parser m_parser; // Model to receive objects extracted from an AMF file. @@ -616,7 +616,7 @@ void AMFParserContext::endElement(const char * /* name */) if (end != nullptr) *end = 0; - point(coord_idx) = atof(p); + point(coord_idx) = float(atof(p)); if (++coord_idx == 5) { m_object->sla_support_points.push_back(sla::SupportPoint(point)); coord_idx = 0; @@ -628,8 +628,8 @@ void AMFParserContext::endElement(const char * /* name */) m_object->sla_points_status = sla::PointsStatus::UserModified; } else if (m_path.size() == 5 && m_path[1] == NODE_TYPE_OBJECT && m_path[3] == NODE_TYPE_RANGE && - m_object && strcmp(opt_key, "layer_height_ranges") == 0) { - // Parse object's layer_height_ranges, a semicolon separated doubles. + m_object && strcmp(opt_key, "layer_height_range") == 0) { + // Parse object's layer_height_range, a semicolon separated doubles. char* p = const_cast(m_value[1].c_str()); char* end = strchr(p, ';'); *end = 0; @@ -946,7 +946,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) for (auto range : config_ranges) { stream << " \n"; - stream << " "; + stream << " "; stream << range.first.first << ";" << range.first.second << "\n"; for (const std::string& key : range.second.keys()) @@ -994,7 +994,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) stream << " \n"; stream << " \n"; } - num_vertices += its.vertices.size(); + num_vertices += (int)its.vertices.size(); } stream << " \n"; for (size_t i_volume = 0; i_volume < object->volumes.size(); ++i_volume) { diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 1f9efbc564..90725f31ed 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -593,7 +593,6 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->config = rhs.config; this->sla_support_points = rhs.sla_support_points; this->sla_points_status = rhs.sla_points_status; - this->layer_height_ranges = rhs.layer_height_ranges; this->layer_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment this->layer_height_profile = rhs.layer_height_profile; this->origin_translation = rhs.origin_translation; @@ -630,7 +629,6 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs) this->config = std::move(rhs.config); this->sla_support_points = std::move(rhs.sla_support_points); this->sla_points_status = std::move(rhs.sla_points_status); - this->layer_height_ranges = std::move(rhs.layer_height_ranges); this->layer_config_ranges = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment this->layer_height_profile = std::move(rhs.layer_height_profile); this->origin_translation = std::move(rhs.origin_translation); @@ -1809,7 +1807,7 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO if (!mv_old.get_matrix().isApprox(mv_new.get_matrix())) return true; - ++i_old; + ++ i_old; ++ i_new; } for (; i_old < model_object_old.volumes.size(); ++ i_old) { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 22d43dbc7d..c1850df980 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -179,10 +179,8 @@ public: ModelVolumePtrs volumes; // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings. DynamicPrintConfig config; - // Variation of a layer thickness for spans of Z coordinates. - t_layer_height_ranges layer_height_ranges; - // Variation of a layer thickness for spans of Z coordinates. - t_layer_config_ranges layer_config_ranges; + // Variation of a layer thickness for spans of Z coordinates + optional parameter overrides. + t_layer_config_ranges layer_config_ranges; // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. // The pairs of are packed into a 1D array. std::vector layer_height_profile; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index ed4af39957..5e4f253348 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -41,36 +41,6 @@ void Print::clear() m_model.clear_objects(); } -// Only used by the Perl test cases. -void Print::reload_object(size_t /* idx */) -{ - ModelObjectPtrs model_objects; - { - tbb::mutex::scoped_lock lock(this->state_mutex()); - // The following call should stop background processing if it is running. - this->invalidate_all_steps(); - /* TODO: this method should check whether the per-object config and per-material configs - have changed in such a way that regions need to be rearranged or we can just apply - the diff and invalidate something. Same logic as apply() - For now we just re-add all objects since we haven't implemented this incremental logic yet. - This should also check whether object volumes (parts) have changed. */ - // collect all current model objects - model_objects.reserve(m_objects.size()); - for (PrintObject *object : m_objects) - model_objects.push_back(object->model_object()); - // remove our print objects - for (PrintObject *object : m_objects) - delete object; - m_objects.clear(); - for (PrintRegion *region : m_regions) - delete region; - m_regions.clear(); - } - // re-add model objects - for (ModelObject *mo : model_objects) - this->add_model_object(mo); -} - PrintRegion* Print::add_region() { m_regions.emplace_back(new PrintRegion(this)); @@ -335,7 +305,7 @@ unsigned int Print::num_object_instances() const { unsigned int instances = 0; for (const PrintObject *print_object : m_objects) - instances += print_object->copies().size(); + instances += (unsigned int)print_object->copies().size(); return instances; } @@ -360,7 +330,7 @@ double Print::max_allowed_layer_height() const // Caller is responsible for supplying models whose objects don't collide // and have explicit instance positions. -void Print::add_model_object(ModelObject* model_object, int idx) +void Print::add_model_object_perl_tests_only(ModelObject* model_object, int idx) { tbb::mutex::scoped_lock lock(this->state_mutex()); // Add a copy of this ModelObject to this Print. @@ -389,26 +359,26 @@ void Print::add_model_object(ModelObject* model_object, int idx) object->set_trafo(trafo); } - size_t volume_id = 0; + int volume_id = 0; for (const ModelVolume *volume : model_object->volumes) { if (! volume->is_model_part() && ! volume->is_modifier()) continue; // Get the config applied to this volume. - PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, *volume, 99999); + PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, nullptr, *volume, 99999); // Find an existing print region with the same config. - size_t region_id = size_t(-1); - for (size_t i = 0; i < m_regions.size(); ++ i) + int region_id = -1; + for (int i = 0; i < (int)m_regions.size(); ++ i) if (config.equals(m_regions[i]->config())) { region_id = i; break; } // If no region exists with the same config, create a new one. - if (region_id == size_t(-1)) { - region_id = m_regions.size(); + if (region_id == -1) { + region_id = (int)m_regions.size(); this->add_region(config); } // Assign volume to a region. - object->add_region_volume(region_id, volume_id); + object->add_region_volume((unsigned int)region_id, volume_id, t_layer_height_range(0, DBL_MAX)); ++ volume_id; } @@ -489,18 +459,18 @@ bool Print::apply_config_perl_tests_only(DynamicPrintConfig config) bool this_region_config_set = false; for (PrintObject *object : m_objects) { if (region_id < object->region_volumes.size()) { - for (int volume_id : object->region_volumes[region_id]) { - const ModelVolume &volume = *object->model_object()->volumes[volume_id]; + for (const std::pair &volume_and_range : object->region_volumes[region_id]) { + const ModelVolume &volume = *object->model_object()->volumes[volume_and_range.second]; if (this_region_config_set) { // If the new config for this volume differs from the other // volume configs currently associated to this region, it means // the region subdivision does not make sense anymore. - if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, volume, 99999))) { + if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, nullptr, volume, 99999))) { rearrange_regions = true; goto exit_for_rearrange_regions; } } else { - this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, volume, 99999); + this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, nullptr, volume, 99999); this_region_config_set = true; } for (const PrintRegionConfig &cfg : other_region_configs) { @@ -540,7 +510,7 @@ exit_for_rearrange_regions: model_objects.push_back(object->model_object()); this->clear(); for (ModelObject *mo : model_objects) - this->add_model_object(mo); + this->add_model_object_perl_tests_only(mo); invalidated = true; } @@ -620,6 +590,20 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, } } +static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src) +{ + assert(lr_dst.size() == lr_src.size()); + auto it_src = lr_src.cbegin(); + for (auto &kvp_dst : lr_dst) { + const auto &kvp_src = *it_src ++; + assert(std::abs(kvp_dst.first.first - kvp_src.first.first ) <= EPSILON); + assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON); + // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile. + // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON); + kvp_dst.second = kvp_src.second; + } +} + static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs) { typedef Transform3d::Scalar T; @@ -674,6 +658,23 @@ static std::vector print_objects_from_model_object(const ModelOb return std::vector(trafos.begin(), trafos.end()); } +// Compare just the layer ranges and their layer heights, not the associated configs. +// Ignore the layer heights if check_layer_heights is false. +bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height) +{ + if (lr1.size() != lr2.size()) + return false; + auto it2 = lr2.begin(); + for (const auto &kvp1 : lr1) { + const auto &kvp2 = *it2 ++; + if (std::abs(kvp1.first.first - kvp2.first.first ) > EPSILON || + std::abs(kvp1.first.second - kvp2.first.second) > EPSILON || + (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON)) + return false; + } + return true; +} + Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &config_in) { #ifdef _DEBUG @@ -724,6 +725,50 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co // Handle changes to regions config defaults m_default_region_config.apply_only(config, region_diff, true); + class LayerRanges + { + public: + LayerRanges() {} + // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs. + void assign(const t_layer_config_ranges &in) { + m_ranges.clear(); + m_ranges.reserve(in.size()); + // Input ranges are sorted lexicographically. First range trims the other ranges. + coordf_t last_z = 0; + for (const std::pair &range : in) { +// for (auto &range : in) { + if (range.first.second > last_z) { + coordf_t min_z = std::max(range.first.first, 0.); + if (min_z > last_z + EPSILON) { + m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr); + last_z = min_z; + } + if (range.first.second > last_z + EPSILON) { + const DynamicPrintConfig* cfg = &range.second; + m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg); + last_z = range.first.second; + } + } + } + if (m_ranges.empty()) + m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr); + else if (m_ranges.back().second == nullptr) + m_ranges.back().first.second = DBL_MAX; + else + m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr); + } + const DynamicPrintConfig* config(const t_layer_height_range &range) const { + auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr)); + assert(it != m_ranges.end()); + assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON); + assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); + return (it == m_ranges.end()) ? nullptr : it->second; + } + auto begin() const { return m_ranges.cbegin(); } + auto end() const { return m_ranges.cend(); } + private: + std::vector> m_ranges; + }; struct ModelObjectStatus { enum Status { Unknown, @@ -733,8 +778,9 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co Deleted, }; ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {} - ModelID id; - Status status; + ModelID id; + Status status; + LayerRanges layer_ranges; // Search by id. bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } }; @@ -861,22 +907,23 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); assert(it_status != model_object_status.end()); assert(it_status->status != ModelObjectStatus::Deleted); + const ModelObject& model_object_new = *model.objects[idx_model_object]; + const_cast(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges); if (it_status->status == ModelObjectStatus::New) // PrintObject instances will be added in the next loop. continue; // Update the ModelObject instance, possibly invalidate the linked PrintObjects. assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved); - const ModelObject &model_object_new = *model.objects[idx_model_object]; // Check whether a model part volume was added or removed, their transformations or order changed. + // Only volume IDs, volume types and their order are checked, configuration and other parameters are NOT checked. bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER); bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER); bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); if (model_parts_differ || modifiers_differ || model_object.origin_translation != model_object_new.origin_translation || -// model_object.layer_height_ranges != model_object_new.layer_height_ranges || - model_object.layer_config_ranges != model_object_new.layer_config_ranges || // #ys_FIXME_experiment - model_object.layer_height_profile != model_object_new.layer_height_profile) { + model_object.layer_height_profile != model_object_new.layer_height_profile || + ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty())) { // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); for (auto it = range.first; it != range.second; ++ it) { @@ -916,7 +963,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co //FIXME What to do with m_material_id? model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART); model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER); - // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. + layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */); + // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step. model_object.name = model_object_new.name; model_object.input_file = model_object_new.input_file; model_object.clear_instances(); @@ -1028,19 +1076,27 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co PrintRegionConfig this_region_config; bool this_region_config_set = false; for (PrintObject *print_object : m_objects) { + const LayerRanges *layer_ranges; + { + auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); + assert(it_status != model_object_status.end()); + assert(it_status->status != ModelObjectStatus::Deleted); + layer_ranges = &it_status->layer_ranges; + } if (region_id < print_object->region_volumes.size()) { - for (int volume_id : print_object->region_volumes[region_id]) { - const ModelVolume &volume = *print_object->model_object()->volumes[volume_id]; + for (const std::pair &volume_and_range : print_object->region_volumes[region_id]) { + const ModelVolume &volume = *print_object->model_object()->volumes[volume_and_range.second]; + const DynamicPrintConfig *layer_range_config = layer_ranges->config(volume_and_range.first); if (this_region_config_set) { // If the new config for this volume differs from the other // volume configs currently associated to this region, it means // the region subdivision does not make sense anymore. - if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, volume, num_extruders))) + if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders))) // Regions were split. Reset this print_object. goto print_object_end; } else { - this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, volume, num_extruders); - for (size_t i = 0; i < region_id; ++i) { + this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, layer_range_config, volume, num_extruders); + for (size_t i = 0; i < region_id; ++ i) { const PrintRegion ®ion_other = *m_regions[i]; if (region_other.m_refcnt != 0 && region_other.config().equals(this_region_config)) // Regions were merged. Reset this print_object. @@ -1055,7 +1111,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co update_apply_status(print_object->invalidate_all_steps()); // Decrease the references to regions from this volume. int ireg = 0; - for (const std::vector &volumes : print_object->region_volumes) { + for (const std::vector> &volumes : print_object->region_volumes) { if (! volumes.empty()) -- m_regions[ireg]->m_refcnt; ++ ireg; @@ -1077,52 +1133,65 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co for (size_t idx_print_object = 0; idx_print_object < m_objects.size(); ++ idx_print_object) { PrintObject &print_object0 = *m_objects[idx_print_object]; const ModelObject &model_object = *print_object0.model_object(); - std::vector map_volume_to_region(model_object.volumes.size(), -1); + const LayerRanges *layer_ranges; + { + auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); + assert(it_status != model_object_status.end()); + assert(it_status->status != ModelObjectStatus::Deleted); + layer_ranges = &it_status->layer_ranges; + } + std::vector regions_in_object; + regions_in_object.reserve(64); for (size_t i = idx_print_object; i < m_objects.size() && m_objects[i]->model_object() == &model_object; ++ i) { PrintObject &print_object = *m_objects[i]; bool fresh = print_object.region_volumes.empty(); unsigned int volume_id = 0; + unsigned int idx_region_in_object = 0; for (const ModelVolume *volume : model_object.volumes) { if (! volume->is_model_part() && ! volume->is_modifier()) { ++ volume_id; continue; } - int region_id = -1; - if (&print_object == &print_object0) { - // Get the config applied to this volume. - PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, *volume, num_extruders); - // Find an existing print region with the same config. - int idx_empty_slot = -1; - for (int i = 0; i < (int)m_regions.size(); ++ i) { - if (m_regions[i]->m_refcnt == 0) { - if (idx_empty_slot == -1) - idx_empty_slot = i; - } else if (config.equals(m_regions[i]->config())) { - region_id = i; - break; + // Filter the layer ranges, so they do not overlap and they contain at least a single layer. + // Now insert a volume with a layer range to its own region. + for (auto it_range = layer_ranges->begin(); it_range != layer_ranges->end(); ++ it_range) { + int region_id = -1; + if (&print_object == &print_object0) { + // Get the config applied to this volume. + PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, it_range->second, *volume, num_extruders); + // Find an existing print region with the same config. + int idx_empty_slot = -1; + for (int i = 0; i < (int)m_regions.size(); ++ i) { + if (m_regions[i]->m_refcnt == 0) { + if (idx_empty_slot == -1) + idx_empty_slot = i; + } else if (config.equals(m_regions[i]->config())) { + region_id = i; + break; + } + } + // If no region exists with the same config, create a new one. + if (region_id == -1) { + if (idx_empty_slot == -1) { + region_id = (int)m_regions.size(); + this->add_region(config); + } else { + region_id = idx_empty_slot; + m_regions[region_id]->set_config(std::move(config)); + } } - } - // If no region exists with the same config, create a new one. - if (region_id == -1) { - if (idx_empty_slot == -1) { - region_id = (int)m_regions.size(); - this->add_region(config); - } else { - region_id = idx_empty_slot; - m_regions[region_id]->set_config(std::move(config)); - } - } - map_volume_to_region[volume_id] = region_id; - } else - region_id = map_volume_to_region[volume_id]; - // Assign volume to a region. - if (fresh) { - if (region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty()) - ++ m_regions[region_id]->m_refcnt; - print_object.add_region_volume(region_id, volume_id); - } - ++ volume_id; - } + regions_in_object.emplace_back(region_id); + } else + region_id = regions_in_object[idx_region_in_object ++]; + // Assign volume to a region. + if (fresh) { + if (region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty()) + ++ m_regions[region_id]->m_refcnt; + print_object.add_region_volume(region_id, volume_id, it_range->first); + } + } + ++ volume_id; + } } } @@ -1176,7 +1245,7 @@ std::string Print::validate() const Polygon convex_hull0 = offset( print_object->model_object()->convex_hull_2d( Geometry::assemble_transform(Vec3d::Zero(), rotation, model_instance0->get_scaling_factor(), model_instance0->get_mirror())), - scale_(m_config.extruder_clearance_radius.value) / 2., jtRound, scale_(0.1)).front(); + float(scale_(0.5 * m_config.extruder_clearance_radius.value)), jtRound, float(scale_(0.1))).front(); // Now we check that no instance of convex_hull intersects any of the previously checked object instances. for (const Point © : print_object->m_copies) { Polygon convex_hull = convex_hull0; @@ -1228,7 +1297,6 @@ std::string Print::validate() const bool has_custom_layering = false; std::vector> layer_height_profiles; for (const PrintObject *object : m_objects) { -// has_custom_layering = ! object->model_object()->layer_height_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); has_custom_layering = ! object->model_object()->layer_config_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); // #ys_FIXME_experiment if (has_custom_layering) { layer_height_profiles.assign(m_objects.size(), std::vector()); @@ -1437,9 +1505,9 @@ Flow Print::brim_flow() const generation as well. */ return Flow::new_from_config_width( frPerimeter, - width, - m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1), - this->skirt_first_layer_height(), + width, + (float)m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1), + (float)this->skirt_first_layer_height(), 0 ); } @@ -1459,9 +1527,9 @@ Flow Print::skirt_flow() const generation as well. */ return Flow::new_from_config_width( frPerimeter, - width, - m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1), - this->skirt_first_layer_height(), + width, + (float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1), + (float)this->skirt_first_layer_height(), 0 ); } @@ -1636,20 +1704,20 @@ void Print::_make_skirt() // Initial offset of the brim inner edge from the object (possible with a support & raft). // The skirt will touch the brim if the brim is extruded. - Flow brim_flow = this->brim_flow(); + Flow brim_flow = this->brim_flow(); double actual_brim_width = brim_flow.spacing() * floor(m_config.brim_width.value / brim_flow.spacing()); - coord_t distance = scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.); + auto distance = float(scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.)); // Draw outlines from outside to inside. // Loop while we have less skirts than required or any extruder hasn't reached the min length if any. std::vector extruded_length(extruders.size(), 0.); for (int i = n_skirts, extruder_idx = 0; i > 0; -- i) { this->throw_if_canceled(); // Offset the skirt outside. - distance += coord_t(scale_(spacing)); + distance += float(scale_(spacing)); // Generate the skirt centerline. Polygon loop; { - Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, scale_(0.1)); + Polygons loops = offset(convex_hull, distance, ClipperLib::jtRound, float(scale_(0.1))); Geometry::simplify_polygons(loops, scale_(0.05), &loops); if (loops.empty()) break; @@ -1660,9 +1728,9 @@ void Print::_make_skirt() eloop.paths.emplace_back(ExtrusionPath( ExtrusionPath( erSkirt, - mm3_per_mm, // this will be overridden at G-code export time + (float)mm3_per_mm, // this will be overridden at G-code export time flow.width, - first_layer_height // this will be overridden at G-code export time + (float)first_layer_height // this will be overridden at G-code export time ))); eloop.paths.back().polyline = loop.split_at_first_point(); m_skirt.append(eloop); @@ -1788,7 +1856,7 @@ void Print::_make_wipe_tower() // Insert the new support layer. double height = lt.print_z - m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z; //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway. - it_layer = m_objects.front()->insert_support_layer(it_layer, size_t(-1), height, lt.print_z, lt.print_z - 0.5 * height); + it_layer = m_objects.front()->insert_support_layer(it_layer, -1, height, lt.print_z, lt.print_z - 0.5 * height); ++ it_layer; } } @@ -1815,19 +1883,19 @@ void Print::_make_wipe_tower() WipeTowerPrusaMM::parse_material(m_config.filament_type.get_at(i).c_str()), m_config.temperature.get_at(i), m_config.first_layer_temperature.get_at(i), - m_config.filament_loading_speed.get_at(i), - m_config.filament_loading_speed_start.get_at(i), - m_config.filament_unloading_speed.get_at(i), - m_config.filament_unloading_speed_start.get_at(i), - m_config.filament_toolchange_delay.get_at(i), + (float)m_config.filament_loading_speed.get_at(i), + (float)m_config.filament_loading_speed_start.get_at(i), + (float)m_config.filament_unloading_speed.get_at(i), + (float)m_config.filament_unloading_speed_start.get_at(i), + (float)m_config.filament_toolchange_delay.get_at(i), m_config.filament_cooling_moves.get_at(i), - m_config.filament_cooling_initial_speed.get_at(i), - m_config.filament_cooling_final_speed.get_at(i), + (float)m_config.filament_cooling_initial_speed.get_at(i), + (float)m_config.filament_cooling_final_speed.get_at(i), m_config.filament_ramming_parameters.get_at(i), - m_config.nozzle_diameter.get_at(i)); + (float)m_config.nozzle_diameter.get_at(i)); m_wipe_tower_data.priming = Slic3r::make_unique( - wipe_tower.prime(this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); + wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); // Lets go through the wipe tower layers and determine pairs of extruder changes for each // to pass to wipe_tower (so that it can use it for planning the layout of the tower) @@ -1836,21 +1904,21 @@ void Print::_make_wipe_tower() for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers if (!layer_tools.has_wipe_tower) continue; bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); - wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false); + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id, false); for (const auto extruder_id : layer_tools.extruders) { if ((first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) { float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange // Not all of that can be used for infill purging: - volume_to_wipe -= m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); + volume_to_wipe -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); // try to assign some infills/objects for the wiping: volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe); // add back the minimal amount toforce on the wipe tower: - volume_to_wipe += m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); + volume_to_wipe += (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); // request a toolchange at the wipe tower with at least volume_to_wipe purging amount - wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back(), volume_to_wipe); current_extruder_id = extruder_id; } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 53d6d692db..c024a3ad25 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -80,8 +80,8 @@ private: // Prevents erroneous use by other classes. typedef PrintObjectBaseWithState Inherited; public: - // vector of (vectors of volume ids), indexed by region_id - std::vector> region_volumes; + // vector of (layer height ranges and vectors of volume ids), indexed by region_id + std::vector>> region_volumes; // this is set to true when LayerRegion->slices is split in top/internal/bottom // so that next call to make_perimeters() performs a union() before computing loops @@ -99,10 +99,10 @@ public: BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } // adds region_id, too, if necessary - void add_region_volume(unsigned int region_id, int volume_id) { + void add_region_volume(unsigned int region_id, int volume_id, const t_layer_height_range &layer_range) { if (region_id >= region_volumes.size()) region_volumes.resize(region_id + 1); - region_volumes[region_id].emplace_back(volume_id); + region_volumes[region_id].emplace_back(layer_range, volume_id); } // This is the *total* layer count (including support layers) // this value is not supposed to be compared with Layer::id @@ -141,8 +141,9 @@ public: void slice(); // Helpers to slice support enforcer / blocker meshes by the support generator. - std::vector slice_support_enforcers() const; - std::vector slice_support_blockers() const; + std::vector slice_support_volumes(const ModelVolumeType &model_volume_type) const; + std::vector slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); } + std::vector slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } protected: // to be called from Print only. @@ -165,7 +166,7 @@ protected: void update_slicing_parameters(); static PrintObjectConfig object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders); - static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_region_config, const ModelVolume &volume, size_t num_extruders); + static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders); private: void make_perimeters(); @@ -201,9 +202,11 @@ private: LayerPtrs m_layers; SupportLayerPtrs m_support_layers; - std::vector _slice_region(size_t region_id, const std::vector &z, bool modifier); - std::vector _slice_volumes(const std::vector &z, const std::vector &volumes) const; - std::vector _slice_volume(const std::vector &z, const ModelVolume &volume) const; + std::vector slice_region(size_t region_id, const std::vector &z) const; + std::vector slice_modifiers(size_t region_id, const std::vector &z) const; + std::vector slice_volumes(const std::vector &z, const std::vector &volumes) const; + std::vector slice_volume(const std::vector &z, const ModelVolume &volume) const; + std::vector slice_volume(const std::vector &z, const std::vector &ranges, const ModelVolume &volume) const; }; struct WipeTowerData @@ -292,8 +295,7 @@ public: ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; // The following three methods are used by the Perl tests only. Get rid of them! - void reload_object(size_t idx); - void add_model_object(ModelObject* model_object, int idx = -1); + void add_model_object_perl_tests_only(ModelObject* model_object, int idx = -1); bool apply_config_perl_tests_only(DynamicPrintConfig config); void process() override; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 3add190210..f7d6f891d0 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -49,7 +49,7 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta { // Translate meshes so that our toolpath generation algorithms work with smaller // XY coordinates; this translation is an optimization and not strictly required. - // A cloned mesh will be aligned to 0 before slicing in _slice_region() since we + // A cloned mesh will be aligned to 0 before slicing in slice_region() since we // don't assume it's already aligned and we don't alter the original position in model. // We store the XY translation so that we can place copies correctly in the output G-code // (copies are expressed in G-code coordinates and this translation is not publicly exposed). @@ -590,7 +590,12 @@ bool PrintObject::invalidate_step(PrintObjectStep step) bool PrintObject::invalidate_all_steps() { - return Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); + // First call the "invalidate" functions, which may cancel background processing. + bool result = Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); + // Then reset some of the depending values. + this->m_slicing_params.valid = false; + this->region_volumes.clear(); + return result; } bool PrintObject::has_support_material() const @@ -1354,10 +1359,12 @@ PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObject return config; } -PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegionConfig &default_region_config, const ModelVolume &volume, size_t num_extruders) +PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegionConfig &default_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders) { PrintRegionConfig config = default_region_config; normalize_and_apply_config(config, volume.get_object()->config); + if (layer_range_config != nullptr) + normalize_and_apply_config(config, *layer_range_config); normalize_and_apply_config(config, volume.config); if (! volume.material_id().empty()) normalize_and_apply_config(config, volume.material()->config); @@ -1375,28 +1382,37 @@ void PrintObject::update_slicing_parameters() this->print()->config(), m_config, unscale(this->size(2)), this->object_extruders()); } -SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z) +SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) { - PrintConfig print_config; - PrintObjectConfig object_config; - PrintRegionConfig default_region_config; - print_config .apply(full_config, true); - object_config.apply(full_config, true); - default_region_config.apply(full_config, true); - size_t num_extruders = print_config.nozzle_diameter.size(); - object_config = object_config_from_model_object(object_config, model_object, num_extruders); + PrintConfig print_config; + PrintObjectConfig object_config; + PrintRegionConfig default_region_config; + print_config.apply(full_config, true); + object_config.apply(full_config, true); + default_region_config.apply(full_config, true); + size_t num_extruders = print_config.nozzle_diameter.size(); + object_config = object_config_from_model_object(object_config, model_object, num_extruders); - std::vector object_extruders; - for (const ModelVolume *model_volume : model_object.volumes) - if (model_volume->is_model_part()) - PrintRegion::collect_object_printing_extruders( - print_config, - region_config_from_model_volume(default_region_config, *model_volume, num_extruders), - object_extruders); + std::vector object_extruders; + for (const ModelVolume* model_volume : model_object.volumes) + if (model_volume->is_model_part()) { + PrintRegion::collect_object_printing_extruders( + print_config, + region_config_from_model_volume(default_region_config, nullptr, *model_volume, num_extruders), + object_extruders); + for (const std::pair &range_and_config : model_object.layer_config_ranges) + if (range_and_config.second.has("perimeter_extruder") || + range_and_config.second.has("infill_extruder") || + range_and_config.second.has("solid_infill_extruder")) + PrintRegion::collect_object_printing_extruders( + print_config, + region_config_from_model_volume(default_region_config, &range_and_config.second, *model_volume, num_extruders), + object_extruders); + } sort_remove_duplicates(object_extruders); if (object_max_z <= 0.f) - object_max_z = model_object.raw_bounding_box().size().z(); + object_max_z = (float)model_object.raw_bounding_box().size().z(); return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders); } @@ -1430,13 +1446,12 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c layer_height_profile.clear(); if (layer_height_profile.empty()) { - if (0) + if (0) // if (this->layer_height_profile.empty()) - layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_height_ranges, model_object.volumes); + layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes); else -// layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_height_ranges); - layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges); // #ys_FIXME_experiment - updated = true; + layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges); // #ys_FIXME_experiment + updated = true; } return updated; } @@ -1490,22 +1505,28 @@ void PrintObject::_slice(const std::vector &layer_height_profile) } // Count model parts and modifier meshes, check whether the model parts are of the same region. - int single_volume_region = -2; // not set yet + int all_volumes_single_region = -2; // not set yet + bool has_z_ranges = false; size_t num_volumes = 0; size_t num_modifiers = 0; - std::vector map_volume_to_region(this->model_object()->volumes.size()); for (int region_id = 0; region_id < (int)this->region_volumes.size(); ++ region_id) { - for (int volume_id : this->region_volumes[region_id]) { + int last_volume_id = -1; + for (const std::pair &volume_and_range : this->region_volumes[region_id]) { + const int volume_id = volume_and_range.second; const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; if (model_volume->is_model_part()) { - map_volume_to_region[volume_id] = region_id; - if (single_volume_region == -2) - // first model volume met - single_volume_region = region_id; - else if (single_volume_region != region_id) - // multiple volumes met and they are not equal - single_volume_region = -1; - ++ num_volumes; + if (last_volume_id == volume_id) { + has_z_ranges = true; + } else { + last_volume_id = volume_id; + if (all_volumes_single_region == -2) + // first model volume met + all_volumes_single_region = region_id; + else if (all_volumes_single_region != region_id) + // multiple volumes met and they are not equal + all_volumes_single_region = -1; + ++ num_volumes; + } } else if (model_volume->is_modifier()) ++ num_modifiers; } @@ -1515,13 +1536,13 @@ void PrintObject::_slice(const std::vector &layer_height_profile) // Slice all non-modifier volumes. bool clipped = false; bool upscaled = false; - if (! m_config.clip_multipart_objects.value || single_volume_region >= 0) { + if (! has_z_ranges && (! m_config.clip_multipart_objects.value || all_volumes_single_region >= 0)) { // Cheap path: Slice regions without mutual clipping. // The cheap path is possible if no clipping is allowed or if slicing volumes of just a single region. for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing objects - region " << region_id; // slicing in parallel - std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, false); + std::vector expolygons_by_layer = this->slice_region(region_id, slice_zs); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " start"; for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) @@ -1542,15 +1563,29 @@ void PrintObject::_slice(const std::vector &layer_height_profile) }; std::vector sliced_volumes; sliced_volumes.reserve(num_volumes); - for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) - for (int volume_id : this->region_volumes[region_id]) { + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + const std::vector> &volumes_and_ranges = this->region_volumes[region_id]; + for (size_t i = 0; i < volumes_and_ranges.size(); ) { + int volume_id = volumes_and_ranges[i].second; const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; if (model_volume->is_model_part()) { BOOST_LOG_TRIVIAL(debug) << "Slicing objects - volume " << volume_id; + // Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume. + std::vector ranges; + ranges.emplace_back(volumes_and_ranges[i].first); + size_t j = i + 1; + for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) + if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges[j].first.first) < EPSILON) + ranges.back().second = volumes_and_ranges[j].first.second; + else + ranges.emplace_back(volumes_and_ranges[j].first); // slicing in parallel - sliced_volumes.emplace_back(volume_id, map_volume_to_region[volume_id], this->_slice_volume(slice_zs, *model_volume)); - } + sliced_volumes.emplace_back(volume_id, (int)region_id, this->slice_volume(slice_zs, ranges, *model_volume)); + i = j; + } else + ++ i; } + } // Second clip the volumes in the order they are presented at the user interface. BOOST_LOG_TRIVIAL(debug) << "Slicing objects - parallel clipping - start"; tbb::parallel_for( @@ -1604,7 +1639,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { BOOST_LOG_TRIVIAL(debug) << "Slicing modifier volumes - region " << region_id; // slicing in parallel - std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, true); + std::vector expolygons_by_layer = this->slice_modifiers(region_id, slice_zs); m_print->throw_if_canceled(); if (expolygons_by_layer.empty()) continue; @@ -1620,7 +1655,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) Layer *layer = m_layers[layer_id]; LayerRegion *layerm = layer->m_regions[region_id]; LayerRegion *other_layerm = layer->m_regions[other_region_id]; - if (layerm == nullptr || other_layerm == nullptr) + if (layerm == nullptr || other_layerm == nullptr || other_layerm->slices.empty() || expolygons_by_layer[layer_id].empty()) continue; Polygons other_slices = to_polygons(other_layerm->slices); ExPolygons my_parts = intersection_ex(other_slices, to_polygons(expolygons_by_layer[layer_id])); @@ -1753,46 +1788,127 @@ end: BOOST_LOG_TRIVIAL(debug) << "Slicing objects - make_slices in parallel - end"; } -std::vector PrintObject::_slice_region(size_t region_id, const std::vector &z, bool modifier) +// To be used only if there are no layer span specific configurations applied, which would lead to z ranges being generated for this region. +std::vector PrintObject::slice_region(size_t region_id, const std::vector &z) const { - std::vector volumes; + std::vector volumes; if (region_id < this->region_volumes.size()) { - for (int volume_id : this->region_volumes[region_id]) { - const ModelVolume *volume = this->model_object()->volumes[volume_id]; - if (modifier ? volume->is_modifier() : volume->is_model_part()) - volumes.emplace_back(volume); - } + for (const std::pair &volume_and_range : this->region_volumes[region_id]) { + const ModelVolume *volume = this->model_object()->volumes[volume_and_range.second]; + if (volume->is_model_part()) + volumes.emplace_back(volume); + } } - return this->_slice_volumes(z, volumes); + return this->slice_volumes(z, volumes); } -std::vector PrintObject::slice_support_enforcers() const +// Z ranges are not applicable to modifier meshes, therefore a sinle volume will be found in volume_and_range at most once. +std::vector PrintObject::slice_modifiers(size_t region_id, const std::vector &slice_zs) const +{ + std::vector out; + if (region_id < this->region_volumes.size()) + { + std::vector> volume_ranges; + const std::vector> &volumes_and_ranges = this->region_volumes[region_id]; + volume_ranges.reserve(volumes_and_ranges.size()); + for (size_t i = 0; i < volumes_and_ranges.size(); ) { + int volume_id = volumes_and_ranges[i].second; + const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; + if (model_volume->is_modifier()) { + std::vector ranges; + ranges.emplace_back(volumes_and_ranges[i].first); + size_t j = i + 1; + for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) { + if (! ranges.empty() && std::abs(ranges.back().second - volumes_and_ranges[j].first.first) < EPSILON) + ranges.back().second = volumes_and_ranges[j].first.second; + else + ranges.emplace_back(volumes_and_ranges[j].first); + } + volume_ranges.emplace_back(std::move(ranges)); + i = j; + } else + ++ i; + } + + if (! volume_ranges.empty()) + { + bool equal_ranges = true; + for (size_t i = 1; i < volume_ranges.size(); ++ i) { + assert(! volume_ranges[i].empty()); + if (volume_ranges.front() != volume_ranges[i]) { + equal_ranges = false; + break; + } + } + + if (equal_ranges && volume_ranges.front().size() == 1 && volume_ranges.front().front() == t_layer_height_range(0, DBL_MAX)) { + // No modifier in this region was split to layer spans. + std::vector volumes; + for (const std::pair &volume_and_range : this->region_volumes[region_id]) { + const ModelVolume *volume = this->model_object()->volumes[volume_and_range.second]; + if (volume->is_modifier()) + volumes.emplace_back(volume); + } + out = this->slice_volumes(slice_zs, volumes); + } else { + // Some modifier in this region was split to layer spans. + std::vector merge; + for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { + const std::vector> &volumes_and_ranges = this->region_volumes[region_id]; + for (size_t i = 0; i < volumes_and_ranges.size(); ) { + int volume_id = volumes_and_ranges[i].second; + const ModelVolume *model_volume = this->model_object()->volumes[volume_id]; + if (model_volume->is_modifier()) { + BOOST_LOG_TRIVIAL(debug) << "Slicing modifiers - volume " << volume_id; + // Find the ranges of this volume. Ranges in volumes_and_ranges must not overlap for a single volume. + std::vector ranges; + ranges.emplace_back(volumes_and_ranges[i].first); + size_t j = i + 1; + for (; j < volumes_and_ranges.size() && volume_id == volumes_and_ranges[j].second; ++ j) + ranges.emplace_back(volumes_and_ranges[j].first); + // slicing in parallel + std::vector this_slices = this->slice_volume(slice_zs, ranges, *model_volume); + if (out.empty()) { + out = std::move(this_slices); + merge.assign(out.size(), false); + } else { + for (size_t i = 0; i < out.size(); ++ i) + if (! this_slices[i].empty()) + if (! out[i].empty()) { + append(out[i], this_slices[i]); + merge[i] = true; + } else + out[i] = std::move(this_slices[i]); + } + i = j; + } else + ++ i; + } + } + for (size_t i = 0; i < merge.size(); ++ i) + if (merge[i]) + out[i] = union_ex(out[i]); + } + } + } + + return out; +} + +std::vector PrintObject::slice_support_volumes(const ModelVolumeType &model_volume_type) const { std::vector volumes; for (const ModelVolume *volume : this->model_object()->volumes) - if (volume->is_support_enforcer()) + if (volume->type() == model_volume_type) volumes.emplace_back(volume); std::vector zs; zs.reserve(this->layers().size()); for (const Layer *l : this->layers()) zs.emplace_back((float)l->slice_z); - return this->_slice_volumes(zs, volumes); + return this->slice_volumes(zs, volumes); } -std::vector PrintObject::slice_support_blockers() const -{ - std::vector volumes; - for (const ModelVolume *volume : this->model_object()->volumes) - if (volume->is_support_blocker()) - volumes.emplace_back(volume); - std::vector zs; - zs.reserve(this->layers().size()); - for (const Layer *l : this->layers()) - zs.emplace_back((float)l->slice_z); - return this->_slice_volumes(zs, volumes); -} - -std::vector PrintObject::_slice_volumes(const std::vector &z, const std::vector &volumes) const +std::vector PrintObject::slice_volumes(const std::vector &z, const std::vector &volumes) const { std::vector layers; if (! volumes.empty()) { @@ -1829,34 +1945,71 @@ std::vector PrintObject::_slice_volumes(const std::vector &z, return layers; } -std::vector PrintObject::_slice_volume(const std::vector &z, const ModelVolume &volume) const +std::vector PrintObject::slice_volume(const std::vector &z, const ModelVolume &volume) const { std::vector layers; - // Compose mesh. - //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. - TriangleMesh mesh(volume.mesh()); - mesh.transform(volume.get_matrix(), true); - if (mesh.repaired) { - //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. - stl_check_facets_exact(&mesh.stl); + if (! z.empty()) { + // Compose mesh. + //FIXME better to split the mesh into separate shells, perform slicing over each shell separately and then to use a Boolean operation to merge them. + TriangleMesh mesh(volume.mesh()); + mesh.transform(volume.get_matrix(), true); + if (mesh.repaired) { + //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. + stl_check_facets_exact(&mesh.stl); + } + if (mesh.stl.stats.number_of_facets > 0) { + mesh.transform(m_trafo, true); + // apply XY shift + mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), 0); + // perform actual slicing + TriangleMeshSlicer mslicer; + const Print *print = this->print(); + auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); + // TriangleMeshSlicer needs the shared vertices. + mesh.require_shared_vertices(); + mslicer.init(&mesh, callback); + mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); + m_print->throw_if_canceled(); + } } - if (mesh.stl.stats.number_of_facets > 0) { - mesh.transform(m_trafo, true); - // apply XY shift - mesh.translate(- unscale(m_copies_shift(0)), - unscale(m_copies_shift(1)), 0); - // perform actual slicing - TriangleMeshSlicer mslicer; - const Print *print = this->print(); - auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); - // TriangleMeshSlicer needs the shared vertices. - mesh.require_shared_vertices(); - mslicer.init(&mesh, callback); - mslicer.slice(z, float(m_config.slice_closing_radius.value), &layers, callback); - m_print->throw_if_canceled(); - } return layers; } +// Filter the zs not inside the ranges. The ranges are closed at the botton and open at the top, they are sorted lexicographically and non overlapping. +std::vector PrintObject::slice_volume(const std::vector &z, const std::vector &ranges, const ModelVolume &volume) const +{ + std::vector out; + if (! z.empty() && ! ranges.empty()) { + if (ranges.size() == 1 && z.front() >= ranges.front().first && z.back() < ranges.front().second) { + // All layers fit into a single range. + out = this->slice_volume(z, volume); + } else { + std::vector z_filtered; + std::vector> n_filtered; + z_filtered.reserve(z.size()); + n_filtered.reserve(2 * ranges.size()); + size_t i = 0; + for (const t_layer_height_range &range : ranges) { + for (; i < z.size() && z[i] < range.first; ++ i) ; + size_t first = i; + for (; i < z.size() && z[i] < range.second; ++ i) + z_filtered.emplace_back(z[i]); + if (i > first) + n_filtered.emplace_back(std::make_pair(first, i)); + } + if (! n_filtered.empty()) { + std::vector layers = this->slice_volume(z_filtered, volume); + out.assign(z.size(), ExPolygons()); + i = 0; + for (const std::pair &span : n_filtered) + for (size_t j = span.first; j < span.second; ++ j) + out[j] = std::move(layers[i ++]); + } + } + } + return out; +} + std::string PrintObject::_fix_slicing_errors() { // Collect layers with slicing errors. @@ -2120,7 +2273,7 @@ void PrintObject::clip_fill_surfaces() //Should the pw not be half of the current value? float pw = FLT_MAX; for (const LayerRegion *layerm : layer->m_regions) - pw = std::min(pw, layerm->flow(frPerimeter).scaled_width()); + pw = std::min(pw, (float)layerm->flow(frPerimeter).scaled_width()); // Append such thick perimeters to the areas that need support polygons_append(overhangs, offset2(perimeters, -pw, +pw)); } diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 1c6476eae6..6b0e3f8950 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -153,29 +153,33 @@ SlicingParameters SlicingParameters::create_from_config( return params; } -// Convert layer_height_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for +std::vector> layer_height_ranges(const t_layer_config_ranges &config_ranges) +{ + std::vector> out; + out.reserve(config_ranges.size()); + for (const auto &kvp : config_ranges) + out.emplace_back(kvp.first, kvp.second.option("layer_height")->getFloat()); + return out; +} + +// Convert layer_config_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for // in the height profile and the printed object may be lifted by the raft thickness at the time of the G-code generation. std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, -// const t_layer_height_ranges &layer_height_ranges) const t_layer_config_ranges &layer_config_ranges) // #ys_FIXME_experiment { // 1) If there are any height ranges, trim one by the other to make them non-overlapping. Insert the 1st layer if fixed. std::vector> ranges_non_overlapping; -// ranges_non_overlapping.reserve(layer_height_ranges.size() * 4); ranges_non_overlapping.reserve(layer_config_ranges.size() * 4); // #ys_FIXME_experiment if (slicing_params.first_object_layer_height_fixed()) ranges_non_overlapping.push_back(std::pair( t_layer_height_range(0., slicing_params.first_object_layer_height), slicing_params.first_object_layer_height)); // The height ranges are sorted lexicographically by low / high layer boundaries. -// for (t_layer_height_ranges::const_iterator it_range = layer_height_ranges.begin(); it_range != layer_height_ranges.end(); ++ it_range) { - for (t_layer_config_ranges::const_iterator it_range = layer_config_ranges.begin(); - it_range != layer_config_ranges.end(); ++ it_range) { // #ys_FIXME_experiment + for (t_layer_config_ranges::const_iterator it_range = layer_config_ranges.begin(); it_range != layer_config_ranges.end(); ++ it_range) { coordf_t lo = it_range->first.first; coordf_t hi = std::min(it_range->first.second, slicing_params.object_print_z_height()); -// coordf_t height = it_range->second; - coordf_t height = it_range->second.option("layer_height")->getFloat(); // #ys_FIXME_experiment + coordf_t height = it_range->second.option("layer_height")->getFloat(); if (! ranges_non_overlapping.empty()) // Trim current low with the last high. lo = std::max(lo, ranges_non_overlapping.back().first.second); @@ -224,7 +228,7 @@ std::vector layer_height_profile_from_ranges( // Fill layer_height_profile by heights ensuring a prescribed maximum cusp height. std::vector layer_height_profile_adaptive( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges, + const t_layer_config_ranges & /* layer_config_ranges */, const ModelVolumePtrs &volumes) { // 1) Initialize the SlicingAdaptive class with the object meshes. diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index ea5413e9c6..7ebb3f3296 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -130,17 +130,17 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters } typedef std::pair t_layer_height_range; -typedef std::map t_layer_height_ranges; typedef std::map t_layer_config_ranges; +extern std::vector> layer_height_ranges(const t_layer_config_ranges &config_ranges); + extern std::vector layer_height_profile_from_ranges( const SlicingParameters &slicing_params, -// const t_layer_height_ranges &layer_height_ranges); const t_layer_config_ranges &layer_config_ranges); extern std::vector layer_height_profile_adaptive( const SlicingParameters &slicing_params, - const t_layer_height_ranges &layer_height_ranges, + const t_layer_config_ranges &layer_config_ranges, const ModelVolumePtrs &volumes); diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index fde35ca1e4..0ad4f816a1 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -829,7 +829,7 @@ namespace SupportMaterialInternal { assert(expansion_scaled >= 0.f); for (const ExtrusionPath &ep : loop.paths) if (ep.role() == erOverhangPerimeter && ! ep.polyline.empty()) { - float exp = 0.5f * scale_(ep.width) + expansion_scaled; + float exp = 0.5f * (float)scale_(ep.width) + expansion_scaled; if (ep.is_closed()) { if (ep.size() >= 3) { // This is a complete loop. @@ -2214,7 +2214,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raf // Expand the bases of the support columns in the 1st layer. columns_base->polygons = diff( offset(columns_base->polygons, inflate_factor_1st_layer), - offset(m_object->layers().front()->slices.expolygons, scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + offset(m_object->layers().front()->slices.expolygons, (float)scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (contacts != nullptr) columns_base->polygons = diff(columns_base->polygons, interface_polygons); } @@ -3226,7 +3226,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // TODO: use brim ordering algorithm Polygons to_infill_polygons = to_polygons(to_infill); // TODO: use offset2_ex() - to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing())); + to_infill = offset_ex(to_infill, - 0.4f * float(flow.scaled_spacing())); extrusion_entities_append_paths( base_layer.extrusions, to_polylines(std::move(to_infill_polygons)), diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 8392e534a4..d9bb67f576 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -600,12 +600,12 @@ void Bed3D::render_prusa_shader(bool transparent) const if (position_id != -1) { glsafe(::glEnableVertexAttribArray(position_id)); - glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)m_triangles.get_position_offset())); + glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset())); } if (tex_coords_id != -1) { glsafe(::glEnableVertexAttribArray(tex_coords_id)); - glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)m_triangles.get_tex_coords_offset())); + glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset())); } glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count())); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e8973f6e30..84a4db9659 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1922,9 +1922,6 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode } object->ensure_on_bed(); - - // print.auto_assign_extruders(object); - // print.add_model_object(object); } #ifdef AUTOPLACEMENT_ON_LOAD diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 402b4248ff..e681b8af14 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1129,7 +1129,6 @@ void Selection::copy_to_clipboard() dst_object->config = src_object->config; dst_object->sla_support_points = src_object->sla_support_points; dst_object->sla_points_status = src_object->sla_points_status; -// dst_object->layer_height_ranges = src_object->layer_height_ranges; dst_object->layer_config_ranges = src_object->layer_config_ranges; // #ys_FIXME_experiment dst_object->layer_height_profile = src_object->layer_height_profile; dst_object->origin_translation = src_object->origin_translation; diff --git a/t/print.t b/t/print.t index be2db34318..e76d226afb 100644 --- a/t/print.t +++ b/t/print.t @@ -1,4 +1,4 @@ -use Test::More tests => 6; +use Test::More tests => 2; use strict; use warnings; @@ -31,6 +31,8 @@ use Slic3r::Test; ok abs(unscale($center->[Y]) - $print_center->[Y]) < 0.005, 'print is centered around print_center (Y)'; } +# This is really testing a path, which is no more used by the slicer, just by the test cases. +if (0) { # this represents the aggregate config from presets my $config = Slic3r::Config::new_from_defaults; @@ -40,7 +42,7 @@ use Slic3r::Test; # user sets a per-region option $print->print->objects->[0]->model_object->config->set('fill_density', 100); - $print->print->reload_object(0); +# $print->print->reload_object(0); is $print->print->regions->[0]->config->fill_density, 100, 'region config inherits model object config'; # user exports G-code, thus the default config is reapplied @@ -51,7 +53,7 @@ use Slic3r::Test; # user assigns object extruders $print->print->objects->[0]->model_object->config->set('extruder', 3); $print->print->objects->[0]->model_object->config->set('perimeter_extruder', 2); - $print->print->reload_object(0); +# $print->print->reload_object(0); is $print->print->regions->[0]->config->infill_extruder, 3, 'extruder setting is correctly expanded'; is $print->print->regions->[0]->config->perimeter_extruder, 2, 'extruder setting does not override explicitely specified extruders'; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index c35f967f87..70313ff5e2 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -100,7 +100,6 @@ _constant() %code%{ RETVAL = const_cast(&THIS->objects()); %}; Ref get_object(int idx) %code%{ RETVAL = THIS->objects()[idx]; %}; - void reload_object(int idx); size_t object_count() %code%{ RETVAL = THIS->objects().size(); %}; @@ -141,7 +140,6 @@ _constant() } %}; - void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config_perl_tests_only(DynamicPrintConfig* config) %code%{ RETVAL = THIS->apply_config_perl_tests_only(*config); %}; bool has_infinite_skirt(); From 8bf6e69851657bbd9242d54d8e08f706fcdb1874 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 20 Jun 2019 16:28:37 +0200 Subject: [PATCH 171/627] Removed the layer_height_ranges from the Perl bindings and unit tests. --- xs/t/19_model.t | 8 ++++---- xs/xsp/Model.xsp | 5 ----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/xs/t/19_model.t b/xs/t/19_model.t index d6f6d97a18..48a000e463 100644 --- a/xs/t/19_model.t +++ b/xs/t/19_model.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 4; +use Test::More tests => 3; { my $model = Slic3r::Model->new; @@ -14,9 +14,9 @@ use Test::More tests => 4; $object->origin_translation->translate(10,0,0); is_deeply \@{$object->origin_translation}, [10,0,0], 'origin_translation is modified by ref'; - my $lhr = [ [ 5, 10, 0.1 ] ]; - $object->set_layer_height_ranges($lhr); - is_deeply $object->layer_height_ranges, $lhr, 'layer_height_ranges roundtrip'; +# my $lhr = [ [ 5, 10, 0.1 ] ]; +# $object->set_layer_height_ranges($lhr); +# is_deeply $object->layer_height_ranges, $lhr, 'layer_height_ranges roundtrip'; } __END__ diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 6a2cc60802..af4cdce064 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -206,11 +206,6 @@ ModelMaterial::attributes() Ref model() %code%{ RETVAL = THIS->get_model(); %}; - t_layer_height_ranges layer_height_ranges() - %code%{ RETVAL = THIS->layer_height_ranges; %}; - void set_layer_height_ranges(t_layer_height_ranges ranges) - %code%{ THIS->layer_height_ranges = ranges; %}; - Ref origin_translation() %code%{ RETVAL = &THIS->origin_translation; %}; void set_origin_translation(Vec3d* point) From ac6969c992b8b260f804790edc10d98412ea8060 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 20 Jun 2019 20:23:05 +0200 Subject: [PATCH 172/627] Reworked the Perl unit / integration tests to use the same Print interface that the application is using. Old interface used just for the integration tests was removed. --- lib/Slic3r.pm | 1 - lib/Slic3r/Print/Simple.pm | 104 -------------------- lib/Slic3r/Test.pm | 82 ++++++++-------- src/libslic3r/Model.hpp | 4 +- src/libslic3r/Print.cpp | 194 +------------------------------------ src/libslic3r/Print.hpp | 4 - t/combineinfill.t | 2 +- t/print.t | 27 +++--- t/skirt_brim.t | 4 +- xs/xsp/Config.xsp | 2 +- xs/xsp/Model.xsp | 1 + xs/xsp/Print.xsp | 6 +- 12 files changed, 72 insertions(+), 359 deletions(-) delete mode 100644 lib/Slic3r/Print/Simple.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 44100db8cf..94f0b5658f 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -50,7 +50,6 @@ use Slic3r::Point; use Slic3r::Polygon; use Slic3r::Polyline; use Slic3r::Print::Object; -use Slic3r::Print::Simple; use Slic3r::Surface; our $build = eval "use Slic3r::Build; 1"; diff --git a/lib/Slic3r/Print/Simple.pm b/lib/Slic3r/Print/Simple.pm deleted file mode 100644 index 2ab68f4d38..0000000000 --- a/lib/Slic3r/Print/Simple.pm +++ /dev/null @@ -1,104 +0,0 @@ -# A simple wrapper to quickly print a single model without a GUI. -# Used by the command line slic3r.pl, by command line utilities pdf-slic3s.pl and view-toolpaths.pl, -# and by the quick slice menu of the Slic3r GUI. -# -# It creates and owns an instance of Slic3r::Print to perform the slicing -# and it accepts an instance of Slic3r::Model from the outside. - -package Slic3r::Print::Simple; -use Moo; - -use Slic3r::Geometry qw(X Y); - -has '_print' => ( - is => 'ro', - default => sub { Slic3r::Print->new }, - handles => [qw(apply_config_perl_tests_only extruders output_filepath - total_used_filament total_extruded_volume - placeholder_parser process)], -); - -has 'duplicate' => ( - is => 'rw', - default => sub { 1 }, -); - -has 'scale' => ( - is => 'rw', - default => sub { 1 }, -); - -has 'rotate' => ( - is => 'rw', - default => sub { 0 }, -); - -has 'duplicate_grid' => ( - is => 'rw', - default => sub { [1,1] }, -); - -has 'print_center' => ( - is => 'rw', - default => sub { Slic3r::Pointf->new(100,100) }, -); - -has 'dont_arrange' => ( - is => 'rw', - default => sub { 0 }, -); - -has 'output_file' => ( - is => 'rw', -); - -sub set_model { - # $model is of type Slic3r::Model - my ($self, $model) = @_; - - # make method idempotent so that the object is reusable - $self->_print->clear_objects; - - # make sure all objects have at least one defined instance - my $need_arrange = $model->add_default_instances && ! $self->dont_arrange; - - # apply scaling and rotation supplied from command line if any - foreach my $instance (map @{$_->instances}, @{$model->objects}) { - $instance->set_scaling_factor($instance->scaling_factor * $self->scale); - $instance->set_rotation($instance->rotation + $self->rotate); - } - - if ($self->duplicate_grid->[X] > 1 || $self->duplicate_grid->[Y] > 1) { - $model->duplicate_objects_grid($self->duplicate_grid->[X], $self->duplicate_grid->[Y], $self->_print->config->duplicate_distance); - } elsif ($need_arrange) { - $model->duplicate_objects($self->duplicate, $self->_print->config->min_object_distance); - } elsif ($self->duplicate > 1) { - # if all input objects have defined position(s) apply duplication to the whole model - $model->duplicate($self->duplicate, $self->_print->config->min_object_distance); - } - $_->translate(0,0,-$_->bounding_box->z_min) for @{$model->objects}; - $model->center_instances_around_point($self->print_center) if (! $self->dont_arrange); - - foreach my $model_object (@{$model->objects}) { - $self->_print->auto_assign_extruders($model_object); - $self->_print->add_model_object($model_object); - } -} - -sub export_gcode { - my ($self) = @_; - $self->_print->validate; - $self->_print->export_gcode($self->output_file // ''); -} - -sub export_png { - my ($self) = @_; - - $self->_before_export; - - $self->_print->export_png(output_file => $self->output_file); - - $self->_after_export; -} - -1; diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index d1b99e48c4..570bca41bc 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -146,60 +146,66 @@ sub mesh { } sub model { - my ($model_name, %params) = @_; + my ($model_names, %params) = @_; + $model_names = [ $model_names ] if ! ref($model_names); - my $input_file = "${model_name}.stl"; - my $mesh = mesh($model_name, %params); -# $mesh->write_ascii("out/$input_file"); - my $model = Slic3r::Model->new; - my $object = $model->add_object(input_file => $input_file); - $model->set_material($model_name); - $object->add_volume(mesh => $mesh, material_id => $model_name); - $object->add_instance( - offset => Slic3r::Pointf->new(0,0), - # 3D full transform - rotation => Slic3r::Pointf3->new(0, 0, $params{rotation} // 0), - scaling_factor => Slic3r::Pointf3->new($params{scale} // 1, $params{scale} // 1, $params{scale} // 1), - # old transform -# rotation => $params{rotation} // 0, -# scaling_factor => $params{scale} // 1, - ); + + for my $model_name (@$model_names) { + my $input_file = "${model_name}.stl"; + my $mesh = mesh($model_name, %params); + # $mesh->write_ascii("out/$input_file"); + + my $object = $model->add_object(input_file => $input_file); + $model->set_material($model_name); + $object->add_volume(mesh => $mesh, material_id => $model_name); + $object->add_instance( + offset => Slic3r::Pointf->new(0,0), + # 3D full transform + rotation => Slic3r::Pointf3->new(0, 0, $params{rotation} // 0), + scaling_factor => Slic3r::Pointf3->new($params{scale} // 1, $params{scale} // 1, $params{scale} // 1), + # old transform + # rotation => $params{rotation} // 0, + # scaling_factor => $params{scale} // 1, + ); + } return $model; } sub init_print { my ($models, %params) = @_; + my $model; + if (ref($models) eq 'ARRAY') { + $model = model($models, %params); + } elsif (ref($models)) { + $model = $models; + } else { + $model = model([$models], %params); + } my $config = Slic3r::Config->new; $config->apply($params{config}) if $params{config}; $config->set('gcode_comments', 1) if $ENV{SLIC3R_TESTS_GCODE}; my $print = Slic3r::Print->new; - $print->apply_config_perl_tests_only($config); - - $models = [$models] if ref($models) ne 'ARRAY'; - $models = [ map { ref($_) ? $_ : model($_, %params) } @$models ]; - for my $model (@$models) { - die "Unknown model in test" if !defined $model; - if (defined $params{duplicate} && $params{duplicate} > 1) { - $model->duplicate($params{duplicate} // 1, $print->config->min_object_distance); - } - $model->arrange_objects($print->config->min_object_distance); - $model->center_instances_around_point($params{print_center} ? Slic3r::Pointf->new(@{$params{print_center}}) : Slic3r::Pointf->new(100,100)); - foreach my $model_object (@{$model->objects}) { - $print->auto_assign_extruders($model_object); - $print->add_model_object($model_object); - } + die "Unknown model in test" if !defined $model; + if (defined $params{duplicate} && $params{duplicate} > 1) { + $model->duplicate($params{duplicate} // 1, $config->min_object_distance); } - # Call apply_config_perl_tests_only one more time, so that the layer height profiles are updated over all PrintObjects. - $print->apply_config_perl_tests_only($config); + $model->arrange_objects($config->min_object_distance); + $model->center_instances_around_point($params{print_center} ? Slic3r::Pointf->new(@{$params{print_center}}) : Slic3r::Pointf->new(100,100)); + foreach my $model_object (@{$model->objects}) { + $model_object->ensure_on_bed; + $print->auto_assign_extruders($model_object); + } + + $print->apply($model, $config); $print->validate; # We return a proxy object in order to keep $models alive as required by the Print API. return Slic3r::Test::Print->new( - print => $print, - models => $models, + print => $print, + model => $model, ); } @@ -250,7 +256,7 @@ sub add_facet { package Slic3r::Test::Print; use Moo; -has 'print' => (is => 'ro', required => 1, handles => [qw(process apply_config_perl_tests_only)]); -has 'models' => (is => 'ro', required => 1); +has 'print' => (is => 'ro', required => 1, handles => [qw(process apply)]); +has 'model' => (is => 'ro', required => 1); 1; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index c1850df980..bac8ae5077 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -597,8 +597,8 @@ public: Model() {} ~Model() { this->clear_objects(); this->clear_materials(); } - /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ - /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ + // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" + // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). Model(const Model &rhs) : ModelBase(-1) { this->assign_copy(rhs); } explicit Model(Model &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); } Model& operator=(const Model &rhs) { this->assign_copy(rhs); return *this; } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 5e4f253348..227123f72a 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -328,198 +328,6 @@ double Print::max_allowed_layer_height() const return nozzle_diameter_max; } -// Caller is responsible for supplying models whose objects don't collide -// and have explicit instance positions. -void Print::add_model_object_perl_tests_only(ModelObject* model_object, int idx) -{ - tbb::mutex::scoped_lock lock(this->state_mutex()); - // Add a copy of this ModelObject to this Print. - m_model.objects.emplace_back(ModelObject::new_copy(*model_object)); - m_model.objects.back()->set_model(&m_model); - // Initialize a new print object and store it at the given position. - PrintObject *object = new PrintObject(this, model_object, true); - if (idx != -1) { - delete m_objects[idx]; - m_objects[idx] = object; - } else - m_objects.emplace_back(object); - // Invalidate all print steps. - this->invalidate_all_steps(); - - // Set the transformation matrix without translation from the first instance. - if (! model_object->instances.empty()) { - // Trafo and bounding box, both in world coordinate system. - Transform3d trafo = model_object->instances.front()->get_matrix(); - BoundingBoxf3 bbox = model_object->instance_bounding_box(0); - // Now shift the object up to align it with the print bed. - trafo.data()[14] -= bbox.min(2); - // and reset the XY translation. - trafo.data()[12] = 0; - trafo.data()[13] = 0; - object->set_trafo(trafo); - } - - int volume_id = 0; - for (const ModelVolume *volume : model_object->volumes) { - if (! volume->is_model_part() && ! volume->is_modifier()) - continue; - // Get the config applied to this volume. - PrintRegionConfig config = PrintObject::region_config_from_model_volume(m_default_region_config, nullptr, *volume, 99999); - // Find an existing print region with the same config. - int region_id = -1; - for (int i = 0; i < (int)m_regions.size(); ++ i) - if (config.equals(m_regions[i]->config())) { - region_id = i; - break; - } - // If no region exists with the same config, create a new one. - if (region_id == -1) { - region_id = (int)m_regions.size(); - this->add_region(config); - } - // Assign volume to a region. - object->add_region_volume((unsigned int)region_id, volume_id, t_layer_height_range(0, DBL_MAX)); - ++ volume_id; - } - - // Apply config to print object. - object->config_apply(this->default_object_config()); - { - //normalize_and_apply_config(object->config(), model_object->config); - DynamicPrintConfig src_normalized(model_object->config); - src_normalized.normalize(); - object->config_apply(src_normalized, true); - } -} - -// This function is only called through the Perl-C++ binding from the unit tests, should be -// removed when unit tests are rewritten to C++. -bool Print::apply_config_perl_tests_only(DynamicPrintConfig config) -{ - tbb::mutex::scoped_lock lock(this->state_mutex()); - - - // Perl unit tests were failing in case the preset was not normalized (e.g. https://github.com/prusa3d/PrusaSlicer/issues/2288 was caused - // by too short max_layer_height vector. Calling the necessary function Preset::normalize(...) is not currently possible because there is no - // access to preset. This should be solved when the unit tests are rewritten to C++. For now we just copy-pasted code from Preset.cpp - // to make sure the unit tests pass (functions set_num_extruders and nozzle_options()). - auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter", true)); - assert(nozzle_diameter != nullptr); - const auto &defaults = FullPrintConfig::defaults(); - for (const std::string &key : { "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", - "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", - "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", - "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour" }) - { - auto *opt = config.option(key, true); - assert(opt != nullptr); - assert(opt->is_vector()); - unsigned int num_extruders = (unsigned int)nozzle_diameter->values.size(); - static_cast(opt)->resize(num_extruders, defaults.option(key)); - } - - // we get a copy of the config object so we can modify it safely - config.normalize(); - - // apply variables to placeholder parser - this->placeholder_parser().apply_config(config); - - // handle changes to print config - t_config_option_keys print_diff = m_config.diff(config); - m_config.apply_only(config, print_diff, true); - bool invalidated = this->invalidate_state_by_config_options(print_diff); - - // handle changes to object config defaults - m_default_object_config.apply(config, true); - for (PrintObject *object : m_objects) { - // we don't assume that config contains a full ObjectConfig, - // so we base it on the current print-wise default - PrintObjectConfig new_config = this->default_object_config(); - // we override the new config with object-specific options - normalize_and_apply_config(new_config, object->model_object()->config); - // check whether the new config is different from the current one - t_config_option_keys diff = object->config().diff(new_config); - object->config_apply_only(new_config, diff, true); - invalidated |= object->invalidate_state_by_config_options(diff); - } - - // handle changes to regions config defaults - m_default_region_config.apply(config, true); - - // All regions now have distinct settings. - // Check whether applying the new region config defaults we'd get different regions. - bool rearrange_regions = false; - { - // Collect the already visited region configs into other_region_configs, - // so one may check for duplicates. - std::vector other_region_configs; - for (size_t region_id = 0; region_id < m_regions.size(); ++ region_id) { - PrintRegion ®ion = *m_regions[region_id]; - PrintRegionConfig this_region_config; - bool this_region_config_set = false; - for (PrintObject *object : m_objects) { - if (region_id < object->region_volumes.size()) { - for (const std::pair &volume_and_range : object->region_volumes[region_id]) { - const ModelVolume &volume = *object->model_object()->volumes[volume_and_range.second]; - if (this_region_config_set) { - // If the new config for this volume differs from the other - // volume configs currently associated to this region, it means - // the region subdivision does not make sense anymore. - if (! this_region_config.equals(PrintObject::region_config_from_model_volume(m_default_region_config, nullptr, volume, 99999))) { - rearrange_regions = true; - goto exit_for_rearrange_regions; - } - } else { - this_region_config = PrintObject::region_config_from_model_volume(m_default_region_config, nullptr, volume, 99999); - this_region_config_set = true; - } - for (const PrintRegionConfig &cfg : other_region_configs) { - // If the new config for this volume equals any of the other - // volume configs that are not currently associated to this - // region, it means the region subdivision does not make - // sense anymore. - if (cfg.equals(this_region_config)) { - rearrange_regions = true; - goto exit_for_rearrange_regions; - } - } - } - } - } - if (this_region_config_set) { - t_config_option_keys diff = region.config().diff(this_region_config); - if (! diff.empty()) { - region.config_apply_only(this_region_config, diff, false); - for (PrintObject *object : m_objects) - if (region_id < object->region_volumes.size() && ! object->region_volumes[region_id].empty()) - invalidated |= object->invalidate_state_by_config_options(diff); - } - other_region_configs.emplace_back(std::move(this_region_config)); - } - } - } - -exit_for_rearrange_regions: - - if (rearrange_regions) { - // The current subdivision of regions does not make sense anymore. - // We need to remove all objects and re-add them. - ModelObjectPtrs model_objects; - model_objects.reserve(m_objects.size()); - for (PrintObject *object : m_objects) - model_objects.push_back(object->model_object()); - this->clear(); - for (ModelObject *mo : model_objects) - this->add_model_object_perl_tests_only(mo); - invalidated = true; - } - - for (PrintObject *object : m_objects) - object->update_slicing_parameters(); - - return invalidated; -} - // Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new // in the exact order and with the same IDs. // It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order. @@ -1247,7 +1055,7 @@ std::string Print::validate() const Geometry::assemble_transform(Vec3d::Zero(), rotation, model_instance0->get_scaling_factor(), model_instance0->get_mirror())), float(scale_(0.5 * m_config.extruder_clearance_radius.value)), jtRound, float(scale_(0.1))).front(); // Now we check that no instance of convex_hull intersects any of the previously checked object instances. - for (const Point © : print_object->m_copies) { + for (const Point © : print_object->copies()) { Polygon convex_hull = convex_hull0; convex_hull.translate(copy); if (! intersection(convex_hulls_other, convex_hull).empty()) diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index c024a3ad25..c3fa5e062f 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -294,10 +294,6 @@ public: ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; - // The following three methods are used by the Perl tests only. Get rid of them! - void add_model_object_perl_tests_only(ModelObject* model_object, int idx = -1); - bool apply_config_perl_tests_only(DynamicPrintConfig config); - void process() override; // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. // If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r). diff --git a/t/combineinfill.t b/t/combineinfill.t index 8aa0ff5e39..282bf467ac 100644 --- a/t/combineinfill.t +++ b/t/combineinfill.t @@ -89,7 +89,7 @@ plan tests => 8; # we disable combination after infill has been generated $config->set('infill_every_layers', 1); - $print->apply_config_perl_tests_only($config); + $print->apply($print->print->model->clone, $config); $print->process; ok !(defined first { @{$_->get_region(0)->fill_surfaces} == 0 } diff --git a/t/print.t b/t/print.t index e76d226afb..2144e80c1e 100644 --- a/t/print.t +++ b/t/print.t @@ -1,4 +1,4 @@ -use Test::More tests => 2; +use Test::More tests => 6; use strict; use warnings; @@ -31,30 +31,33 @@ use Slic3r::Test; ok abs(unscale($center->[Y]) - $print_center->[Y]) < 0.005, 'print is centered around print_center (Y)'; } -# This is really testing a path, which is no more used by the slicer, just by the test cases. -if (0) { # this represents the aggregate config from presets my $config = Slic3r::Config::new_from_defaults; + # Define 4 extruders. + $config->set('nozzle_diameter', [0.4, 0.4, 0.4, 0.4]); # user adds one object to the plater my $print = Slic3r::Test::init_print(my $model = Slic3r::Test::model('20mm_cube'), config => $config); # user sets a per-region option - $print->print->objects->[0]->model_object->config->set('fill_density', 100); -# $print->print->reload_object(0); + my $model2 = $model->clone; + $model2->get_object(0)->config->set('fill_density', 100); + $print->apply($model2, $config); + is $print->print->regions->[0]->config->fill_density, 100, 'region config inherits model object config'; # user exports G-code, thus the default config is reapplied - $print->print->apply_config_perl_tests_only($config); - - is $print->print->regions->[0]->config->fill_density, 100, 'apply_config() does not override per-object settings'; + $model2->get_object(0)->config->erase('fill_density'); + $print->apply($model2, $config); + + is $print->print->regions->[0]->config->fill_density, 20, 'region config is resetted'; # user assigns object extruders - $print->print->objects->[0]->model_object->config->set('extruder', 3); - $print->print->objects->[0]->model_object->config->set('perimeter_extruder', 2); -# $print->print->reload_object(0); - + $model2->get_object(0)->config->set('extruder', 3); + $model2->get_object(0)->config->set('perimeter_extruder', 2); + $print->apply($model2, $config); + is $print->print->regions->[0]->config->infill_extruder, 3, 'extruder setting is correctly expanded'; is $print->print->regions->[0]->config->perimeter_extruder, 2, 'extruder setting does not override explicitely specified extruders'; } diff --git a/t/skirt_brim.t b/t/skirt_brim.t index b054357841..beb3c94c50 100644 --- a/t/skirt_brim.t +++ b/t/skirt_brim.t @@ -91,6 +91,8 @@ use Slic3r::Test; { my $config = Slic3r::Config::new_from_defaults; + # Define 4 extruders. + $config->set('nozzle_diameter', [0.4, 0.4, 0.4, 0.4]); $config->set('layer_height', 0.4); $config->set('first_layer_height', 0.4); $config->set('skirts', 1); @@ -106,7 +108,7 @@ use Slic3r::Test; # we enable support material after skirt has been generated $config->set('support_material', 1); - $print->apply_config_perl_tests_only($config); + $print->apply($print->print->model->clone, $config); my $skirt_length = 0; my @extrusion_points = (); diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 4d48a2c6f1..e193a23217 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -49,7 +49,7 @@ void erase(t_config_option_key opt_key); void normalize(); %name{setenv} void setenv_(); - double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; + double min_object_distance() %code{% PrintConfig cfg; cfg.apply(*THIS, true); RETVAL = cfg.min_object_distance(); %}; static DynamicPrintConfig* load(char *path) %code%{ auto config = new DynamicPrintConfig(); diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index af4cdce064..35b1c01ce8 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -211,6 +211,7 @@ ModelMaterial::attributes() void set_origin_translation(Vec3d* point) %code%{ THIS->origin_translation = *point; %}; + void ensure_on_bed(); bool needed_repair() const; int materials_count() const; int facets_count(); diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 70313ff5e2..df5f48587a 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -70,6 +70,8 @@ _constant() Print(); ~Print(); + Ref model() + %code%{ RETVAL = const_cast(&THIS->model()); %}; Ref config() %code%{ RETVAL = const_cast(static_cast(&THIS->config())); %}; Ref placeholder_parser() @@ -140,8 +142,8 @@ _constant() } %}; - bool apply_config_perl_tests_only(DynamicPrintConfig* config) - %code%{ RETVAL = THIS->apply_config_perl_tests_only(*config); %}; + bool apply(Model *model, DynamicPrintConfig* config) + %code%{ RETVAL = THIS->apply(*model, *config); %}; bool has_infinite_skirt(); std::vector extruders() const; int validate() %code%{ From b7a949a53e1bb6b1694b1ce2e6d747760fb371b4 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 20 Jun 2019 20:40:17 +0200 Subject: [PATCH 173/627] Fix for C++11 --- src/libslic3r/Print.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 227123f72a..8590a1a0d6 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -572,8 +572,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); return (it == m_ranges.end()) ? nullptr : it->second; } - auto begin() const { return m_ranges.cbegin(); } - auto end() const { return m_ranges.cend(); } + std::vector>::const_iterator begin() const { return m_ranges.cbegin(); } + std::vector>::const_iterator end() const { return m_ranges.cend(); } private: std::vector> m_ranges; }; From 7de5a42df3d59b928d3cfb1213278bd1cfbb8d59 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 20 Jun 2019 21:07:26 +0200 Subject: [PATCH 174/627] Fixed compilation on clang / OSX (missing float.h) --- src/libslic3r/Print.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 8590a1a0d6..60bd18a592 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -12,6 +12,8 @@ //#include "PrintExport.hpp" +#include + #include #include #include From 743a08f0cf52d65fead7be251a466f915d22aece Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 21 Jun 2019 10:58:20 +0200 Subject: [PATCH 175/627] WipeTower - fixed a crash in extrude_explicit when called from finish_layer before the first toolchange --- src/libslic3r/GCode/WipeTower.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 8f7f9f26c3..37e4040d19 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1230,7 +1230,15 @@ void WipeTower::generate(std::vector> & make_wipe_tower_square(); m_layer_info = m_plan.begin(); - m_current_tool = (unsigned int)(-2); // we don't know which extruder to start with - we'll set it according to the first toolchange + + // we don't know which extruder to start with - we'll set it according to the first toolchange + for (const auto& layer : m_plan) { + if (!layer.tool_changes.empty()) { + m_current_tool = layer.tool_changes.front().old_tool; + break; + } + } + for (auto& used : m_used_filament_length) // reset used filament stats used = 0.f; @@ -1246,11 +1254,8 @@ void WipeTower::generate(std::vector> & if (!m_peters_wipe_tower && m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width) m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f; - for (const auto &toolchange : layer.tool_changes) { - if (m_current_tool == (unsigned int)(-2)) - m_current_tool = toolchange.old_tool; + for (const auto &toolchange : layer.tool_changes) layer_result.emplace_back(tool_change(toolchange.new_tool, false)); - } if (! layer_finished()) { auto finish_layer_toolchange = finish_layer(); From 301eda7369acc02c534a454cfe4c49d8c9ccea2a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 24 Jun 2019 09:27:19 +0200 Subject: [PATCH 176/627] ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION set as default --- src/libslic3r/Technologies.hpp | 3 - src/slic3r/GUI/3DBed.cpp | 16 ------ src/slic3r/GUI/3DScene.cpp | 4 -- src/slic3r/GUI/GLCanvas3D.cpp | 2 - src/slic3r/GUI/GLCanvas3DManager.cpp | 85 ---------------------------- src/slic3r/GUI/GLCanvas3DManager.hpp | 28 --------- 6 files changed, 138 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index d7e5aed768..657dab95f8 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -49,7 +49,4 @@ // Enable saving textures on GPU in compressed format #define ENABLE_COMPRESSED_TEXTURES 1 -// Enable texture max size to be dependent on detected OpenGL version -#define ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION 1 - #endif // _technologies_h_ diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 04ab4e2da2..d63e054c56 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -546,7 +546,6 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const std::string model_path = resources_dir() + "/models/" + key; -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION #if !ENABLE_COMPRESSED_TEXTURES // use anisotropic filter if graphic card allows GLfloat max_anisotropy = GLCanvas3DManager::get_gl_info().get_max_anisotropy(); @@ -554,21 +553,6 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const // use higher resolution images if graphic card and opengl version allow GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); -#else -#if !ENABLE_COMPRESSED_TEXTURES - // use anisotropic filter if graphic card allows - GLfloat max_anisotropy = 0.0f; - if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) - glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy)); -#endif // !ENABLE_COMPRESSED_TEXTURES - - // use higher resolution images if graphic card allows - GLint max_tex_size; - glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size)); - - // clamp or the texture generation becomes too slow - max_tex_size = std::min(max_tex_size, 8192); -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION std::string filename = tex_path + ".svg"; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 2e30f00cae..f9a79f2d8c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -2015,11 +2015,7 @@ bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs) std::string _3DScene::get_gl_info(bool format_as_html, bool extensions) { -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION return Slic3r::GUI::GLCanvas3DManager::get_gl_info().to_string(format_as_html, extensions); -#else - return s_canvas_mgr.get_gl_info(format_as_html, extensions); -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION } bool _3DScene::add_canvas(wxGLCanvas* canvas, GUI::Bed3D& bed, GUI::Camera& camera, GUI::GLToolbar& view_toolbar) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a71c030967..973fd1dd94 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1756,11 +1756,9 @@ void GLCanvas3D::render() ImGui::SameLine(); imgui.text(GLCanvas3DManager::are_compressed_textures_supported() ? "supported" : "not supported"); #endif // ENABLE_COMPRESSED_TEXTURES -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION imgui.text("Max texture size: "); ImGui::SameLine(); imgui.text(std::to_string(GLCanvas3DManager::get_gl_info().get_max_tex_size())); -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION imgui.end(); #endif // ENABLE_RENDER_STATISTICS diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index d213990ba3..a123fcfa7c 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -15,17 +15,14 @@ #include #include -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION #ifdef __APPLE__ #include "../Utils/MacDarkMode.hpp" #endif // __APPLE__ -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION namespace Slic3r { namespace GUI { GLCanvas3DManager::GLInfo::GLInfo() -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION : m_detected(false) , m_version("") , m_glsl_version("") @@ -33,16 +30,9 @@ GLCanvas3DManager::GLInfo::GLInfo() , m_renderer("") , m_max_tex_size(0) , m_max_anisotropy(0.0f) -#else - : version("") - , glsl_version("") - , vendor("") - , renderer("") -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION { } -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION const std::string& GLCanvas3DManager::GLInfo::get_version() const { if (!m_detected) @@ -123,28 +113,7 @@ void GLCanvas3DManager::GLInfo::detect() const m_detected = true; } -#else -void GLCanvas3DManager::GLInfo::detect() -{ - const char* data = (const char*)::glGetString(GL_VERSION); - if (data != nullptr) - version = data; - data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION); - if (data != nullptr) - glsl_version = data; - - data = (const char*)::glGetString(GL_VENDOR); - if (data != nullptr) - vendor = data; - - data = (const char*)::glGetString(GL_RENDERER); - if (data != nullptr) - renderer = data; -} -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION - -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { if (!m_detected) @@ -175,42 +144,11 @@ bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int majo else return gl_minor >= minor; } -#else -bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const -{ - std::vector tokens; - boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); - - if (tokens.empty()) - return false; - - std::vector numbers; - boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on); - - unsigned int gl_major = 0; - unsigned int gl_minor = 0; - - if (numbers.size() > 0) - gl_major = ::atoi(numbers[0].c_str()); - - if (numbers.size() > 1) - gl_minor = ::atoi(numbers[1].c_str()); - - if (gl_major < major) - return false; - else if (gl_major > major) - return true; - else - return gl_minor >= minor; -} -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool extensions) const { -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION if (!m_detected) detect(); -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION std::stringstream out; @@ -221,17 +159,10 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten std::string line_end = format_as_html ? "
" : "\n"; out << h2_start << "OpenGL installation" << h2_end << line_end; -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION out << b_start << "GL version: " << b_end << (m_version.empty() ? "N/A" : m_version) << line_end; out << b_start << "Vendor: " << b_end << (m_vendor.empty() ? "N/A" : m_vendor) << line_end; out << b_start << "Renderer: " << b_end << (m_renderer.empty() ? "N/A" : m_renderer) << line_end; out << b_start << "GLSL version: " << b_end << (m_glsl_version.empty() ? "N/A" : m_glsl_version) << line_end; -#else - out << b_start << "GL version: " << b_end << (version.empty() ? "N/A" : version) << line_end; - out << b_start << "Vendor: " << b_end << (vendor.empty() ? "N/A" : vendor) << line_end; - out << b_start << "Renderer: " << b_end << (renderer.empty() ? "N/A" : renderer) << line_end; - out << b_start << "GLSL version: " << b_end << (glsl_version.empty() ? "N/A" : glsl_version) << line_end; -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION if (extensions) { @@ -258,9 +189,7 @@ GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas #if ENABLE_COMPRESSED_TEXTURES bool GLCanvas3DManager::s_compressed_textures_supported = false; #endif // ENABLE_COMPRESSED_TEXTURES -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info; -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION GLCanvas3DManager::GLCanvas3DManager() : m_context(nullptr) @@ -340,16 +269,9 @@ void GLCanvas3DManager::init_gl() if (!m_gl_initialized) { glewInit(); -#if !ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION - m_gl_info.detect(); -#endif // !ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION const AppConfig* config = GUI::get_app_config(); m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1"); -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION m_use_VBOs = !m_use_legacy_opengl && s_gl_info.is_version_greater_or_equal_to(2, 0); -#else - m_use_VBOs = !m_use_legacy_opengl && m_gl_info.is_version_greater_or_equal_to(2, 0); -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION m_gl_initialized = true; #if ENABLE_COMPRESSED_TEXTURES if (GLEW_EXT_texture_compression_s3tc) @@ -360,13 +282,6 @@ void GLCanvas3DManager::init_gl() } } -#if !ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION -std::string GLCanvas3DManager::get_gl_info(bool format_as_html, bool extensions) const -{ - return m_gl_info.to_string(format_as_html, extensions); -} -#endif // !ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION - bool GLCanvas3DManager::init(wxGLCanvas* canvas) { CanvasesMap::const_iterator it = do_get_canvas(canvas); diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 3ad30411c2..e6e12ca5d6 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -29,7 +29,6 @@ struct Camera; class GLCanvas3DManager { -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION public: class GLInfo { @@ -61,26 +60,8 @@ public: private: void detect() const; }; -#else - struct GLInfo - { - std::string version; - std::string glsl_version; - std::string vendor; - std::string renderer; - GLInfo(); - - void detect(); - bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const; - - std::string to_string(bool format_as_html, bool extensions) const; - }; -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION - -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION private: -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION enum EMultisampleState : unsigned char { MS_Unknown, @@ -92,11 +73,7 @@ private: CanvasesMap m_canvases; wxGLContext* m_context; -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION static GLInfo s_gl_info; -#else - GLInfo m_gl_info; -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION bool m_gl_initialized; bool m_use_legacy_opengl; bool m_use_VBOs; @@ -116,9 +93,6 @@ public: unsigned int count() const; void init_gl(); -#if !ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION - std::string get_gl_info(bool format_as_html, bool extensions) const; -#endif // !ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION bool init(wxGLCanvas* canvas); @@ -131,9 +105,7 @@ public: static wxGLCanvas* create_wxglcanvas(wxWindow *parent); -#if ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION static const GLInfo& get_gl_info() { return s_gl_info; } -#endif // ENABLE_TEXTURES_MAXSIZE_DEPENDENT_ON_OPENGL_VERSION private: CanvasesMap::iterator do_get_canvas(wxGLCanvas* canvas); From 8b3d88bc0a654fe8c488bcf4f30b6896587c116c Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 24 Jun 2019 09:38:46 +0200 Subject: [PATCH 177/627] Adaptive perspective camera frustrum --- src/slic3r/GUI/Camera.cpp | 100 +++++++++++++++++++++++++++++----- src/slic3r/GUI/Camera.hpp | 12 +++- src/slic3r/GUI/GLCanvas3D.cpp | 5 +- 3 files changed, 99 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index b8c182ef4a..1f8513ac29 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -24,6 +24,8 @@ namespace GUI { const double Camera::DefaultDistance = 1000.0; double Camera::FrustrumMinZSize = 50.0; double Camera::FrustrumZMargin = 10.0; +double Camera::FovMinDeg = 5.0; +double Camera::FovMaxDeg = 75.0; Camera::Camera() : phi(45.0f) @@ -34,6 +36,7 @@ Camera::Camera() , m_theta(45.0f) , m_zoom(1.0) , m_distance(DefaultDistance) + , m_gui_scale(1.0) , m_view_matrix(Transform3d::Identity()) , m_projection_matrix(Transform3d::Identity()) { @@ -148,6 +151,18 @@ bool Camera::select_view(const std::string& direction) return false; } +double Camera::get_fov() const +{ + switch (m_type) + { + case Perspective: + return 2.0 * Geometry::rad2deg(std::atan(1.0 / m_projection_matrix.matrix()(1, 1))); + default: + case Ortho: + return 0.0; + }; +} + void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h) const { glsafe(::glViewport(0, 0, w, h)); @@ -174,17 +189,67 @@ void Camera::apply_view_matrix() const void Camera::apply_projection(const BoundingBoxf3& box) const { - m_frustrum_zs = calc_tight_frustrum_zs_around(box); + m_distance = DefaultDistance; + double w = 0.0; + double h = 0.0; - double w = (double)m_viewport[2]; - double h = (double)m_viewport[3]; - - double two_zoom = 2.0 * m_zoom; - if (two_zoom != 0.0) + while (true) { - double inv_two_zoom = 1.0 / two_zoom; - w *= inv_two_zoom; - h *= inv_two_zoom; + m_frustrum_zs = calc_tight_frustrum_zs_around(box); + + w = (double)m_viewport[2]; + h = (double)m_viewport[3]; + + double two_zoom = 2.0 * m_zoom; + if (two_zoom != 0.0) + { + double inv_two_zoom = 1.0 / two_zoom; + w *= inv_two_zoom; + h *= inv_two_zoom; + } + + switch (m_type) + { + default: + case Ortho: + { + m_gui_scale = 1.0; + break; + } + case Perspective: + { + // scale near plane to keep w and h constant on the plane at z = m_distance + double scale = m_frustrum_zs.first / m_distance; + w *= scale; + h *= scale; + m_gui_scale = scale; + break; + } + } + + if (m_type == Perspective) + { + double fov_rad = 2.0 * std::atan(h / m_frustrum_zs.first); + double fov_deg = Geometry::rad2deg(fov_rad); + + // adjust camera distance to keep fov in a limited range + if (fov_deg > FovMaxDeg + 0.001) + { + double new_near_z = h / ::tan(0.5 * Geometry::deg2rad(FovMaxDeg)); + m_distance += (new_near_z - m_frustrum_zs.first); + apply_view_matrix(); + } + else if (fov_deg < FovMinDeg - 0.001) + { + double new_near_z = h / ::tan(0.5 * Geometry::deg2rad(FovMinDeg)); + m_distance += (new_near_z - m_frustrum_zs.first); + apply_view_matrix(); + } + else + break; + } + else + break; } glsafe(::glMatrixMode(GL_PROJECTION)); @@ -231,17 +296,22 @@ void Camera::debug_render() const std::string type = get_type_as_string(); Vec3f position = get_position().cast(); Vec3f target = m_target.cast(); + float distance = (float)get_distance(); Vec3f forward = get_dir_forward().cast(); Vec3f right = get_dir_right().cast(); Vec3f up = get_dir_up().cast(); float nearZ = (float)m_frustrum_zs.first; float farZ = (float)m_frustrum_zs.second; float deltaZ = farZ - nearZ; + float zoom = (float)m_zoom; + float fov = (float)get_fov(); + float gui_scale = (float)get_gui_scale(); ImGui::InputText("Type", const_cast(type.data()), type.length(), ImGuiInputTextFlags_ReadOnly); ImGui::Separator(); ImGui::InputFloat3("Position", position.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Target", target.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat("Distance", &distance, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::Separator(); ImGui::InputFloat3("Forward", forward.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Right", right.data(), "%.6f", ImGuiInputTextFlags_ReadOnly); @@ -250,6 +320,11 @@ void Camera::debug_render() const ImGui::InputFloat("Near Z", &nearZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat("Far Z", &farZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat("Delta Z", &deltaZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); + ImGui::InputFloat("Zoom", &zoom, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::InputFloat("Fov", &fov, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); + ImGui::Separator(); + ImGui::InputFloat("GUI scale", &gui_scale, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly); imgui.end(); } #endif // ENABLE_CAMERA_STATISTICS @@ -273,11 +348,10 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo vertices.push_back(bb_max); vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2)); - // set the Z range in eye coordinates (only negative Zs are in front of the camera) + // set the Z range in eye coordinates (negative Zs are in front of the camera) for (const Vec3d& v : vertices) { - // ensure non-negative values - double z = std::max(-(m_view_matrix * v)(2), 0.0); + double z = -(m_view_matrix * v)(2); ret.first = std::min(ret.first, z); ret.second = std::max(ret.second, z); } @@ -295,8 +369,6 @@ std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBo ret.second = mid_z + half_size; } - assert(ret.first > 0.0); - return ret; } diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 6a2f65a30e..bd2541ce2f 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -12,6 +12,8 @@ struct Camera static const double DefaultDistance; static double FrustrumMinZSize; static double FrustrumZMargin; + static double FovMinDeg; + static double FovMaxDeg; enum EType : unsigned char { @@ -31,7 +33,8 @@ private: float m_theta; double m_zoom; // Distance between camera position and camera target measured along the camera Z axis - double m_distance; + mutable double m_distance; + mutable double m_gui_scale; mutable std::array m_viewport; mutable Transform3d m_view_matrix; @@ -52,13 +55,14 @@ public: const Vec3d& get_target() const { return m_target; } void set_target(const Vec3d& target); + double get_distance() const { return m_distance; } + double get_gui_scale() const { return m_gui_scale; } + float get_theta() const { return m_theta; } void set_theta(float theta, bool apply_limit); double get_zoom() const { return m_zoom; } void set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h); - // this method does not check if the given zoom is valid, use instead the other set_zoom() method - // called only by: void GLCanvas3D::update_ui_from_settings() void set_zoom(double zoom) { m_zoom = zoom; } const BoundingBoxf3& get_scene_box() const { return m_scene_box; } @@ -79,6 +83,8 @@ public: double get_near_z() const { return m_frustrum_zs.first; } double get_far_z() const { return m_frustrum_zs.second; } + double get_fov() const; + void apply_viewport(int x, int y, unsigned int w, unsigned int h) const; void apply_view_matrix() const; void apply_projection(const BoundingBoxf3& box) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d9a8a870ce..2bb284fd4e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3902,8 +3902,11 @@ void GLCanvas3D::_render_overlays() const glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glPushMatrix()); glsafe(::glLoadIdentity()); - // ensure the textures are renderered inside the frustrum + // ensure that the textures are renderered inside the frustrum glsafe(::glTranslated(0.0, 0.0, -(m_camera.get_near_z() + 0.5))); + // ensure that the overlay fits the frustrum near z plane + double gui_scale = m_camera.get_gui_scale(); + glsafe(::glScaled(gui_scale, gui_scale, 1.0)); _render_gizmos_overlay(); _render_warning_texture(); From 00b9a3ad3250c79a58fc7ea3369932967aa80352 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 24 Jun 2019 09:54:58 +0200 Subject: [PATCH 178/627] ENABLE_COMPRESSED_TEXTURES set as default --- src/libslic3r/Technologies.hpp | 3 - src/slic3r/GUI/3DBed.cpp | 76 ---------------------- src/slic3r/GUI/3DBed.hpp | 12 ---- src/slic3r/GUI/GLCanvas3D.cpp | 48 -------------- src/slic3r/GUI/GLCanvas3D.hpp | 12 ---- src/slic3r/GUI/GLCanvas3DManager.cpp | 4 -- src/slic3r/GUI/GLCanvas3DManager.hpp | 4 -- src/slic3r/GUI/GLTexture.cpp | 78 +---------------------- src/slic3r/GUI/GLTexture.hpp | 24 ------- src/slic3r/GUI/GLToolbar.cpp | 8 --- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 8 --- src/slic3r/GUI/ImGuiWrapper.cpp | 12 ---- src/slic3r/GUI/ImGuiWrapper.hpp | 4 -- 13 files changed, 2 insertions(+), 291 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 657dab95f8..bffc45cde7 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -46,7 +46,4 @@ #define ENABLE_SVG_ICONS (1 && ENABLE_1_42_0_ALPHA8 && ENABLE_TEXTURES_FROM_SVG) -// Enable saving textures on GPU in compressed format -#define ENABLE_COMPRESSED_TEXTURES 1 - #endif // _technologies_h_ diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index d63e054c56..6ffcdcbe87 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -9,9 +9,7 @@ #include "GUI_App.hpp" #include "PresetBundle.hpp" #include "Gizmos/GLGizmoBase.hpp" -#if ENABLE_COMPRESSED_TEXTURES #include "GLCanvas3D.hpp" -#endif // ENABLE_COMPRESSED_TEXTURES #include @@ -276,9 +274,7 @@ void Bed3D::Axes::render_axis(double length) const Bed3D::Bed3D() : m_type(Custom) -#if ENABLE_COMPRESSED_TEXTURES , m_requires_canvas_update(false) -#endif // ENABLE_COMPRESSED_TEXTURES #if ENABLE_TEXTURES_FROM_SVG , m_vbo_id(0) #endif // ENABLE_TEXTURES_FROM_SVG @@ -334,18 +330,13 @@ Point Bed3D::point_projection(const Point& point) const } #if ENABLE_TEXTURES_FROM_SVG -#if ENABLE_COMPRESSED_TEXTURES void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const -#else -void Bed3D::render(float theta, bool useVBOs, float scale_factor) const -#endif // ENABLE_COMPRESSED_TEXTURES { m_scale_factor = scale_factor; EType type = useVBOs ? m_type : Custom; switch (type) { -#if ENABLE_COMPRESSED_TEXTURES case MK2: { render_prusa(canvas, "mk2", theta > 90.0f); @@ -361,23 +352,6 @@ void Bed3D::render(float theta, bool useVBOs, float scale_factor) const render_prusa(canvas, "sl1", theta > 90.0f); break; } -#else - case MK2: - { - render_prusa("mk2", theta > 90.0f); - break; - } - case MK3: - { - render_prusa("mk3", theta > 90.0f); - break; - } - case SL1: - { - render_prusa("sl1", theta > 90.0f); - break; - } -#endif // ENABLE_COMPRESSED_TEXTURES default: case Custom: { @@ -387,11 +361,7 @@ void Bed3D::render(float theta, bool useVBOs, float scale_factor) const } } #else -#if ENABLE_COMPRESSED_TEXTURES void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const -#else -void Bed3D::render(float theta, bool useVBOs, float scale_factor) const -#endif // ENABLE_COMPRESSED_TEXTURES { m_scale_factor = scale_factor; @@ -400,7 +370,6 @@ void Bed3D::render(float theta, bool useVBOs, float scale_factor) const switch (m_type) { -#if ENABLE_COMPRESSED_TEXTURES case MK2: { render_prusa(canvas, "mk2", theta, useVBOs); @@ -416,23 +385,6 @@ void Bed3D::render(float theta, bool useVBOs, float scale_factor) const render_prusa(canvas, "sl1", theta, useVBOs); break; } -#else - case MK2: - { - render_prusa("mk2", theta, useVBOs); - break; - } - case MK3: - { - render_prusa("mk3", theta, useVBOs); - break; - } - case SL1: - { - render_prusa("sl1", theta, useVBOs); - break; - } -#endif // ENABLE_COMPRESSED_TEXTURES default: case Custom: { @@ -536,21 +488,12 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const } #if ENABLE_TEXTURES_FROM_SVG -#if ENABLE_COMPRESSED_TEXTURES void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom) const -#else -void Bed3D::render_prusa(const std::string &key, bool bottom) const -#endif // ENABLE_COMPRESSED_TEXTURES { std::string tex_path = resources_dir() + "/icons/bed/" + key; std::string model_path = resources_dir() + "/models/" + key; -#if !ENABLE_COMPRESSED_TEXTURES - // use anisotropic filter if graphic card allows - GLfloat max_anisotropy = GLCanvas3DManager::get_gl_info().get_max_anisotropy(); -#endif // !ENABLE_COMPRESSED_TEXTURES - // use higher resolution images if graphic card and opengl version allow GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); @@ -558,7 +501,6 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) { -#if ENABLE_COMPRESSED_TEXTURES // generate a temporary lower resolution texture to show while no main texture levels have been compressed if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8)) { @@ -568,24 +510,11 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const // starts generating the main texture, compression will run asynchronously if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size)) -#else - if (!m_texture.load_from_svg_file(filename, true, max_tex_size)) -#endif // ENABLE_COMPRESSED_TEXTURES { render_custom(); return; } - -#if !ENABLE_COMPRESSED_TEXTURES - if (max_anisotropy > 0.0f) - { - glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.get_id())); - glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - } -#endif // !ENABLE_COMPRESSED_TEXTURES } -#if ENABLE_COMPRESSED_TEXTURES else if (m_texture.unsent_compressed_data_available()) { // sends to gpu the already available compressed levels of the main texture @@ -604,7 +533,6 @@ void Bed3D::render_prusa(const std::string &key, bool bottom) const m_requires_canvas_update = false; } -#endif // ENABLE_COMPRESSED_TEXTURES if (!bottom) { @@ -677,16 +605,12 @@ void Bed3D::render_prusa_shader(bool transparent) const GLint position_id = m_shader.get_attrib_location("v_position"); GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords"); -#if ENABLE_COMPRESSED_TEXTURES // show the temporary texture while no compressed data is available GLuint tex_id = (GLuint)m_temp_texture.get_id(); if (tex_id == 0) tex_id = (GLuint)m_texture.get_id(); glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); -#else - glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_texture.get_id())); -#endif // ENABLE_COMPRESSED_TEXTURES glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); if (position_id != -1) diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 401f1232f3..a10042cccf 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -13,9 +13,7 @@ typedef class GLUquadric GLUquadricObj; namespace Slic3r { namespace GUI { -#if ENABLE_COMPRESSED_TEXTURES class GLCanvas3D; -#endif // ENABLE_COMPRESSED_TEXTURES class GeometryBuffer { @@ -95,12 +93,10 @@ private: GeometryBuffer m_gridlines; #if ENABLE_TEXTURES_FROM_SVG mutable GLTexture m_texture; -#if ENABLE_COMPRESSED_TEXTURES // temporary texture shown until the main texture has still no levels compressed mutable GLTexture m_temp_texture; // used to trigger 3D scene update once all compressed textures have been sent to GPU mutable bool m_requires_canvas_update; -#endif // ENABLE_COMPRESSED_TEXTURES mutable Shader m_shader; mutable unsigned int m_vbo_id; #else @@ -131,11 +127,7 @@ public: bool contains(const Point& point) const; Point point_projection(const Point& point) const; -#if ENABLE_COMPRESSED_TEXTURES void render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const; -#else - void render(float theta, bool useVBOs, float scale_factor) const; -#endif // ENABLE_COMPRESSED_TEXTURES void render_axes() const; private: @@ -144,11 +136,7 @@ private: void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); EType detect_type(const Pointfs& shape) const; #if ENABLE_TEXTURES_FROM_SVG -#if ENABLE_COMPRESSED_TEXTURES void render_prusa(GLCanvas3D* canvas, const std::string& key, bool bottom) const; -#else - void render_prusa(const std::string& key, bool bottom) const; -#endif // ENABLE_COMPRESSED_TEXTURES void render_prusa_shader(bool transparent) const; #else void render_prusa(const std::string &key, float theta, bool useVBOs) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 973fd1dd94..c8559cb5d4 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -401,11 +401,7 @@ void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas if (m_tooltip_texture.get_id() == 0) { std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png"; -#if ENABLE_COMPRESSED_TEXTURES if (!m_tooltip_texture.load_from_file(filename, false, true)) -#else - if (!m_tooltip_texture.load_from_file(filename, false)) -#endif // ENABLE_COMPRESSED_TEXTURES return; } @@ -437,11 +433,7 @@ void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) co if (m_reset_texture.get_id() == 0) { std::string filename = resources_dir() + "/icons/variable_layer_height_reset.png"; -#if ENABLE_COMPRESSED_TEXTURES if (!m_reset_texture.load_from_file(filename, false, true)) -#else - if (!m_reset_texture.load_from_file(filename, false)) -#endif // ENABLE_COMPRESSED_TEXTURES return; } @@ -736,11 +728,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool } } -#if ENABLE_COMPRESSED_TEXTURES generate(text, canvas, true, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...) -#else - _generate(text, canvas, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...) -#endif // ENABLE_COMPRESSED_TEXTURES // save information for rescaling m_msg_text = text; @@ -801,11 +789,7 @@ static void msw_disable_cleartype(wxFont &font) } #endif /* __WXMSW__ */ -#if ENABLE_COMPRESSED_TEXTURES bool GLCanvas3D::WarningTexture::generate(const std::string& msg_utf8, const GLCanvas3D& canvas, bool compress, bool red_colored/* = false*/) -#else -bool GLCanvas3D::WarningTexture::_generate(const std::string& msg_utf8, const GLCanvas3D& canvas, const bool red_colored/* = false*/) -#endif // ENABLE_COMPRESSED_TEXTURES { reset(); @@ -879,14 +863,10 @@ bool GLCanvas3D::WarningTexture::_generate(const std::string& msg_utf8, const GL glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glGenTextures(1, &m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id)); -#if ENABLE_COMPRESSED_TEXTURES if (compress && GLEW_EXT_texture_compression_s3tc) glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); -#else - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); -#endif // ENABLE_COMPRESSED_TEXTURES glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); @@ -937,11 +917,7 @@ void GLCanvas3D::WarningTexture::msw_rescale(const GLCanvas3D& canvas) if (m_msg_text.empty()) return; -#if ENABLE_COMPRESSED_TEXTURES generate(m_msg_text, canvas, true, m_is_colored_red); -#else - _generate(m_msg_text, canvas, m_is_colored_red); -#endif // ENABLE_COMPRESSED_TEXTURES } const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 }; @@ -984,11 +960,7 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePrevie } } -#if ENABLE_COMPRESSED_TEXTURES bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas, bool compress) -#else -bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas) -#endif // ENABLE_COMPRESSED_TEXTURES { reset(); @@ -1177,14 +1149,10 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glGenTextures(1, &m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id)); -#if ENABLE_COMPRESSED_TEXTURES if (compress && GLEW_EXT_texture_compression_s3tc) glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); -#else - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); -#endif // ENABLE_COMPRESSED_TEXTURES glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); @@ -1267,9 +1235,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar #endif // ENABLE_SVG_ICONS , m_use_clipping_planes(false) , m_sidebar_field("") -#if ENABLE_COMPRESSED_TEXTURES , m_keep_dirty(false) -#endif // ENABLE_COMPRESSED_TEXTURES , m_config(nullptr) , m_process(nullptr) , m_model(nullptr) @@ -1487,10 +1453,8 @@ void GLCanvas3D::bed_shape_changed() m_camera.set_scene_box(scene_bounding_box()); m_camera.requires_zoom_to_bed = true; m_dirty = true; -#if ENABLE_COMPRESSED_TEXTURES if (m_bed.is_prusa()) start_keeping_dirty(); -#endif // ENABLE_COMPRESSED_TEXTURES } void GLCanvas3D::set_color_by(const std::string& value) @@ -1750,12 +1714,10 @@ void GLCanvas3D::render() imgui.text(std::to_string(m_render_stats.last_frame)); ImGui::SameLine(); imgui.text(" ms"); -#if ENABLE_COMPRESSED_TEXTURES ImGui::Separator(); imgui.text("Compressed textures: "); ImGui::SameLine(); imgui.text(GLCanvas3DManager::are_compressed_textures_supported() ? "supported" : "not supported"); -#endif // ENABLE_COMPRESSED_TEXTURES imgui.text("Max texture size: "); ImGui::SameLine(); imgui.text(std::to_string(GLCanvas3DManager::get_gl_info().get_max_tex_size())); @@ -2366,10 +2328,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) _refresh_if_shown_on_screen(); -#if ENABLE_COMPRESSED_TEXTURES if (m_keep_dirty) m_dirty = true; -#endif // ENABLE_COMPRESSED_TEXTURES } void GLCanvas3D::on_char(wxKeyEvent& evt) @@ -4002,11 +3962,7 @@ void GLCanvas3D::_render_bed(float theta) const #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); #endif // ENABLE_RETINA_GL -#if ENABLE_COMPRESSED_TEXTURES m_bed.render(const_cast(this), theta, m_use_VBOs, scale_factor); -#else - m_bed.render(theta, m_use_VBOs, scale_factor); -#endif // ENABLE_COMPRESSED_TEXTURES } void GLCanvas3D::_render_axes() const @@ -5762,11 +5718,7 @@ std::vector GLCanvas3D::_parse_colors(const std::vector& col void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data, const std::vector& tool_colors) { -#if ENABLE_COMPRESSED_TEXTURES m_legend_texture.generate(preview_data, tool_colors, *this, true); -#else - m_legend_texture.generate(preview_data, tool_colors, *this); -#endif // ENABLE_COMPRESSED_TEXTURES } void GLCanvas3D::_set_warning_texture(WarningTexture::Warning warning, bool state) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 4b97124afb..9520fae0e0 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -376,11 +376,7 @@ class GLCanvas3D std::vector m_warnings; // Generates the texture with given text. -#if ENABLE_COMPRESSED_TEXTURES bool generate(const std::string& msg, const GLCanvas3D& canvas, bool compress, bool red_colored = false); -#else - bool _generate(const std::string& msg, const GLCanvas3D& canvas, const bool red_colored = false); -#endif // ENABLE_COMPRESSED_TEXTURES }; class LegendTexture : public GUI::GLTexture @@ -403,11 +399,7 @@ class GLCanvas3D void fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas, std::vector>& cp_legend_values); -#if ENABLE_COMPRESSED_TEXTURES bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas, bool compress); -#else - bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas); -#endif // ENABLE_COMPRESSED_TEXTURES void render(const GLCanvas3D& canvas) const; }; @@ -451,9 +443,7 @@ private: bool m_use_clipping_planes; mutable SlaCap m_sla_caps[2]; std::string m_sidebar_field; -#if ENABLE_COMPRESSED_TEXTURES bool m_keep_dirty; -#endif // ENABLE_COMPRESSED_TEXTURES mutable GLVolumeCollection m_volumes; Selection m_selection; @@ -640,10 +630,8 @@ public: void set_cursor(ECursorType type); void msw_rescale(); -#if ENABLE_COMPRESSED_TEXTURES void start_keeping_dirty() { m_keep_dirty = true; } void stop_keeping_dirty() { m_keep_dirty = false; } -#endif // ENABLE_COMPRESSED_TEXTURES private: bool _is_shown_on_screen() const; diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index a123fcfa7c..4f64b4e877 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -186,9 +186,7 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten } GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown; -#if ENABLE_COMPRESSED_TEXTURES bool GLCanvas3DManager::s_compressed_textures_supported = false; -#endif // ENABLE_COMPRESSED_TEXTURES GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info; GLCanvas3DManager::GLCanvas3DManager() @@ -273,12 +271,10 @@ void GLCanvas3DManager::init_gl() m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1"); m_use_VBOs = !m_use_legacy_opengl && s_gl_info.is_version_greater_or_equal_to(2, 0); m_gl_initialized = true; -#if ENABLE_COMPRESSED_TEXTURES if (GLEW_EXT_texture_compression_s3tc) s_compressed_textures_supported = true; else s_compressed_textures_supported = false; -#endif // ENABLE_COMPRESSED_TEXTURES } } diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index e6e12ca5d6..26c2558d07 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -78,9 +78,7 @@ private: bool m_use_legacy_opengl; bool m_use_VBOs; static EMultisampleState s_multisample; -#if ENABLE_COMPRESSED_TEXTURES static bool s_compressed_textures_supported; -#endif // ENABLE_COMPRESSED_TEXTURES public: GLCanvas3DManager(); @@ -99,9 +97,7 @@ public: GLCanvas3D* get_canvas(wxGLCanvas* canvas); static bool can_multisample() { return s_multisample == MS_Enabled; } -#if ENABLE_COMPRESSED_TEXTURES static bool are_compressed_textures_supported() { return s_compressed_textures_supported; } -#endif // ENABLE_COMPRESSED_TEXTURES static wxGLCanvas* create_wxglcanvas(wxWindow *parent); diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 9b373440a1..171a5c8851 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -12,12 +12,10 @@ #include #include -#if ENABLE_COMPRESSED_TEXTURES #include #define STB_DXT_IMPLEMENTATION #include "stb_dxt/stb_dxt.h" -#endif // ENABLE_COMPRESSED_TEXTURES #include "nanosvg/nanosvg.h" #include "nanosvg/nanosvgrast.h" @@ -27,7 +25,6 @@ namespace Slic3r { namespace GUI { -#if ENABLE_COMPRESSED_TEXTURES void GLTexture::Compressor::reset() { // force compression completion, if any @@ -120,7 +117,6 @@ void GLTexture::Compressor::compress() m_is_compressing = false; m_abort_compressing = false; } -#endif // ENABLE_COMPRESSED_TEXTURES GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } }; @@ -129,9 +125,7 @@ GLTexture::GLTexture() , m_width(0) , m_height(0) , m_source("") -#if ENABLE_COMPRESSED_TEXTURES , m_compressor(*this) -#endif // ENABLE_COMPRESSED_TEXTURES { } @@ -140,11 +134,7 @@ GLTexture::~GLTexture() reset(); } -#if ENABLE_COMPRESSED_TEXTURES bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, bool compress) -#else -bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps) -#endif // ENABLE_COMPRESSED_TEXTURES { reset(); @@ -152,20 +142,12 @@ bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps) return false; if (boost::algorithm::iends_with(filename, ".png")) -#if ENABLE_COMPRESSED_TEXTURES return load_from_png(filename, use_mipmaps, compress); -#else - return load_from_png(filename, use_mipmaps); -#endif // ENABLE_COMPRESSED_TEXTURES else return false; } -#if ENABLE_COMPRESSED_TEXTURES bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) -#else -bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px) -#endif // ENABLE_COMPRESSED_TEXTURES { reset(); @@ -173,20 +155,12 @@ bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps return false; if (boost::algorithm::iends_with(filename, ".svg")) -#if ENABLE_COMPRESSED_TEXTURES return load_from_svg(filename, use_mipmaps, compress, apply_anisotropy, max_size_px); -#else - return load_from_svg(filename, use_mipmaps, max_size_px); -#endif // ENABLE_COMPRESSED_TEXTURES else return false; } -#if ENABLE_COMPRESSED_TEXTURES bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px, bool compress) -#else -bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px) -#endif // ENABLE_COMPRESSED_TEXTURES { reset(); @@ -302,14 +276,10 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vectorwidth); m_height = (int)(scale * image->height); -#if ENABLE_COMPRESSED_TEXTURES if (compression_enabled) { // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 @@ -553,7 +494,6 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns if (height_rem != 0) m_height += (4 - height_rem); } -#endif // ENABLE_COMPRESSED_TEXTURES int n_pixels = m_width * m_height; @@ -581,7 +521,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glGenTextures(1, &m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_id)); -#if ENABLE_COMPRESSED_TEXTURES + if (apply_anisotropy) { GLfloat max_anisotropy = GLCanvas3DManager::get_gl_info().get_max_anisotropy(); @@ -598,9 +538,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns } else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); -#else - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); -#endif // ENABLE_COMPRESSED_TEXTURES + if (use_mipmaps) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards @@ -616,12 +554,9 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns lod_h = std::max(lod_h / 2, 1); scale /= 2.0f; -#if ENABLE_COMPRESSED_TEXTURES data.resize(lod_w * lod_h * 4); -#endif // ENABLE_COMPRESSED_TEXTURES nsvgRasterize(rast, image, 0, 0, scale, data.data(), lod_w, lod_h, lod_w * 4); -#if ENABLE_COMPRESSED_TEXTURES if (compression_enabled) { // initializes the texture on GPU @@ -631,20 +566,13 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns } else glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); -#else - glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); -#endif // ENABLE_COMPRESSED_TEXTURES } -#if ENABLE_COMPRESSED_TEXTURES if (!compression_enabled) { -#endif // ENABLE_COMPRESSED_TEXTURES glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); -#if ENABLE_COMPRESSED_TEXTURES } -#endif // ENABLE_COMPRESSED_TEXTURES } else { @@ -657,11 +585,9 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, uns m_source = filename; -#if ENABLE_COMPRESSED_TEXTURES if (compression_enabled) // start asynchronous compression m_compressor.start_compressing(); -#endif // ENABLE_COMPRESSED_TEXTURES nsvgDeleteRasterizer(rast); nsvgDelete(image); diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index 5df6189b6f..f4dc05a168 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -11,7 +11,6 @@ namespace GUI { class GLTexture { -#if ENABLE_COMPRESSED_TEXTURES class Compressor { struct Level @@ -47,7 +46,6 @@ namespace GUI { private: void compress(); }; -#endif // ENABLE_COMPRESSED_TEXTURES public: struct UV @@ -71,21 +69,14 @@ namespace GUI { int m_width; int m_height; std::string m_source; -#if ENABLE_COMPRESSED_TEXTURES Compressor m_compressor; -#endif // ENABLE_COMPRESSED_TEXTURES public: GLTexture(); virtual ~GLTexture(); -#if ENABLE_COMPRESSED_TEXTURES bool load_from_file(const std::string& filename, bool use_mipmaps, bool compress); bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); -#else - bool load_from_file(const std::string& filename, bool use_mipmaps); - bool load_from_svg_file(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); -#endif // ENABLE_COMPRESSED_TEXTURES // meanings of states: (std::pair) // first field (int): // 0 -> no changes @@ -94,11 +85,7 @@ namespace GUI { // second field (bool): // false -> no changes // true -> add background color -#if ENABLE_COMPRESSED_TEXTURES bool load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px, bool compress); -#else - bool load_from_svg_files_as_sprites_array(const std::vector& filenames, const std::vector>& states, unsigned int sprite_size_px); -#endif // ENABLE_COMPRESSED_TEXTURES void reset(); unsigned int get_id() const { return m_id; } @@ -107,32 +94,21 @@ namespace GUI { const std::string& get_source() const { return m_source; } -#if ENABLE_COMPRESSED_TEXTURES bool unsent_compressed_data_available() const { return m_compressor.unsent_compressed_data_available(); } void send_compressed_data_to_gpu() { m_compressor.send_compressed_data_to_gpu(); } bool all_compressed_data_sent_to_gpu() const { return m_compressor.all_compressed_data_sent_to_gpu(); } -#endif // ENABLE_COMPRESSED_TEXTURES static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs); protected: -#if ENABLE_COMPRESSED_TEXTURES unsigned int generate_mipmaps(wxImage& image, bool compress); -#else - unsigned int generate_mipmaps(wxImage& image); -#endif // ENABLE_COMPRESSED_TEXTURES private: -#if ENABLE_COMPRESSED_TEXTURES bool load_from_png(const std::string& filename, bool use_mipmaps, bool compress); bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); friend class Compressor; -#else - bool load_from_png(const std::string& filename, bool use_mipmaps); - bool load_from_svg(const std::string& filename, bool use_mipmaps, unsigned int max_size_px); -#endif // ENABLE_COMPRESSED_TEXTURES }; } // namespace GUI diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 813a319b80..5a5596ab4b 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -194,11 +194,7 @@ bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const Bac #endif // ENABLE_SVG_ICONS if (!background_texture.filename.empty()) -#if ENABLE_COMPRESSED_TEXTURES res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, true); -#else - res = m_background_texture.texture.load_from_file(path + background_texture.filename, false); -#endif // ENABLE_COMPRESSED_TEXTURES if (res) m_background_texture.metadata = background_texture; @@ -1342,11 +1338,7 @@ bool GLToolbar::generate_icons_texture() const states.push_back(std::make_pair(1, true)); } -#if ENABLE_COMPRESSED_TEXTURES bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_layout.icons_size * m_layout.scale), true); -#else - bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_layout.icons_size * m_layout.scale)); -#endif // ENABLE_COMPRESSED_TEXTURES if (res) m_icons_texture_dirty = false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index c254f5796d..203a928dd6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -65,11 +65,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) if (!m_background_texture.metadata.filename.empty()) { -#if ENABLE_COMPRESSED_TEXTURES if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false, true)) -#else - if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false)) -#endif // ENABLE_COMPRESSED_TEXTURES { reset(); return false; @@ -1164,11 +1160,7 @@ bool GLGizmosManager::generate_icons_texture() const states.push_back(std::make_pair(0, false)); states.push_back(std::make_pair(0, true)); -#if ENABLE_COMPRESSED_TEXTURES bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_overlay_icons_size * m_overlay_scale), true); -#else - bool res = m_icons_texture.load_from_svg_files_as_sprites_array(filenames, states, (unsigned int)(m_overlay_icons_size * m_overlay_scale)); -#endif // ENABLE_COMPRESSED_TEXTURES if (res) m_icons_texture_dirty = false; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 7267da2956..ca1538bf72 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -206,11 +206,7 @@ void ImGuiWrapper::new_frame() } if (m_font_texture == 0) { -#if ENABLE_COMPRESSED_TEXTURES init_font(true); -#else - init_font(); -#endif // ENABLE_COMPRESSED_TEXTURES } ImGui::NewFrame(); @@ -387,11 +383,7 @@ bool ImGuiWrapper::want_any_input() const return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; } -#if ENABLE_COMPRESSED_TEXTURES void ImGuiWrapper::init_font(bool compress) -#else -void ImGuiWrapper::init_font() -#endif // ENABLE_COMPRESSED_TEXTURES { destroy_font(); @@ -420,14 +412,10 @@ void ImGuiWrapper::init_font() glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); -#if ENABLE_COMPRESSED_TEXTURES if (compress && GLEW_EXT_texture_compression_s3tc) glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); -#else - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); -#endif // ENABLE_COMPRESSED_TEXTURES // Store our identifier io.Fonts->TexID = (ImTextureID)(intptr_t)m_font_texture; diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index a1e4048ac6..0479e47434 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -77,11 +77,7 @@ public: bool want_any_input() const; private: -#if ENABLE_COMPRESSED_TEXTURES void init_font(bool compress); -#else - void init_font(); -#endif // ENABLE_COMPRESSED_TEXTURES void init_input(); void init_style(); void render_draw_data(ImDrawData *draw_data); From 38d5817bc9bcc29594587f866cf7cfacbf374940 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 24 Jun 2019 11:23:25 +0200 Subject: [PATCH 179/627] Disabled ENABLE_CAMERA_STATISTICS --- src/libslic3r/Technologies.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index fe4d28a7c3..a5f93b24f2 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -14,7 +14,7 @@ // Shows an imgui dialog with render related data #define ENABLE_RENDER_STATISTICS 0 // Shows an imgui dialog with camera related data -#define ENABLE_CAMERA_STATISTICS 1 +#define ENABLE_CAMERA_STATISTICS 0 //==================== From 6b0d75127b5c8d122052fa06be990ad8cfeadeb6 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 24 Jun 2019 12:26:11 +0200 Subject: [PATCH 180/627] #2428 1) Reworked logic for pasting volumes 2) Fixed paste of volumes into different objects 3) Do not apply offset when pasting into the copied object 4) Keep source transformation matrix and relative positions when copy/pasting volumes into another object --- src/libslic3r/Geometry.cpp | 57 ++++++++++++++++++++++++++++ src/libslic3r/Geometry.hpp | 5 +++ src/slic3r/GUI/GUI_ObjectList.cpp | 62 +------------------------------ src/slic3r/GUI/Selection.cpp | 46 ++++++++++++++++++++--- 4 files changed, 103 insertions(+), 67 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index ccc629723e..accb4e2863 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -1409,6 +1409,63 @@ Transformation Transformation::operator * (const Transformation& other) const return Transformation(get_matrix() * other.get_matrix()); } +Transformation Transformation::volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox) +{ + Transformation out; + + if (instance_transformation.is_scaling_uniform()) { + // No need to run the non-linear least squares fitting for uniform scaling. + // Just set the inverse. + out.set_from_transform(instance_transformation.get_matrix(true).inverse()); + } + else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) + { + // Anisotropic scaling, rotation by multiples of ninety degrees. + Eigen::Matrix3d instance_rotation_trafo = + (Eigen::AngleAxisd(instance_transformation.get_rotation().z(), Vec3d::UnitZ()) * + Eigen::AngleAxisd(instance_transformation.get_rotation().y(), Vec3d::UnitY()) * + Eigen::AngleAxisd(instance_transformation.get_rotation().x(), Vec3d::UnitX())).toRotationMatrix(); + Eigen::Matrix3d volume_rotation_trafo = + (Eigen::AngleAxisd(-instance_transformation.get_rotation().x(), Vec3d::UnitX()) * + Eigen::AngleAxisd(-instance_transformation.get_rotation().y(), Vec3d::UnitY()) * + Eigen::AngleAxisd(-instance_transformation.get_rotation().z(), Vec3d::UnitZ())).toRotationMatrix(); + + // 8 corners of the bounding box. + auto pts = Eigen::MatrixXd(8, 3); + pts(0, 0) = bbox.min.x(); pts(0, 1) = bbox.min.y(); pts(0, 2) = bbox.min.z(); + pts(1, 0) = bbox.min.x(); pts(1, 1) = bbox.min.y(); pts(1, 2) = bbox.max.z(); + pts(2, 0) = bbox.min.x(); pts(2, 1) = bbox.max.y(); pts(2, 2) = bbox.min.z(); + pts(3, 0) = bbox.min.x(); pts(3, 1) = bbox.max.y(); pts(3, 2) = bbox.max.z(); + pts(4, 0) = bbox.max.x(); pts(4, 1) = bbox.min.y(); pts(4, 2) = bbox.min.z(); + pts(5, 0) = bbox.max.x(); pts(5, 1) = bbox.min.y(); pts(5, 2) = bbox.max.z(); + pts(6, 0) = bbox.max.x(); pts(6, 1) = bbox.max.y(); pts(6, 2) = bbox.min.z(); + pts(7, 0) = bbox.max.x(); pts(7, 1) = bbox.max.y(); pts(7, 2) = bbox.max.z(); + + // Corners of the bounding box transformed into the modifier mesh coordinate space, with inverse rotation applied to the modifier. + auto qs = pts * + (instance_rotation_trafo * + Eigen::Scaling(instance_transformation.get_scaling_factor().cwiseProduct(instance_transformation.get_mirror())) * + volume_rotation_trafo).inverse().transpose(); + // Fill in scaling based on least squares fitting of the bounding box corners. + Vec3d scale; + for (int i = 0; i < 3; ++i) + scale(i) = pts.col(i).dot(qs.col(i)) / pts.col(i).dot(pts.col(i)); + + out.set_rotation(Geometry::extract_euler_angles(volume_rotation_trafo)); + out.set_scaling_factor(Vec3d(std::abs(scale(0)), std::abs(scale(1)), std::abs(scale(2)))); + out.set_mirror(Vec3d(scale(0) > 0 ? 1. : -1, scale(1) > 0 ? 1. : -1, scale(2) > 0 ? 1. : -1)); + } + else + { + // General anisotropic scaling, general rotation. + // Keep the modifier mesh in the instance coordinate system, so the modifier mesh will not be aligned with the world. + // Scale it to get the required size. + out.set_scaling_factor(instance_transformation.get_scaling_factor().cwiseInverse()); + } + + return out; +} + Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) { return diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 033c24a846..bcbe80a0d8 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -258,6 +258,11 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; Transformation operator * (const Transformation& other) const; + + // Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity + // as possible in least squares norm in regard to the 8 corners of bbox. + // Bounding box is expected to be centered around zero in all axes. + static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox); }; // Rotation when going from the first coordinate system with rotation rot_xyz_from applied diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a95b71bcb3..d7d1a1af7b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1485,66 +1485,6 @@ void ObjectList::load_part( ModelObject* model_object, } -// Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity -// as possible in least squares norm in regard to the 8 corners of bbox. -// Bounding box is expected to be centered around zero in all axes. -Geometry::Transformation volume_to_bed_transformation(const Geometry::Transformation &instance_transformation, const BoundingBoxf3 &bbox) -{ - Geometry::Transformation out; - - if (instance_transformation.is_scaling_uniform()) { - // No need to run the non-linear least squares fitting for uniform scaling. - // Just set the inverse. - out.set_from_transform(instance_transformation.get_matrix(true).inverse()); - } - else if (Geometry::is_rotation_ninety_degrees(instance_transformation.get_rotation())) - { - // Anisotropic scaling, rotation by multiples of ninety degrees. - Eigen::Matrix3d instance_rotation_trafo = - (Eigen::AngleAxisd(instance_transformation.get_rotation().z(), Vec3d::UnitZ()) * - Eigen::AngleAxisd(instance_transformation.get_rotation().y(), Vec3d::UnitY()) * - Eigen::AngleAxisd(instance_transformation.get_rotation().x(), Vec3d::UnitX())).toRotationMatrix(); - Eigen::Matrix3d volume_rotation_trafo = - (Eigen::AngleAxisd(-instance_transformation.get_rotation().x(), Vec3d::UnitX()) * - Eigen::AngleAxisd(-instance_transformation.get_rotation().y(), Vec3d::UnitY()) * - Eigen::AngleAxisd(-instance_transformation.get_rotation().z(), Vec3d::UnitZ())).toRotationMatrix(); - - // 8 corners of the bounding box. - auto pts = Eigen::MatrixXd(8, 3); - pts(0, 0) = bbox.min.x(); pts(0, 1) = bbox.min.y(); pts(0, 2) = bbox.min.z(); - pts(1, 0) = bbox.min.x(); pts(1, 1) = bbox.min.y(); pts(1, 2) = bbox.max.z(); - pts(2, 0) = bbox.min.x(); pts(2, 1) = bbox.max.y(); pts(2, 2) = bbox.min.z(); - pts(3, 0) = bbox.min.x(); pts(3, 1) = bbox.max.y(); pts(3, 2) = bbox.max.z(); - pts(4, 0) = bbox.max.x(); pts(4, 1) = bbox.min.y(); pts(4, 2) = bbox.min.z(); - pts(5, 0) = bbox.max.x(); pts(5, 1) = bbox.min.y(); pts(5, 2) = bbox.max.z(); - pts(6, 0) = bbox.max.x(); pts(6, 1) = bbox.max.y(); pts(6, 2) = bbox.min.z(); - pts(7, 0) = bbox.max.x(); pts(7, 1) = bbox.max.y(); pts(7, 2) = bbox.max.z(); - - // Corners of the bounding box transformed into the modifier mesh coordinate space, with inverse rotation applied to the modifier. - auto qs = pts * - (instance_rotation_trafo * - Eigen::Scaling(instance_transformation.get_scaling_factor().cwiseProduct(instance_transformation.get_mirror())) * - volume_rotation_trafo).inverse().transpose(); - // Fill in scaling based on least squares fitting of the bounding box corners. - Vec3d scale; - for (int i = 0; i < 3; ++ i) - scale(i) = pts.col(i).dot(qs.col(i)) / pts.col(i).dot(pts.col(i)); - - out.set_rotation(Geometry::extract_euler_angles(volume_rotation_trafo)); - out.set_scaling_factor(Vec3d(std::abs(scale(0)), std::abs(scale(1)), std::abs(scale(2)))); - out.set_mirror(Vec3d(scale(0) > 0 ? 1. : -1, scale(1) > 0 ? 1. : -1, scale(2) > 0 ? 1. : -1)); - } - else - { - // General anisotropic scaling, general rotation. - // Keep the modifier mesh in the instance coordinate system, so the modifier mesh will not be aligned with the world. - // Scale it to get the required size. - out.set_scaling_factor(instance_transformation.get_scaling_factor().cwiseInverse()); - } - - return out; -} - void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type) { const auto obj_idx = get_selected_obj_idx(); @@ -1598,7 +1538,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); // Transform the new modifier to be aligned with the print bed. const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); - new_volume->set_transformation(volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); + new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); // Set the modifier position. auto offset = (type_name == "Slab") ? // Slab: Lift to print bed diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 480b59ba03..6e80783ccd 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1893,25 +1893,59 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const void Selection::paste_volumes_from_clipboard() { - int obj_idx = get_object_idx(); - if ((obj_idx < 0) || ((int)m_model->objects.size() <= obj_idx)) + int dst_obj_idx = get_object_idx(); + if ((dst_obj_idx < 0) || ((int)m_model->objects.size() <= dst_obj_idx)) + return; + + ModelObject* dst_object = m_model->objects[dst_obj_idx]; + + int dst_inst_idx = get_instance_idx(); + if ((dst_inst_idx < 0) || ((int)dst_object->instances.size() <= dst_inst_idx)) return; ModelObject* src_object = m_clipboard.get_object(0); if (src_object != nullptr) { - ModelObject* dst_object = m_model->objects[obj_idx]; + ModelInstance* dst_instance = dst_object->instances[dst_inst_idx]; + BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx); + Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true); + Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true); + bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix); + + // used to keep relative position of multivolume selections when pasting from another object + BoundingBoxf3 total_bb; ModelVolumePtrs volumes; for (ModelVolume* src_volume : src_object->volumes) { ModelVolume* dst_volume = dst_object->add_volume(*src_volume); dst_volume->set_new_unique_id(); - double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); - dst_volume->translate(offset, offset, 0.0); + if (from_same_object) + { +// // if the volume comes from the same object, apply the offset in world system +// double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); +// dst_volume->translate(dst_matrix.inverse() * Vec3d(offset, offset, 0.0)); + } + else + { + // if the volume comes from another object, apply the offset as done when adding modifiers + // see ObjectList::load_generic_subobject() + total_bb.merge(dst_volume->mesh().bounding_box().transformed(src_volume->get_matrix())); + } + volumes.push_back(dst_volume); } - wxGetApp().obj_list()->paste_volumes_into_list(obj_idx, volumes); + + // keeps relative position of multivolume selections + if (!from_same_object) + { + for (ModelVolume* v : volumes) + { + v->set_offset((v->get_offset() - total_bb.center()) + dst_matrix.inverse() * (Vec3d(dst_instance_bb.max(0), dst_instance_bb.min(1), dst_instance_bb.min(2)) + 0.5 * total_bb.size() - dst_instance->get_transformation().get_offset())); + } + } + + wxGetApp().obj_list()->paste_volumes_into_list(dst_obj_idx, volumes); } } From 1459ad65c66e85770bddf07db462003df32899b0 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 24 Jun 2019 12:35:20 +0200 Subject: [PATCH 181/627] #2433 - Time Estimator: clamp accelerate/decelerate distances to avoid them to become negative --- src/libslic3r/GCodeTimeEstimator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index 60d7a4cdf4..b87305da87 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -125,8 +125,8 @@ namespace Slic3r { trapezoid.distance = distance; trapezoid.feedrate = feedrate; - float accelerate_distance = estimate_acceleration_distance(feedrate.entry, feedrate.cruise, acceleration); - float decelerate_distance = estimate_acceleration_distance(feedrate.cruise, feedrate.exit, -acceleration); + float accelerate_distance = std::max(0.0f, estimate_acceleration_distance(feedrate.entry, feedrate.cruise, acceleration)); + float decelerate_distance = std::max(0.0f, estimate_acceleration_distance(feedrate.cruise, feedrate.exit, -acceleration)); float cruise_distance = distance - accelerate_distance - decelerate_distance; // Not enough space to reach the nominal feedrate. From fab363493140a93c1786c7898424a73dd1ed2a2e Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 24 Jun 2019 12:43:18 +0200 Subject: [PATCH 182/627] #2395 - Reworked logic of method Model::convert_multipart_object() --- src/libslic3r/Model.cpp | 52 ++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 8e879a3e68..596f806710 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -489,21 +489,57 @@ void Model::convert_multipart_object(unsigned int max_extruders) reset_auto_extruder_id(); + bool is_single_object = (this->objects.size() == 1); + for (const ModelObject* o : this->objects) + { for (const ModelVolume* v : o->volumes) { - ModelVolume* new_v = object->add_volume(*v); - if (new_v != nullptr) + if (is_single_object) { - new_v->name = o->name; - new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string(max_extruders)); - new_v->translate(-o->origin_translation); + // If there is only one object, just copy the volumes + ModelVolume* new_v = object->add_volume(*v); + if (new_v != nullptr) + { + new_v->name = o->name; + new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string(max_extruders)); + new_v->translate(-o->origin_translation); + } + } + else + { + // If there are more than one object, put all volumes together + // Each object may contain any number of volumes and instances + // The volumes transformations are relative to the object containing them... + int counter = 1; + for (const ModelInstance* i : o->instances) + { + ModelVolume* new_v = object->add_volume(*v); + if (new_v != nullptr) + { + new_v->name = o->name + "_" + std::to_string(counter++); + new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string(max_extruders)); + new_v->translate(-o->origin_translation); + // ...so, transform everything to a common reference system (world) + new_v->set_transformation(i->get_transformation() * v->get_transformation()); + } + } } } + } + + if (is_single_object) + { + // If there is only one object, keep its instances + for (const ModelInstance* i : this->objects.front()->instances) + { + object->add_instance(*i); + } + } + else + // If there are more than one object, create a single instance + object->add_instance(); - for (const ModelInstance* i : this->objects.front()->instances) - object->add_instance(*i); - this->clear_objects(); this->objects.push_back(object); } From 121b6c078b4e5217ca5818b23102a7725e7cf20f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Jun 2019 13:01:52 +0200 Subject: [PATCH 183/627] Print bed not considered as object in arrange anymore. --- src/libslic3r/MTUtils.hpp | 9 +++++++++ src/libslic3r/ModelArrange.cpp | 31 ++++++++++++++----------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 0afe3563f9..f6bff2b1f8 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -191,6 +191,15 @@ template bool all_of(const C &container) { }); } +template inline X ceil_i(X x, Y y) +{ + static_assert(std::is_integral::value && + std::is_integral::value && sizeof(X) >= sizeof(Y), + ""); + + return (x % y) ? x / y + 1 : x / y; +} + } #endif // MTUTILS_HPP diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index a440c3999d..36f7e39710 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -2,6 +2,7 @@ #include "Model.hpp" #include "Geometry.hpp" #include "SVG.hpp" +#include "MTUtils.hpp" #include @@ -820,15 +821,13 @@ bool arrange(Model &model, // The model with the geometries BoundingBox bbb(bed); auto& cfn = stopcondition; + + coord_t md = ceil_i(min_obj_distance, 2) - SCALED_EPSILON; - auto binbb = Box({ - static_cast(bbb.min(0)), - static_cast(bbb.min(1)) - }, - { - static_cast(bbb.max(0)), - static_cast(bbb.max(1)) - }); + auto binbb = Box({libnest2d::Coord{bbb.min(0)} - md, + libnest2d::Coord{bbb.min(1)} - md}, + {libnest2d::Coord{bbb.max(0)} + md, + libnest2d::Coord{bbb.max(1)} + md}); switch(bedhint.type) { case BedShapeType::BOX: { @@ -916,15 +915,13 @@ void find_new_position(const Model &model, BedShapeHint bedhint = bedShape(bed); BoundingBox bbb(bed); - - auto binbb = Box({ - static_cast(bbb.min(0)), - static_cast(bbb.min(1)) - }, - { - static_cast(bbb.max(0)), - static_cast(bbb.max(1)) - }); + + coord_t md = ceil_i(min_obj_distance, 2) - SCALED_EPSILON; + + auto binbb = Box({libnest2d::Coord{bbb.min(0)} - md, + libnest2d::Coord{bbb.min(1)} - md}, + {libnest2d::Coord{bbb.max(0)} + md, + libnest2d::Coord{bbb.max(1)} + md}); for(auto it = shapemap.begin(); it != shapemap.end(); ++it) { if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end()) { From 75ed542686c66b21f5b2812ec48061055f2ca738 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 24 Jun 2019 13:03:46 +0200 Subject: [PATCH 184/627] Reformatted MTUtils with some refined directives. Only whitespace changes in MTUtils.hpp ! --- .clang-format | 8 +- src/libslic3r/MTUtils.hpp | 200 +++++++++++++++++++++++--------------- 2 files changed, 128 insertions(+), 80 deletions(-) diff --git a/.clang-format b/.clang-format index d5740f6894..9cbcf26f26 100644 --- a/.clang-format +++ b/.clang-format @@ -18,7 +18,7 @@ AllowShortLoopsOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: Yes +AlwaysBreakTemplateDeclarations: false BinPackArguments: false BinPackParameters: false BraceWrapping: @@ -37,18 +37,18 @@ BraceWrapping: SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false -BreakBeforeBinaryOperators: All +BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon -BreakBeforeTernaryOperators: true +BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 75 CommentPragmas: '^ IWYU pragma:' -CompactNamespaces: false +CompactNamespaces: true ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index f6bff2b1f8..b261a79be8 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -1,172 +1,218 @@ #ifndef MTUTILS_HPP #define MTUTILS_HPP -#include // for std::atomic_flag and memory orders -#include // for std::lock_guard -#include // for std::function -#include // for std::forward +#include // for std::atomic_flag and memory orders +#include // for std::lock_guard +#include // for std::function +#include // for std::forward #include namespace Slic3r { /// Handy little spin mutex for the cached meshes. /// Implements the "Lockable" concept -class SpinMutex { - std::atomic_flag m_flg; +class SpinMutex +{ + std::atomic_flag m_flg; static const /*constexpr*/ auto MO_ACQ = std::memory_order_acquire; static const /*constexpr*/ auto MO_REL = std::memory_order_release; + public: inline SpinMutex() { m_flg.clear(MO_REL); } - inline void lock() { while(m_flg.test_and_set(MO_ACQ)); } + inline void lock() { while (m_flg.test_and_set(MO_ACQ)) ; } inline bool try_lock() { return !m_flg.test_and_set(MO_ACQ); } inline void unlock() { m_flg.clear(MO_REL); } }; /// A wrapper class around arbitrary object that needs thread safe caching. -template class CachedObject { +template class CachedObject +{ public: // Method type which refreshes the object when it has been invalidated - using Setter = std::function; + using Setter = std::function; + private: - T m_obj; // the object itself - bool m_valid; // invalidation flag - SpinMutex m_lck; // to make the caching thread safe + T m_obj; // the object itself + bool m_valid; // invalidation flag + SpinMutex m_lck; // to make the caching thread safe + + // the setter will be called just before the object's const value is + // about to be retrieved. + std::function m_setter; - // the setter will be called just before the object's const value is about - // to be retrieved. - std::function m_setter; public: - // Forwarded constructor - template inline CachedObject(Setter fn, Args&&...args): - m_obj(std::forward(args)...), m_valid(false), m_setter(fn) {} + template + inline CachedObject(Setter fn, Args &&... args) + : m_obj(std::forward(args)...), m_valid(false), m_setter(fn) + {} - // invalidate the value of the object. The object will be refreshed at the - // next retrieval (Setter will be called). The data that is used in - // the setter function should be guarded as well during modification so the - // modification has to take place in fn. - inline void invalidate(std::function fn) { - std::lock_guard lck(m_lck); fn(); m_valid = false; + // invalidate the value of the object. The object will be refreshed at + // the next retrieval (Setter will be called). The data that is used in + // the setter function should be guarded as well during modification so + // the modification has to take place in fn. + inline void invalidate(std::function fn) + { + std::lock_guard lck(m_lck); + fn(); + m_valid = false; } // Get the const object properly updated. - inline const T& get() { + inline const T &get() + { std::lock_guard lck(m_lck); - if(!m_valid) { m_setter(m_obj); m_valid = true; } + if (!m_valid) { + m_setter(m_obj); + m_valid = true; + } return m_obj; } }; -/// An std compatible random access iterator which uses indices to the source -/// vector thus resistant to invalidation caused by relocations. It also "knows" -/// its container. No comparison is neccesary to the container "end()" iterator. -/// The template can be instantiated with a different value type than that of -/// the container's but the types must be compatible. E.g. a base class of the -/// contained objects is compatible. +/// An std compatible random access iterator which uses indices to the +/// source vector thus resistant to invalidation caused by relocations. It +/// also "knows" its container. No comparison is neccesary to the container +/// "end()" iterator. The template can be instantiated with a different +/// value type than that of the container's but the types must be +/// compatible. E.g. a base class of the contained objects is compatible. /// /// For a constant iterator, one can instantiate this template with a value /// type preceded with 'const'. -template -class IndexBasedIterator { +class IndexBasedIterator +{ static const size_t NONE = size_t(-1); std::reference_wrapper m_index_ref; - size_t m_idx = NONE; -public: + size_t m_idx = NONE; - using value_type = Value; - using pointer = Value *; - using reference = Value &; - using difference_type = long; +public: + using value_type = Value; + using pointer = Value *; + using reference = Value &; + using difference_type = long; using iterator_category = std::random_access_iterator_tag; - inline explicit - IndexBasedIterator(Vector& index, size_t idx): - m_index_ref(index), m_idx(idx) {} + inline explicit IndexBasedIterator(Vector &index, size_t idx) + : m_index_ref(index), m_idx(idx) + {} // Post increment - inline IndexBasedIterator operator++(int) { - IndexBasedIterator cpy(*this); ++m_idx; return cpy; + inline IndexBasedIterator operator++(int) + { + IndexBasedIterator cpy(*this); + ++m_idx; + return cpy; } - inline IndexBasedIterator operator--(int) { - IndexBasedIterator cpy(*this); --m_idx; return cpy; + inline IndexBasedIterator operator--(int) + { + IndexBasedIterator cpy(*this); + --m_idx; + return cpy; } - inline IndexBasedIterator& operator++() { - ++m_idx; return *this; + inline IndexBasedIterator &operator++() + { + ++m_idx; + return *this; } - inline IndexBasedIterator& operator--() { - --m_idx; return *this; + inline IndexBasedIterator &operator--() + { + --m_idx; + return *this; } - inline IndexBasedIterator& operator+=(difference_type l) { - m_idx += size_t(l); return *this; + inline IndexBasedIterator &operator+=(difference_type l) + { + m_idx += size_t(l); + return *this; } - inline IndexBasedIterator operator+(difference_type l) { - auto cpy = *this; cpy += l; return cpy; + inline IndexBasedIterator operator+(difference_type l) + { + auto cpy = *this; + cpy += l; + return cpy; } - inline IndexBasedIterator& operator-=(difference_type l) { - m_idx -= size_t(l); return *this; + inline IndexBasedIterator &operator-=(difference_type l) + { + m_idx -= size_t(l); + return *this; } - inline IndexBasedIterator operator-(difference_type l) { - auto cpy = *this; cpy -= l; return cpy; + inline IndexBasedIterator operator-(difference_type l) + { + auto cpy = *this; + cpy -= l; + return cpy; } operator difference_type() { return difference_type(m_idx); } /// Tesing the end of the container... this is not possible with std /// iterators. - inline bool is_end() const { return m_idx >= m_index_ref.get().size();} + inline bool is_end() const + { + return m_idx >= m_index_ref.get().size(); + } - inline Value & operator*() const { + inline Value &operator*() const + { assert(m_idx < m_index_ref.get().size()); return m_index_ref.get().operator[](m_idx); } - inline Value * operator->() const { + inline Value *operator->() const + { assert(m_idx < m_index_ref.get().size()); return &m_index_ref.get().operator[](m_idx); } /// If both iterators point past the container, they are equal... - inline bool operator ==(const IndexBasedIterator& other) { + inline bool operator==(const IndexBasedIterator &other) + { size_t e = m_index_ref.get().size(); return m_idx == other.m_idx || (m_idx >= e && other.m_idx >= e); } - inline bool operator !=(const IndexBasedIterator& other) { + inline bool operator!=(const IndexBasedIterator &other) + { return !(*this == other); } - inline bool operator <=(const IndexBasedIterator& other) { + inline bool operator<=(const IndexBasedIterator &other) + { return (m_idx < other.m_idx) || (*this == other); } - inline bool operator <(const IndexBasedIterator& other) { + inline bool operator<(const IndexBasedIterator &other) + { return m_idx < other.m_idx && (*this != other); } - inline bool operator >=(const IndexBasedIterator& other) { + inline bool operator>=(const IndexBasedIterator &other) + { return m_idx > other.m_idx || *this == other; } - inline bool operator >(const IndexBasedIterator& other) { + inline bool operator>(const IndexBasedIterator &other) + { return m_idx > other.m_idx && *this != other; } }; /// A very simple range concept implementation with iterator-like objects. -template class Range { +template class Range +{ It from, to; -public: +public: // The class is ready for range based for loops. It begin() const { return from; } It end() const { return to; } @@ -175,15 +221,17 @@ public: using Type = It; Range() = default; - Range(It &&b, It &&e): - from(std::forward(b)), to(std::forward(e)) {} + Range(It &&b, It &&e) + : from(std::forward(b)), to(std::forward(e)) + {} // Some useful container-like methods... inline size_t size() const { return end() - begin(); } - inline bool empty() const { return size() == 0; } + inline bool empty() const { return size() == 0; } }; -template bool all_of(const C &container) { +template bool all_of(const C &container) +{ return std::all_of(container.begin(), container.end(), [](const typename C::value_type &v) { @@ -196,10 +244,10 @@ template inline X ceil_i(X x, Y y) static_assert(std::is_integral::value && std::is_integral::value && sizeof(X) >= sizeof(Y), ""); - + return (x % y) ? x / y + 1 : x / y; } -} +} // namespace Slic3r #endif // MTUTILS_HPP From 14d8621ffe6e7791528fede8d9e101d3b06b56ce Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 24 Jun 2019 13:11:18 +0200 Subject: [PATCH 185/627] Color_print issues : - fixed #1933 - implemented thumb moving to the mouse click position - implemented "discard color changes" button --- src/slic3r/GUI/wxExtensions.cpp | 78 +++++++++++++++++++++++++++++---- src/slic3r/GUI/wxExtensions.hpp | 4 ++ 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 406daccf55..ed496a97ec 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1583,6 +1583,9 @@ DoubleSlider::DoubleSlider( wxWindow *parent, m_bmp_one_layer_unlock_off = ScalableBitmap(this, "one_layer_unlock_off.png"); m_lock_icon_dim = m_bmp_one_layer_lock_on.bmp().GetSize().x; + m_bmp_revert = ScalableBitmap(this, "undo"); + m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x; + m_selection = ssUndef; // slider events @@ -1638,6 +1641,9 @@ void DoubleSlider::msw_rescale() m_bmp_one_layer_unlock_off.msw_rescale(); m_lock_icon_dim = m_bmp_one_layer_lock_on.bmp().GetSize().x; + m_bmp_revert.msw_rescale(); + m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x; + SLIDER_MARGIN = 4 + Slic3r::GUI::wxGetApp().em_unit(); SetMinSize(get_min_size()); @@ -1874,8 +1880,11 @@ void DoubleSlider::render() //draw color print ticks draw_ticks(dc); - //draw color print ticks + //draw lock/unlock draw_one_layer_icon(dc); + + //draw revert bitmap (if it's shown) + draw_revert_icon(dc); } void DoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end) @@ -2102,6 +2111,24 @@ void DoubleSlider::draw_one_layer_icon(wxDC& dc) m_rect_one_layer_icon = wxRect(x_draw, y_draw, m_lock_icon_dim, m_lock_icon_dim); } +void DoubleSlider::draw_revert_icon(wxDC& dc) +{ + if (m_ticks.empty()) + return; + + int width, height; + get_size(&width, &height); + + wxCoord x_draw, y_draw; + is_horizontal() ? x_draw = width-2 : x_draw = 0.25*SLIDER_MARGIN; + is_horizontal() ? y_draw = 0.25*SLIDER_MARGIN: y_draw = height-2; + + dc.DrawBitmap(m_bmp_revert.bmp(), x_draw, y_draw); + + //update rect of the lock/unlock icon + m_rect_revert_icon = wxRect(x_draw, y_draw, m_revert_icon_dim, m_revert_icon_dim); +} + void DoubleSlider::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection) { const wxRect& rect = wxRect(begin_x, begin_y, m_thumb_size.x, m_thumb_size.y); @@ -2118,8 +2145,8 @@ int DoubleSlider::get_value_from_position(const wxCoord x, const wxCoord y) if (is_horizontal()) return int(double(x - SLIDER_MARGIN) / step + 0.5); - else - return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5); + + return int(m_min_value + double(height - SLIDER_MARGIN - y) / step + 0.5); } void DoubleSlider::detect_selected_slider(const wxPoint& pt) @@ -2169,7 +2196,10 @@ void DoubleSlider::ChangeOneLayerLock() void DoubleSlider::OnLeftDown(wxMouseEvent& event) { + if (HasCapture()) + return; this->CaptureMouse(); + wxClientDC dc(this); wxPoint pos = event.GetLogicalPosition(dc); if (is_point_in_rect(pos, m_rect_tick_action) && m_is_enabled_tick_manipulation) { @@ -2179,6 +2209,7 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event) m_is_left_down = true; if (is_point_in_rect(pos, m_rect_one_layer_icon)) { + // switch on/off one layer mode m_is_one_layer = !m_is_one_layer; if (!m_is_one_layer) { SetLowerValue(m_min_value); @@ -2187,20 +2218,36 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event) m_selection == ssLower ? correct_lower_value() : correct_higher_value(); if (!m_selection) m_selection = ssHigher; } + else if (is_point_in_rect(pos, m_rect_revert_icon)) { + // discard all color changes + SetLowerValue(m_min_value); + SetHigherValue(m_max_value); + + m_selection == ssLower ? correct_lower_value() : correct_higher_value(); + if (!m_selection) m_selection = ssHigher; + + m_ticks.clear(); + wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); + } else detect_selected_slider(pos); - if (!m_selection && m_is_enabled_tick_manipulation) { - const auto tick = is_point_near_tick(pos); - if (tick >= 0) + if (!m_selection) { + const int tick_val = is_point_near_tick(pos); + /* Set current thumb position to the nearest tick (if it is) + * OR to a value corresponding to the mouse click + * */ + const int mouse_val = tick_val >= 0 && m_is_enabled_tick_manipulation ? tick_val : + get_value_from_position(pos.x, pos.y); + if (mouse_val >= 0) { - if (abs(tick - m_lower_value) < abs(tick - m_higher_value)) { - SetLowerValue(tick); + if (abs(mouse_val - m_lower_value) < abs(mouse_val - m_higher_value)) { + SetLowerValue(mouse_val); correct_lower_value(); m_selection = ssLower; } else { - SetHigherValue(tick); + SetHigherValue(mouse_val); correct_higher_value(); m_selection = ssHigher; } @@ -2240,9 +2287,13 @@ void DoubleSlider::OnMotion(wxMouseEvent& event) const wxClientDC dc(this); const wxPoint pos = event.GetLogicalPosition(dc); + m_is_one_layer_icon_focesed = is_point_in_rect(pos, m_rect_one_layer_icon); + bool is_revert_icon_focused = false; + if (!m_is_left_down && !m_is_one_layer) { m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action); + is_revert_icon_focused = !m_ticks.empty() && is_point_in_rect(pos, m_rect_revert_icon); } else if (m_is_left_down || m_is_right_down) { if (m_selection == ssLower) { @@ -2262,6 +2313,13 @@ void DoubleSlider::OnMotion(wxMouseEvent& event) Update(); event.Skip(); + // Set tooltips with information for each icon + const wxString tooltip = m_is_one_layer_icon_focesed ? _(L("One layer mode")) : + m_is_action_icon_focesed ? _(L("Add/Del color change")) : + is_revert_icon_focused ? _(L("Discard all color changes")) : + wxEmptyString; + this->SetToolTip(tooltip); + if (action) { wxCommandEvent e(wxEVT_SCROLL_CHANGED); @@ -2412,7 +2470,9 @@ void DoubleSlider::OnChar(wxKeyEvent& event) void DoubleSlider::OnRightDown(wxMouseEvent& event) { + if (HasCapture()) return; this->CaptureMouse(); + const wxClientDC dc(this); detect_selected_slider(event.GetLogicalPosition(dc)); if (!m_selection) diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index dd035690ad..081c0d48f9 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -742,6 +742,7 @@ protected: void draw_ticks(wxDC& dc); void draw_colored_band(wxDC& dc); void draw_one_layer_icon(wxDC& dc); + void draw_revert_icon(wxDC& dc); void draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection); void draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection); void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const; @@ -783,6 +784,7 @@ private: ScalableBitmap m_bmp_one_layer_lock_off; ScalableBitmap m_bmp_one_layer_unlock_on; ScalableBitmap m_bmp_one_layer_unlock_off; + ScalableBitmap m_bmp_revert; SelectedSlider m_selection; bool m_is_left_down = false; bool m_is_right_down = false; @@ -796,9 +798,11 @@ private: wxRect m_rect_higher_thumb; wxRect m_rect_tick_action; wxRect m_rect_one_layer_icon; + wxRect m_rect_revert_icon; wxSize m_thumb_size; int m_tick_icon_dim; int m_lock_icon_dim; + int m_revert_icon_dim; long m_style; float m_label_koef = 1.0; From 7aaba25520ea37d662ced74b465b66c394f34593 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 24 Jun 2019 13:21:05 +0200 Subject: [PATCH 186/627] Do not allow to copy/paste volumes when using sla printer --- src/slic3r/GUI/GLCanvas3D.cpp | 4 +-- src/slic3r/GUI/MainFrame.cpp | 4 +-- src/slic3r/GUI/Plater.cpp | 58 ++++++++++++++++++++++------------- src/slic3r/GUI/Plater.hpp | 5 ++- src/slic3r/GUI/Selection.cpp | 34 ++++++++++++++++++++ src/slic3r/GUI/Selection.hpp | 3 ++ 6 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e0aa670740..ffaa2d20c5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3488,7 +3488,7 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]"; item.sprite_id = 4; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy(); }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); }; if (!m_toolbar.add_item(item)) return false; @@ -3499,7 +3499,7 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]"; item.sprite_id = 5; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste(); }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); }; if (!m_toolbar.add_item(item)) return false; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 667dcd899d..d800f6f380 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -505,10 +505,10 @@ void MainFrame::init_menubar() editMenu->AppendSeparator(); append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C", _(L("Copy selection to clipboard")), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, - menu_icon("copy_menu"), nullptr, [this](){return m_plater->can_copy(); }, this); + menu_icon("copy_menu"), nullptr, [this](){return m_plater->can_copy_to_clipboard(); }, this); append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "V", _(L("Paste clipboard")), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, - menu_icon("paste_menu"), nullptr, [this](){return m_plater->can_paste(); }, this); + menu_icon("paste_menu"), nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this); } // Window menu diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c2456db800..f68267cef0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4187,30 +4187,14 @@ void Plater::update_object_menu() { p->update_object_menu(); } void Plater::copy_selection_to_clipboard() { - p->view3D->get_canvas3d()->get_selection().copy_to_clipboard(); + if (can_copy_to_clipboard()) + p->view3D->get_canvas3d()->get_selection().copy_to_clipboard(); } void Plater::paste_from_clipboard() { - p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); -} - -bool Plater::can_paste_from_clipboard() const -{ - const Selection& selection = p->view3D->get_canvas3d()->get_selection(); - const Selection::Clipboard& clipboard = selection.get_clipboard(); - Selection::EMode mode = clipboard.get_mode(); - - if (clipboard.is_empty()) - return false; - - if ((mode == Selection::Volume) && !selection.is_from_single_instance()) - return false; - - if ((mode == Selection::Instance) && (selection.get_mode() != Selection::Instance)) - return false; - - return true; + if (can_paste_from_clipboard()) + p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); } void Plater::msw_rescale() @@ -4237,7 +4221,37 @@ bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); } bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); } bool Plater::can_arrange() const { return p->can_arrange(); } bool Plater::can_layers_editing() const { return p->can_layers_editing(); } -bool Plater::can_copy() const { return !is_selection_empty(); } -bool Plater::can_paste() const { return can_paste_from_clipboard(); } +bool Plater::can_paste_from_clipboard() const +{ + const Selection& selection = p->view3D->get_canvas3d()->get_selection(); + const Selection::Clipboard& clipboard = selection.get_clipboard(); + + if (clipboard.is_empty()) + return false; + + if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !clipboard.is_sla_compliant()) + return false; + + Selection::EMode mode = clipboard.get_mode(); + if ((mode == Selection::Volume) && !selection.is_from_single_instance()) + return false; + + if ((mode == Selection::Instance) && (selection.get_mode() != Selection::Instance)) + return false; + + return true; +} + +bool Plater::can_copy_to_clipboard() const +{ + if (is_selection_empty()) + return false; + + const Selection& selection = p->view3D->get_canvas3d()->get_selection(); + if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !selection.is_sla_compliant()) + return false; + + return true; +} }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 761bf7a052..2851af6544 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -200,7 +200,6 @@ public: void copy_selection_to_clipboard(); void paste_from_clipboard(); - bool can_paste_from_clipboard() const; bool can_delete() const; bool can_delete_all() const; @@ -212,8 +211,8 @@ public: bool can_split_to_volumes() const; bool can_arrange() const; bool can_layers_editing() const; - bool can_copy() const; - bool can_paste() const; + bool can_paste_from_clipboard() const; + bool can_copy_to_clipboard() const; void msw_rescale(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 6e80783ccd..97168ee045 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -47,6 +47,26 @@ Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_trans { } +bool Selection::Clipboard::is_sla_compliant() const +{ + if (m_mode == Selection::Volume) + return false; + + for (const ModelObject* o : m_model.objects) + { + if (o->is_multiparts()) + return false; + + for (const ModelVolume* v : o->volumes) + { + if (v->is_modifier()) + return false; + } + } + + return true; +} + Selection::Selection() : m_volumes(nullptr) , m_model(nullptr) @@ -385,6 +405,20 @@ bool Selection::is_from_single_object() const return (0 <= idx) && (idx < 1000); } +bool Selection::is_sla_compliant() const +{ + if (m_mode == Volume) + return false; + + for (unsigned int i : m_list) + { + if ((*m_volumes)[i]->is_modifier) + return false; + } + + return true; +} + bool Selection::requires_uniform_scale() const { if (is_single_full_instance() || is_single_modifier() || is_single_volume()) diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 5da1e477bc..802f8d2847 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -152,6 +152,8 @@ public: void reset() { m_model.clear_objects(); } bool is_empty() const { return m_model.objects.empty(); } + bool is_sla_compliant() const; + ModelObject* add_object() { return m_model.add_object(); } ModelObject* get_object(unsigned int id) { return (id < (unsigned int)m_model.objects.size()) ? m_model.objects[id] : nullptr; } const ModelObjectPtrs& get_objects() const { return m_model.objects; } @@ -257,6 +259,7 @@ public: bool is_mixed() const { return m_type == Mixed; } bool is_from_single_instance() const { return get_instance_idx() != -1; } bool is_from_single_object() const; + bool is_sla_compliant() const; bool contains_volume(unsigned int volume_idx) const { return m_list.find(volume_idx) != m_list.end(); } bool requires_uniform_scale() const; From e737f15bfa68236d184dfba5861a3389fb903986 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 24 Jun 2019 13:45:35 +0200 Subject: [PATCH 187/627] Fix of OSX build --- src/slic3r/GUI/wxExtensions.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index ed496a97ec..aed4236749 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -2316,8 +2316,7 @@ void DoubleSlider::OnMotion(wxMouseEvent& event) // Set tooltips with information for each icon const wxString tooltip = m_is_one_layer_icon_focesed ? _(L("One layer mode")) : m_is_action_icon_focesed ? _(L("Add/Del color change")) : - is_revert_icon_focused ? _(L("Discard all color changes")) : - wxEmptyString; + is_revert_icon_focused ? _(L("Discard all color changes")) : ""; this->SetToolTip(tooltip); if (action) From 198600543dd707a51f29660c2583459ae2b34933 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 24 Jun 2019 15:27:32 +0200 Subject: [PATCH 188/627] Fix of incorrect color print preview due to the tbb::parallel_for not respecting the grain size exactly. Also the tool path generation has been optimized to launch less threads and to produce larger vertex buffers. --- src/slic3r/GUI/GLCanvas3D.cpp | 102 +++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 19d1bc0edc..3d9b24c11a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4627,17 +4627,12 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c bool color_by_tool() const { return tool_colors != nullptr; } size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } - int volume_idx(int extruder, int feature) const - { - return this->color_by_color_print() ? 0 : this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(extruder - 1, 0)) : feature; - } // For coloring by a color_print(M600), return a parsed color. bool color_by_color_print() const { return color_print_values!=nullptr; } - const float* color_print_by_layer_idx(const size_t layer_idx) const - { + const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const { auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), layers[layer_idx]->print_z + EPSILON); - return color_tool((it - color_print_values->begin()) % number_tools()); + return (it - color_print_values->begin()) % number_tools(); } } ctxt; @@ -4670,7 +4665,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start"; //FIXME Improve the heuristics for a grain size. - size_t grain_size = ctxt.color_by_color_print() ? size_t(1) : std::max(ctxt.layers.size() / 16, size_t(1)); + size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); tbb::spin_mutex new_volume_mutex; auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { auto *volume = new GLVolume(color); @@ -4679,14 +4674,34 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c new_volume_mutex.unlock(); return volume; }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - std::vector volumes_per_thread(ctxt.layers.size()); + const size_t volumes_cnt_initial = m_volumes.volumes.size(); tbb::parallel_for( tbb::blocked_range(0, ctxt.layers.size(), grain_size), [&ctxt, &new_volume](const tbb::blocked_range& range) { - GLVolumePtrs vols; - if (ctxt.color_by_color_print()) - vols.emplace_back(new_volume(ctxt.color_print_by_layer_idx(range.begin()))); + GLVolumePtrs vols; + std::vector color_print_layer_to_glvolume; + auto volume = [&ctxt, &vols, &color_print_layer_to_glvolume, &range](size_t layer_idx, int extruder, int feature) -> GLVolume& { + return *vols[ctxt.color_by_color_print() ? + color_print_layer_to_glvolume[layer_idx - range.begin()] : + ctxt.color_by_tool() ? + std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : + feature + ]; + }; + if (ctxt.color_by_color_print()) { + // Create a map from the layer index to a GLVolume, which is initialized with the correct layer span color. + std::vector color_print_tool_to_glvolume(ctxt.number_tools(), -1); + color_print_layer_to_glvolume.reserve(range.end() - range.begin()); + vols.reserve(ctxt.number_tools()); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + int idx_tool = (int)ctxt.color_print_color_idx_by_layer_idx(idx_layer); + if (color_print_tool_to_glvolume[idx_tool] == -1) { + color_print_tool_to_glvolume[idx_tool] = (int)vols.size(); + vols.emplace_back(new_volume(ctxt.color_tool(idx_tool))); + } + color_print_layer_to_glvolume.emplace_back(color_print_tool_to_glvolume[idx_tool]); + } + } else if (ctxt.color_by_tool()) { for (size_t i = 0; i < ctxt.number_tools(); ++i) vols.emplace_back(new_volume(ctxt.color_tool(i))); @@ -4694,33 +4709,31 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c else vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; for (GLVolume *vol : vols) - vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { const Layer *layer = ctxt.layers[idx_layer]; - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) { - vol.print_zs.push_back(layer->print_z); - vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); + for (GLVolume *vol : vols) + if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { + vol->print_zs.push_back(layer->print_z); + vol->offsets.push_back(vol->indexed_vertex_array.quad_indices.size()); + vol->offsets.push_back(vol->indexed_vertex_array.triangle_indices.size()); } - } for (const Point © : *ctxt.shifted_copies) { for (const LayerRegion *layerm : layer->regions()) { if (ctxt.has_perimeters) _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - *vols[ctxt.volume_idx(layerm->region()->config().perimeter_extruder.value, 0)]); + volume(idx_layer, layerm->region()->config().perimeter_extruder.value, 0)); if (ctxt.has_infill) { for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); - if (!fill->entities.empty()) + if (! fill->entities.empty()) _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - *vols[ctxt.volume_idx( - is_solid_infill(fill->entities.front()->role()) ? - layerm->region()->config().solid_infill_extruder : - layerm->region()->config().infill_extruder, - 1)]); + volume(idx_layer, + is_solid_infill(fill->entities.front()->role()) ? + layerm->region()->config().solid_infill_extruder : + layerm->region()->config().infill_extruder, + 1)); } } } @@ -4729,36 +4742,35 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c if (support_layer) { for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, - *vols[ctxt.volume_idx( - (extrusion_entity->role() == erSupportMaterial) ? - support_layer->object()->config().support_material_extruder : - support_layer->object()->config().support_material_interface_extruder, - 2)]); + volume(idx_layer, + (extrusion_entity->role() == erSupportMaterial) ? + support_layer->object()->config().support_material_extruder : + support_layer->object()->config().support_material_interface_extruder, + 2)); } } } - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { + // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. + for (GLVolume *&vol : vols) + if (vol->indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { // Store the vertex arrays and restart their containers, - vols[i] = new_volume(vol.color); - GLVolume &vol_new = *vols[i]; + vol = new_volume(vol->color); + GLVolume &vol_new = *vol; // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); + vol_new.indexed_vertex_array = std::move(vol->indexed_vertex_array); // Copy the content back to the old GLVolume. - vol.indexed_vertex_array = vol_new.indexed_vertex_array; + vol->indexed_vertex_array = vol_new.indexed_vertex_array; // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); + vol->bounding_box = vol->indexed_vertex_array.bounding_box(); // Clear the buffers, but keep them pre-allocated. vol_new.indexed_vertex_array.clear(); // Just make sure that clear did not clear the reserved memory. vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); } - } } for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); - vol->indexed_vertex_array.shrink_to_fit(); + vol->bounding_box = vol->indexed_vertex_array.bounding_box(); + vol->indexed_vertex_array.shrink_to_fit(); } }); From c198d826af9ead36c511b63b88363335e72487e0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 24 Jun 2019 15:43:16 +0200 Subject: [PATCH 189/627] Setting min_z bigger than max_z cause an increase of the max_z --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 12c1022ceb..1426ccf029 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -82,13 +82,14 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) auto editor = new LayerRangeEditor(m_parent, double_to_string(range.first), etMinZ, set_focus_fn, [range, set_focus, this](coordf_t min_z, bool enter_pressed) { - if (fabs(min_z - range.first) < EPSILON || min_z > range.second) { + if (fabs(min_z - range.first) < EPSILON) { m_selection_type = etUndef; return false; } // data for next focusing - const t_layer_height_range& new_range = { min_z, range.second }; + coordf_t max_z = min_z < range.second ? range.second : min_z + 0.5; + const t_layer_height_range& new_range = { min_z, max_z/*range.second*/ }; set_focus(new_range, etMinZ, enter_pressed); return wxGetApp().obj_list()->edit_layer_range(range, new_range); From 992170c5f66e1bc823c03a26ad43af8b03d188d3 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 24 Jun 2019 15:55:14 +0200 Subject: [PATCH 190/627] 1) Perspective camera set as default camera type 2) Camera type selection added to Preferences dialog --- src/slic3r/GUI/AppConfig.cpp | 4 ++-- src/slic3r/GUI/Camera.cpp | 15 ++++++--------- src/slic3r/GUI/Camera.hpp | 1 + src/slic3r/GUI/GLCanvas3D.cpp | 3 +++ src/slic3r/GUI/GUI_Preview.cpp | 6 ++++++ src/slic3r/GUI/GUI_Preview.hpp | 2 ++ src/slic3r/GUI/Plater.cpp | 4 +--- src/slic3r/GUI/Preferences.cpp | 11 +++++++++-- 8 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index edc9845a13..51bee9e756 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -73,8 +73,8 @@ void AppConfig::set_defaults() if (get("custom_toolbar_size").empty()) set("custom_toolbar_size", "100"); - if (get("camera_type").empty()) - set("camera_type", "1"); + if (get("use_perspective_camera").empty()) + set("use_perspective_camera", "1"); // Remove legacy window positions/sizes erase("", "main_frame_maximized"); diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 1f8513ac29..6cb8ff5201 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -31,7 +31,7 @@ Camera::Camera() : phi(45.0f) , requires_zoom_to_bed(false) , inverted_phi(false) - , m_type(Ortho) + , m_type(Perspective) , m_target(Vec3d::Zero()) , m_theta(45.0f) , m_zoom(1.0) @@ -61,20 +61,17 @@ void Camera::set_type(EType type) if (m_type != type) { m_type = type; - - wxGetApp().app_config->set("camera_type", std::to_string(m_type)); + wxGetApp().app_config->set("use_perspective_camera", (m_type == Perspective) ? "1" : "0"); wxGetApp().app_config->save(); } } void Camera::set_type(const std::string& type) { - if (!type.empty() && (type != "1")) - { - unsigned char type_id = atoi(type.c_str()); - if (((unsigned char)Ortho < type_id) && (type_id < (unsigned char)Num_types)) - set_type((Camera::EType)type_id); - } + if (type == "1") + set_type(Perspective); + else + set_type(Ortho); } void Camera::select_next_type() diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index bd2541ce2f..79e87c7264 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -49,6 +49,7 @@ public: EType get_type() const { return m_type; } std::string get_type_as_string() const; void set_type(EType type); + // valid values for type: "0" -> ortho, "1" -> perspective void set_type(const std::string& type); void select_next_type(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 25319e9ce2..2f265eb928 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3312,6 +3312,9 @@ void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool foc void GLCanvas3D::update_ui_from_settings() { + m_camera.set_type(wxGetApp().app_config->get("use_perspective_camera")); + m_dirty = true; + #if ENABLE_RETINA_GL const float orig_scaling = m_retina_helper->get_scale_factor(); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 2f5e10962e..671c49eefa 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -313,6 +313,12 @@ Preview::~Preview() } } +void Preview::set_as_dirty() +{ + if (m_canvas != nullptr) + m_canvas->set_as_dirty(); +} + void Preview::set_number_extruders(unsigned int number_extruders) { if (m_number_extruders != number_extruders) diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 93038b0e56..993e260e41 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -110,6 +110,8 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } GLCanvas3D* get_canvas3d() { return m_canvas; } + void set_as_dirty(); + void set_number_extruders(unsigned int number_extruders); void set_canvas_as_dirty(); void set_enabled(bool enabled); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f68267cef0..9d0c979b66 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1774,7 +1774,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) set_current_panel(view3D); // updates camera type from .ini file - camera.set_type(get_config("camera_type")); + camera.set_type(get_config("use_perspective_camera")); } void Plater::priv::update(bool force_full_scene_refresh) @@ -1842,10 +1842,8 @@ void Plater::priv::update_ui_from_settings() // $self->{buttons_sizer}->Layout; // } -#if ENABLE_RETINA_GL view3D->get_canvas3d()->update_ui_from_settings(); preview->get_canvas3d()->update_ui_from_settings(); -#endif } ProgressStatusBar* Plater::priv::statusbar() diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 52cbdbb1d9..7b31870127 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -28,7 +28,7 @@ void PreferencesDialog::build() m_icon_size_sizer->ShowItems(boost::any_cast(value)); this->layout(); } - }; + }; // TODO // $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( @@ -97,7 +97,7 @@ void PreferencesDialog::build() option = Option (def,"show_incompatible_presets"); m_optgroup->append_single_option_line(option); - // TODO: remove? + // TODO: remove? def.label = L("Use legacy OpenGL 1.1 rendering"); def.type = coBool; def.tooltip = L("If you have rendering issues caused by a buggy OpenGL 2.0 driver, " @@ -117,6 +117,13 @@ void PreferencesDialog::build() m_optgroup->append_single_option_line(option); #endif + def.label = L("Use perspective camera"); + def.type = coBool; + def.tooltip = L("If enabled, use perspective camera. If not enabled, use orthographic camera."); + def.set_default_value(new ConfigOptionBool{ app_config->get("use_perspective_camera") == "1" }); + option = Option(def, "use_perspective_camera"); + m_optgroup->append_single_option_line(option); + def.label = L("Use custom size for toolbar icons"); def.type = coBool; def.tooltip = L("If enabled, you can change size of toolbar icons manually."); From d689195bda8fe9d0618b97685858dbc2b263bf24 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 25 Jun 2019 08:56:53 +0200 Subject: [PATCH 191/627] Fix of the previous commit on color change fix. --- src/slic3r/GUI/GLCanvas3D.cpp | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 25319e9ce2..1aa542d0cf 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4646,22 +4646,24 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c } } // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. - for (GLVolume *&vol : vols) - if (vol->indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { - // Store the vertex arrays and restart their containers, - vol = new_volume(vol->color); - GLVolume &vol_new = *vol; - // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol->indexed_vertex_array); - // Copy the content back to the old GLVolume. - vol->indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); - // Clear the buffers, but keep them pre-allocated. - vol_new.indexed_vertex_array.clear(); - // Just make sure that clear did not clear the reserved memory. - vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - } + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { + // Store the vertex arrays and restart their containers, + vols[i] = new_volume(vol.color); + GLVolume &vol_new = *vols[i]; + // Assign the large pre-allocated buffers to the new GLVolume. + vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); + // Copy the content back to the old GLVolume. + vol.indexed_vertex_array = vol_new.indexed_vertex_array; + // Finalize a bounding box of the old GLVolume. + vol.bounding_box = vol.indexed_vertex_array.bounding_box(); + // Clear the buffers, but keep them pre-allocated. + vol_new.indexed_vertex_array.clear(); + // Just make sure that clear did not clear the reserved memory. + vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + } + } } for (GLVolume *vol : vols) { vol->bounding_box = vol->indexed_vertex_array.bounding_box(); From 3d755e1bbe642becb73c9f64910616787eade373 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 25 Jun 2019 09:20:58 +0200 Subject: [PATCH 192/627] Removed 'Use legacy OpenGL 1.1 rendering' option --- src/slic3r/GUI/AppConfig.cpp | 7 +++---- src/slic3r/GUI/GLCanvas3D.cpp | 13 +++---------- src/slic3r/GUI/GLCanvas3D.hpp | 4 +--- src/slic3r/GUI/GLCanvas3DManager.cpp | 10 +++------- src/slic3r/GUI/GLCanvas3DManager.hpp | 1 - src/slic3r/GUI/Preferences.cpp | 13 +------------ 6 files changed, 11 insertions(+), 37 deletions(-) diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index 51bee9e756..3b62100582 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -54,10 +54,9 @@ void AppConfig::set_defaults() if (get("preset_update").empty()) set("preset_update", "1"); - // Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers. - // github.com/prusa3d/PrusaSlicer/issues/233 - if (get("use_legacy_opengl").empty()) - set("use_legacy_opengl", "0"); + // remove old 'use_legacy_opengl' parameter from this config, if present + if (!get("use_legacy_opengl").empty()) + erase("", "use_legacy_opengl"); #if __APPLE__ if (get("use_retina_opengl").empty()) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index cf8a49ea22..c828e0ec66 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -198,8 +198,7 @@ void GLCanvas3D::Shader::_reset() #endif // !ENABLE_TEXTURES_FROM_SVG GLCanvas3D::LayersEditing::LayersEditing() - : m_use_legacy_opengl(false) - , m_enabled(false) + : m_enabled(false) , m_z_texture_id(0) , m_model_object(nullptr) , m_object_max_z(0.f) @@ -274,12 +273,7 @@ void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) bool GLCanvas3D::LayersEditing::is_allowed() const { - return !m_use_legacy_opengl && m_shader.is_initialized() && m_shader.get_shader()->shader_program_id > 0 && m_z_texture_id > 0; -} - -void GLCanvas3D::LayersEditing::set_use_legacy_opengl(bool use_legacy_opengl) -{ - m_use_legacy_opengl = use_legacy_opengl; + return m_shader.is_initialized() && m_shader.get_shader()->shader_program_id > 0 && m_z_texture_id > 0; } bool GLCanvas3D::LayersEditing::is_enabled() const @@ -1253,7 +1247,7 @@ void GLCanvas3D::post_event(wxEvent &&event) wxPostEvent(m_canvas, event); } -bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) +bool GLCanvas3D::init(bool useVBOs) { if (m_initialized) return true; @@ -1311,7 +1305,6 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) return false; m_use_VBOs = useVBOs; - m_layers_editing.set_use_legacy_opengl(use_legacy_opengl); // on linux the gl context is not valid until the canvas is not shown on screen // we defer the geometry finalization of volumes until the first call to render() diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index d39a910b3b..5a42879039 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -197,7 +197,6 @@ class GLCanvas3D static const float THICKNESS_BAR_WIDTH; static const float THICKNESS_RESET_BUTTON_HEIGHT; - bool m_use_legacy_opengl; bool m_enabled; Shader m_shader; unsigned int m_z_texture_id; @@ -250,7 +249,6 @@ class GLCanvas3D void select_object(const Model &model, int object_id); bool is_allowed() const; - void set_use_legacy_opengl(bool use_legacy_opengl); bool is_enabled() const; void set_enabled(bool enabled); @@ -492,7 +490,7 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas; } const wxGLCanvas* get_wxglcanvas() const { return m_canvas; } - bool init(bool useVBOs, bool use_legacy_opengl); + bool init(bool useVBOs); void post_event(wxEvent &&event); void set_as_dirty(); diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index 4f64b4e877..a1430ef22b 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -192,7 +192,6 @@ GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info; GLCanvas3DManager::GLCanvas3DManager() : m_context(nullptr) , m_gl_initialized(false) - , m_use_legacy_opengl(false) , m_use_VBOs(false) { } @@ -268,8 +267,7 @@ void GLCanvas3DManager::init_gl() { glewInit(); const AppConfig* config = GUI::get_app_config(); - m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1"); - m_use_VBOs = !m_use_legacy_opengl && s_gl_info.is_version_greater_or_equal_to(2, 0); + m_use_VBOs = s_gl_info.is_version_greater_or_equal_to(2, 0); m_gl_initialized = true; if (GLEW_EXT_texture_compression_s3tc) s_compressed_textures_supported = true; @@ -325,16 +323,14 @@ bool GLCanvas3DManager::init(GLCanvas3D& canvas) if (!m_gl_initialized) init_gl(); - return canvas.init(m_use_VBOs, m_use_legacy_opengl); + return canvas.init(m_use_VBOs); } void GLCanvas3DManager::detect_multisample(int* attribList) { int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER; const AppConfig* app_config = GUI::get_app_config(); - bool enable_multisample = app_config != nullptr - && app_config->get("use_legacy_opengl") != "1" - && wxVersion >= 30003; + bool enable_multisample = wxVersion >= 30003; s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? MS_Enabled : MS_Disabled; // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 26c2558d07..7a600dcbdb 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -75,7 +75,6 @@ private: wxGLContext* m_context; static GLInfo s_gl_info; bool m_gl_initialized; - bool m_use_legacy_opengl; bool m_use_VBOs; static EMultisampleState s_multisample; static bool s_compressed_textures_supported; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 7b31870127..827d1f4e05 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -97,16 +97,6 @@ void PreferencesDialog::build() option = Option (def,"show_incompatible_presets"); m_optgroup->append_single_option_line(option); - // TODO: remove? - def.label = L("Use legacy OpenGL 1.1 rendering"); - def.type = coBool; - def.tooltip = L("If you have rendering issues caused by a buggy OpenGL 2.0 driver, " - "you may try to check this checkbox. This will disable the layer height " - "editing and anti aliasing, so it is likely better to upgrade your graphics driver."); - def.set_default_value(new ConfigOptionBool{ app_config->get("use_legacy_opengl") == "1" }); - option = Option (def,"use_legacy_opengl"); - m_optgroup->append_single_option_line(option); - #if __APPLE__ def.label = L("Use Retina resolution for the 3D scene"); def.type = coBool; @@ -150,8 +140,7 @@ void PreferencesDialog::build() void PreferencesDialog::accept() { - if (m_values.find("no_defaults") != m_values.end() || - m_values.find("use_legacy_opengl") != m_values.end()) { + if (m_values.find("no_defaults") != m_values.end()) { warning_catcher(this, wxString::Format(_(L("You need to restart %s to make the changes effective.")), SLIC3R_APP_NAME)); } From 2f806dedc762c1ee810be44820aaff8de8cb5d07 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 25 Jun 2019 09:51:43 +0200 Subject: [PATCH 193/627] Fixed rendering of selection rectangle with perspective camera --- src/slic3r/GUI/GLSelectionRectangle.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp index 327cb1fde8..684563bff3 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.cpp +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -68,7 +68,8 @@ namespace GUI { if (!is_dragging()) return; - float zoom = (float)canvas.get_camera().get_zoom(); + const Camera& camera = canvas.get_camera(); + float zoom = (float)camera.get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; Size cnv_size = canvas.get_canvas_size(); @@ -96,6 +97,11 @@ namespace GUI { glsafe(::glPushMatrix()); glsafe(::glLoadIdentity()); + // ensure that the rectangle is renderered inside the frustrum + glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); + // ensure that the overlay fits the frustrum near z plane + double gui_scale = camera.get_gui_scale(); + glsafe(::glScaled(gui_scale, gui_scale, 1.0)); glsafe(::glPushAttrib(GL_ENABLE_BIT)); glsafe(::glLineStipple(4, 0xAAAA)); From 471331e8c17c57b23fd8f55e09416789ce53750b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 25 Jun 2019 13:03:10 +0200 Subject: [PATCH 194/627] CMake: -Wignored-attributes is not supported in GCC<6.1 -turned off -Wunknown-pragmas for GCC --- CMakeLists.txt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba264c8c36..346e2dfd5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,8 +169,19 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STRE # On GCC and Clang, no return from a non-void function is a warning only. Here, we make it an error. add_compile_options(-Werror=return-type) - #removes LOTS of extraneous Eigen warnings - # add_compile_options(-Wno-ignored-attributes) # Tamas: Eigen include dirs are marked as SYSTEM + #removes LOTS of extraneous Eigen warnings (GCC only supports it since 6.1) + #if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6.1) + # add_compile_options(-Wno-ignored-attributes) # Tamas: Eigen include dirs are marked as SYSTEM + #endif() + + #GCC generates loads of -Wunknown-pragmas when compiling igl. The fix is not easy due to a bug in gcc, see + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66943 or + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431 + # We will turn the warning of for GCC for now: + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + add_compile_options(-Wno-unknown-pragmas) + endif() + if (SLIC3R_ASAN) add_compile_options(-fsanitize=address -fno-omit-frame-pointer) From cb916c4ddae18ccf13df15af03fdbb212cade047 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 25 Jun 2019 13:06:04 +0200 Subject: [PATCH 195/627] Fixed warnings in libslic3r --- src/libslic3r/EdgeGrid.cpp | 24 ++++++------ src/libslic3r/EdgeGrid.hpp | 4 +- src/libslic3r/ExtrusionSimulator.cpp | 16 ++++---- src/libslic3r/Fill/FillPlanePath.cpp | 6 +-- src/libslic3r/Fill/FillRectilinear2.cpp | 13 ++++--- src/libslic3r/Fill/FillRectilinear3.cpp | 6 +-- src/libslic3r/Format/3mf.cpp | 6 +-- src/libslic3r/Format/AMF.cpp | 8 ++-- src/libslic3r/Format/objparser.cpp | 4 +- src/libslic3r/GCode.cpp | 38 +++++++++--------- src/libslic3r/GCode/CoolingBuffer.cpp | 2 +- src/libslic3r/GCode/PreviewData.cpp | 4 ++ src/libslic3r/GCode/SpiralVase.cpp | 2 +- src/libslic3r/GCode/ToolOrdering.cpp | 6 +-- src/libslic3r/Geometry.cpp | 2 +- src/libslic3r/LayerRegion.cpp | 4 +- src/libslic3r/Line.cpp | 1 - src/libslic3r/MotionPlanner.cpp | 2 +- src/libslic3r/PerimeterGenerator.cpp | 4 +- src/libslic3r/PlaceholderParser.cpp | 6 +-- src/libslic3r/PolylineCollection.cpp | 2 +- src/libslic3r/Print.cpp | 11 +++--- src/libslic3r/Print.hpp | 2 +- src/libslic3r/PrintConfig.cpp | 51 ++++++++++++------------- src/libslic3r/PrintObject.cpp | 15 +++----- src/libslic3r/SLA/SLAAutoSupports.cpp | 9 ++--- src/libslic3r/SLAPrint.cpp | 2 - src/libslic3r/Slicing.cpp | 4 -- src/libslic3r/SupportMaterial.cpp | 18 ++++----- src/libslic3r/utils.cpp | 4 +- 30 files changed, 129 insertions(+), 147 deletions(-) diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index c34aca8f2c..9d02ef09b7 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -146,10 +146,10 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) coord_t iy = p1(1) / m_resolution; coord_t ixb = p2(0) / m_resolution; coord_t iyb = p2(1) / m_resolution; - assert(ix >= 0 && ix < m_cols); - assert(iy >= 0 && iy < m_rows); - assert(ixb >= 0 && ixb < m_cols); - assert(iyb >= 0 && iyb < m_rows); + assert(ix >= 0 && size_t(ix) < m_cols); + assert(iy >= 0 && size_t(iy) < m_rows); + assert(ixb >= 0 && size_t(ixb) < m_cols); + assert(iyb >= 0 && size_t(iyb) < m_rows); // Account for the end points. ++ m_cells[iy*m_cols+ix].end; if (ix == ixb && iy == iyb) @@ -290,10 +290,10 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) coord_t iy = p1(1) / m_resolution; coord_t ixb = p2(0) / m_resolution; coord_t iyb = p2(1) / m_resolution; - assert(ix >= 0 && ix < m_cols); - assert(iy >= 0 && iy < m_rows); - assert(ixb >= 0 && ixb < m_cols); - assert(iyb >= 0 && iyb < m_rows); + assert(ix >= 0 && size_t(ix) < m_cols); + assert(iy >= 0 && size_t(iy) < m_rows); + assert(ixb >= 0 && size_t(ixb) < m_cols); + assert(iyb >= 0 && size_t(iyb) < m_rows); // Account for the end points. m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); if (ix == ixb && iy == iyb) @@ -775,11 +775,11 @@ void EdgeGrid::Grid::calculate_sdf() // For each corner of this cell and its 1 ring neighbours: for (int corner_y = -1; corner_y < 3; ++ corner_y) { coord_t corner_r = r + corner_y; - if (corner_r < 0 || corner_r >= nrows) + if (corner_r < 0 || (size_t)corner_r >= nrows) continue; for (int corner_x = -1; corner_x < 3; ++ corner_x) { coord_t corner_c = c + corner_x; - if (corner_c < 0 || corner_c >= ncols) + if (corner_c < 0 || (size_t)corner_c >= ncols) continue; float &d_min = m_signed_distance_field[corner_r * ncols + corner_c]; Slic3r::Point pt(m_bbox.min(0) + corner_c * m_resolution, m_bbox.min(1) + corner_r * m_resolution); @@ -1137,9 +1137,9 @@ bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radiu return false; bbox.max(0) /= m_resolution; bbox.max(1) /= m_resolution; - if (bbox.max(0) >= m_cols) + if ((size_t)bbox.max(0) >= m_cols) bbox.max(0) = m_cols - 1; - if (bbox.max(1) >= m_rows) + if ((size_t)bbox.max(1) >= m_rows) bbox.max(1) = m_rows - 1; // Lower boundary, round to grid and test validity. bbox.min(0) -= search_radius; diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index f99ab30c46..7faafdb3e1 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -78,8 +78,8 @@ protected: #endif bool cell_inside_or_crossing(int r, int c) const { - if (r < 0 || r >= m_rows || - c < 0 || c >= m_cols) + if (r < 0 || (size_t)r >= m_rows || + c < 0 || (size_t)c >= m_cols) // The cell is outside the domain. Hoping that the contours were correctly oriented, so // there is a CCW outmost contour so the out of domain cells are outside. return false; diff --git a/src/libslic3r/ExtrusionSimulator.cpp b/src/libslic3r/ExtrusionSimulator.cpp index fcb2fe825a..8c2ab037f7 100644 --- a/src/libslic3r/ExtrusionSimulator.cpp +++ b/src/libslic3r/ExtrusionSimulator.cpp @@ -660,7 +660,7 @@ void gcode_spread_points( for (ExtrusionPoints::const_iterator it = points.begin(); it != points.end(); ++ it) { const V2f ¢er = it->center; const float radius = it->radius; - const float radius2 = radius * radius; + //const float radius2 = radius * radius; const float height_target = it->height; B2f bbox(center - V2f(radius, radius), center + V2f(radius, radius)); B2i bboxi( @@ -774,8 +774,8 @@ void gcode_spread_points( } } #endif - float area_circle_total2 = float(M_PI) * sqr(radius); - float area_err = fabs(area_circle_total2 - area_circle_total) / area_circle_total2; +// float area_circle_total2 = float(M_PI) * sqr(radius); +// float area_err = fabs(area_circle_total2 - area_circle_total) / area_circle_total2; // printf("area_circle_total: %f, %f, %f\n", area_circle_total, area_circle_total2, area_err); float volume_full = float(M_PI) * sqr(radius) * height_target; // if (true) { @@ -905,8 +905,8 @@ void ExtrusionSimulator::set_image_size(const Point &image_size) // printf("Allocating image data, allocated\n"); //FIXME fill the image with red vertical lines. - for (size_t r = 0; r < image_size.y(); ++ r) { - for (size_t c = 0; c < image_size.x(); c += 2) { + for (size_t r = 0; r < size_t(image_size.y()); ++ r) { + for (size_t c = 0; c < size_t(image_size.x()); c += 2) { // Color red pimpl->image_data[r * image_size.x() * 4 + c * 4] = 255; // Opacity full @@ -958,7 +958,7 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const float scalex = float(viewport.size().x()) / float(bbox.size().x()); float scaley = float(viewport.size().y()) / float(bbox.size().y()); float w = scale_(path.width) * scalex; - float h = scale_(path.height) * scalex; + //float h = scale_(path.height) * scalex; w = scale_(path.mm3_per_mm / path.height) * scalex; // printf("scalex: %f, scaley: %f\n", scalex, scaley); // printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y); @@ -993,8 +993,8 @@ void ExtrusionSimulator::evaluate_accumulator(ExtrusionSimulationType simulation for (int r = 0; r < sz.y(); ++r) { for (int c = 0; c < sz.x(); ++c) { float p = 0; - for (int j = 0; j < pimpl->bitmap_oversampled; ++ j) { - for (int i = 0; i < pimpl->bitmap_oversampled; ++ i) { + for (unsigned int j = 0; j < pimpl->bitmap_oversampled; ++ j) { + for (unsigned int i = 0; i < pimpl->bitmap_oversampled; ++ i) { if (pimpl->bitmap[r * pimpl->bitmap_oversampled + j][c * pimpl->bitmap_oversampled + i]) p += 1.f; } diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index c52353b02a..3b9266a0fe 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -130,14 +130,14 @@ static inline Point hilbert_n_to_xy(const size_t n) } } int state = (ndigits & 1) ? 4 : 0; - int dirstate = (ndigits & 1) ? 0 : 4; +// int dirstate = (ndigits & 1) ? 0 : 4; coord_t x = 0; coord_t y = 0; for (int i = (int)ndigits - 1; i >= 0; -- i) { int digit = (n >> (i * 2)) & 3; state += digit; - if (digit != 3) - dirstate = state; // lowest non-3 digit +// if (digit != 3) +// dirstate = state; // lowest non-3 digit x |= digit_to_x[state] << i; y |= digit_to_y[state] << i; state = next_state[state]; diff --git a/src/libslic3r/Fill/FillRectilinear2.cpp b/src/libslic3r/Fill/FillRectilinear2.cpp index 65440d0efe..8aea758860 100644 --- a/src/libslic3r/Fill/FillRectilinear2.cpp +++ b/src/libslic3r/Fill/FillRectilinear2.cpp @@ -287,7 +287,8 @@ public: assert(aoffset1 < 0); assert(aoffset2 < 0); assert(aoffset2 < aoffset1); - bool sticks_removed = remove_sticks(polygons_src); +// bool sticks_removed = + remove_sticks(polygons_src); // if (sticks_removed) printf("Sticks removed!\n"); polygons_outer = offset(polygons_src, aoffset1, ClipperLib::jtMiter, @@ -481,7 +482,7 @@ static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical { // This routine will propose a connecting line even if the connecting perimeter segment intersects // iVertical line multiple times before reaching iIntersectionOther. - if (iIntersectionOther == -1) + if (iIntersectionOther == size_t(-1)) return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED; assert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0)); const SegmentedIntersectionLine &il_this = segs[iVerticalLine]; @@ -858,8 +859,8 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP if (il > ir) // No vertical line intersects this segment. continue; - assert(il >= 0 && il < segs.size()); - assert(ir >= 0 && ir < segs.size()); + assert(il >= 0 && size_t(il) < segs.size()); + assert(ir >= 0 && size_t(ir) < segs.size()); for (int i = il; i <= ir; ++ i) { coord_t this_x = segs[i].pos; assert(this_x == i * line_spacing + x0); @@ -1159,8 +1160,8 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP int iSegAbove = -1; int iSegBelow = -1; { - SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? - SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; +// SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? +// SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; // Does the perimeter intersect the current vertical line above intrsctn? for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) // if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) { diff --git a/src/libslic3r/Fill/FillRectilinear3.cpp b/src/libslic3r/Fill/FillRectilinear3.cpp index dab5842982..078feeae92 100644 --- a/src/libslic3r/Fill/FillRectilinear3.cpp +++ b/src/libslic3r/Fill/FillRectilinear3.cpp @@ -849,7 +849,7 @@ static inline IntersectionTypeOtherVLine intersection_type_on_prev_next_vertical { // This routine will propose a connecting line even if the connecting perimeter segment intersects // iVertical line multiple times before reaching iIntersectionOther. - if (iIntersectionOther == -1) + if (iIntersectionOther == size_t(-1)) return INTERSECTION_TYPE_OTHER_VLINE_UNDEFINED; assert(dir_is_next ? (iVerticalLine + 1 < segs.size()) : (iVerticalLine > 0)); const SegmentedIntersectionLine &il_this = segs[iVerticalLine]; @@ -1284,8 +1284,8 @@ static bool fill_hatching_segments_legacy( int iSegAbove = -1; int iSegBelow = -1; { - SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? - SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; +// SegmentIntersection::SegmentIntersectionType type_crossing = (intrsctn->type == SegmentIntersection::INNER_LOW) ? +// SegmentIntersection::INNER_HIGH : SegmentIntersection::INNER_LOW; // Does the perimeter intersect the current vertical line above intrsctn? for (size_t i = i_intersection + 1; i + 1 < seg.intersections.size(); ++ i) // if (seg.intersections[i].iContour == intrsctn->iContour && seg.intersections[i].type == type_crossing) { diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 4793586e3c..1807eb54f4 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -675,7 +675,7 @@ namespace Slic3r { if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1)) { char error_buf[1024]; - ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), XML_GetCurrentLineNumber(m_xml_parser)); + ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), (int)XML_GetCurrentLineNumber(m_xml_parser)); add_error(error_buf); return false; } @@ -895,7 +895,7 @@ namespace Slic3r { if (!XML_ParseBuffer(m_xml_parser, (int)stat.m_uncomp_size, 1)) { char error_buf[1024]; - ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), XML_GetCurrentLineNumber(m_xml_parser)); + ::sprintf(error_buf, "Error (%s) while parsing xml file at line %d", XML_ErrorString(XML_GetErrorCode(m_xml_parser)), (int)XML_GetCurrentLineNumber(m_xml_parser)); add_error(error_buf); return false; } @@ -1452,7 +1452,7 @@ namespace Slic3r { object->second.metadata.emplace_back(key, value); else if (type == VOLUME_TYPE) { - if (m_curr_config.volume_id < object->second.volumes.size()) + if (size_t(m_curr_config.volume_id) < object->second.volumes.size()) object->second.volumes[m_curr_config.volume_id].metadata.emplace_back(key, value); } else diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index a33d21c9fa..5dced91b8d 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -694,8 +694,8 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) } int done = feof(pFile); if (XML_Parse(parser, buff, len, done) == XML_STATUS_ERROR) { - printf("AMF parser: Parse error at line %ul:\n%s\n", - XML_GetCurrentLineNumber(parser), + printf("AMF parser: Parse error at line %d:\n%s\n", + (int)XML_GetCurrentLineNumber(parser), XML_ErrorString(XML_GetErrorCode(parser))); break; } @@ -753,7 +753,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi if (!XML_ParseBuffer(parser, (int)stat.m_uncomp_size, 1)) { - printf("Error (%s) while parsing xml file at line %d\n", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + printf("Error (%s) while parsing xml file at line %d\n", XML_ErrorString(XML_GetErrorCode(parser)), (int)XML_GetCurrentLineNumber(parser)); close_zip_reader(&archive); return false; } @@ -959,7 +959,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) stream << " 1\n"; stream << " " << ModelVolume::type_to_string(volume->type()) << "\n"; const indexed_triangle_set &its = volume->mesh().its; - for (size_t i = 0; i < (int)its.indices.size(); ++i) { + for (size_t i = 0; i < its.indices.size(); ++i) { stream << " \n"; for (int j = 0; j < 3; ++j) stream << " " << its.indices[i][j] + vertices_offset << "\n"; diff --git a/src/libslic3r/Format/objparser.cpp b/src/libslic3r/Format/objparser.cpp index 88dfae695a..62bcd306ee 100644 --- a/src/libslic3r/Format/objparser.cpp +++ b/src/libslic3r/Format/objparser.cpp @@ -176,8 +176,7 @@ static bool obj_parseline(const char *line, ObjData &data) EATWS(); if (*line == 0) return false; - // number of vertices of this face - int n = 0; + // current vertex to be parsed ObjVertex vertex; char *endptr = 0; @@ -266,7 +265,6 @@ static bool obj_parseline(const char *line, ObjData &data) { // o [object name] EATWS(); - const char *name = line; while (*line != ' ' && *line != '\t' && *line != 0) ++ line; // copy name to line. diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c42669de0d..23a8999c49 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -316,10 +316,10 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) { std::string gcode; - assert(m_layer_idx >= 0 && m_layer_idx <= m_tool_changes.size()); + assert(m_layer_idx >= 0 && size_t(m_layer_idx) <= m_tool_changes.size()); if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { - if (m_layer_idx < m_tool_changes.size()) { - assert(m_tool_change_idx < m_tool_changes[m_layer_idx].size()); + if (m_layer_idx < (int)m_tool_changes.size()) { + assert(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()); gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id); } m_brim_done = true; @@ -1253,7 +1253,7 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id); if (temp_by_gcode >= 0 && temp_by_gcode < 1000) temp = temp_by_gcode; - m_writer.set_temperature(temp_by_gcode, wait, first_printing_extruder_id); + m_writer.set_temperature(temp, wait, first_printing_extruder_id); } else { // Custom G-code does not set the extruder temperature. Do it now. if (print.config().single_extruder_multi_material.value) { @@ -1344,11 +1344,11 @@ void GCode::process_layer( // Check whether it is possible to apply the spiral vase logic for this layer. // Just a reminder: A spiral vase mode is allowed for a single object, single material print only. if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) { - bool enable = (layer.id() > 0 || print.config().brim_width.value == 0.) && (layer.id() >= print.config().skirt_height.value && ! print.has_infinite_skirt()); + bool enable = (layer.id() > 0 || print.config().brim_width.value == 0.) && (layer.id() >= (size_t)print.config().skirt_height.value && ! print.has_infinite_skirt()); if (enable) { for (const LayerRegion *layer_region : layer.regions()) - if (layer_region->region()->config().bottom_solid_layers.value > layer.id() || - layer_region->perimeters.items_count() > 1 || + if (size_t(layer_region->region()->config().bottom_solid_layers.value) > layer.id() || + layer_region->perimeters.items_count() > 1u || layer_region->fills.items_count() > 0) { enable = false; break; @@ -1414,11 +1414,11 @@ void GCode::process_layer( bool extrude_skirt = ! print.skirt().entities.empty() && // Not enough skirt layers printed yet. - (m_skirt_done.size() < print.config().skirt_height.value || print.has_infinite_skirt()) && + (m_skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt()) && // This print_z has not been extruded yet (m_skirt_done.empty() ? 0. : m_skirt_done.back()) < print_z - EPSILON && // and this layer is the 1st layer, or it is an object layer, or it is a raft layer. - (first_layer || object_layer != nullptr || support_layer->id() < m_config.raft_layers.value); + (first_layer || object_layer != nullptr || support_layer->id() < (size_t)m_config.raft_layers.value); std::map> skirt_loops_per_extruder; coordf_t skirt_height = 0.; if (extrude_skirt) { @@ -2067,19 +2067,18 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Retrieve the last start position for this object. float last_pos_weight = 1.f; - switch (seam_position) { - case spAligned: + + if (seam_position == spAligned) { // Seam is aligned to the seam at the preceding layer. if (m_layer != NULL && m_seam_position.count(m_layer->object()) > 0) { last_pos = m_seam_position[m_layer->object()]; last_pos_weight = 1.f; } - break; - case spRear: + } + else if (seam_position == spRear) { last_pos = m_layer->object()->bounding_box().center(); last_pos(1) += coord_t(3. * m_layer->object()->bounding_box().radius()); last_pos_weight = 5.f; - break; } // Insert a projection of last_pos into the polygon. @@ -2088,7 +2087,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou Points::iterator it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); last_pos_proj_idx = it - polygon.points.begin(); } - Point last_pos_proj = polygon.points[last_pos_proj_idx]; + // Parametrize the polygon by its length. std::vector lengths = polygon_parameter_by_length(polygon); @@ -2098,7 +2097,6 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. const float penaltyConvexVertex = 1.f; const float penaltyFlatSurface = 5.f; - const float penaltySeam = 1.3f; const float penaltyOverhangHalf = 10.f; // Penalty for visible seams. for (size_t i = 0; i < polygon.points.size(); ++ i) { @@ -2143,10 +2141,14 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // Signed distance is positive outside the object, negative inside the object. // The point is considered at an overhang, if it is more than nozzle radius // outside of the lower layer contour. - bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); + #ifdef NDEBUG // to suppress unused variable warning in release mode + (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); + #else + bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); + #endif // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, // then the signed distnace shall always be known. - assert(found); + assert(found); penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); } } diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 552fbf88c8..1aa15ef331 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -672,7 +672,7 @@ std::string CoolingBuffer::apply_layer_cooldown( #define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; - if (layer_id >= EXTRUDER_CONFIG(disable_fan_first_layers)) { + if (layer_id >= (size_t)EXTRUDER_CONFIG(disable_fan_first_layers)) { int max_fan_speed = EXTRUDER_CONFIG(max_fan_speed); float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time)); float fan_below_layer_time = float(EXTRUDER_CONFIG(fan_below_layer_time)); diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp index 8eba6801e4..4b9604075d 100644 --- a/src/libslic3r/GCode/PreviewData.cpp +++ b/src/libslic3r/GCode/PreviewData.cpp @@ -404,6 +404,8 @@ std::string GCodePreviewData::get_legend_title() const return L("Tool"); case Extrusion::ColorPrint: return L("Color Print"); + case Extrusion::Num_View_Types: + break; // just to supress warning about non-handled value } return ""; @@ -505,6 +507,8 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: } break; } + case Extrusion::Num_View_Types: + break; // just to supress warning about non-handled value } return items; diff --git a/src/libslic3r/GCode/SpiralVase.cpp b/src/libslic3r/GCode/SpiralVase.cpp index 8e8ae3075c..a4ae42b318 100644 --- a/src/libslic3r/GCode/SpiralVase.cpp +++ b/src/libslic3r/GCode/SpiralVase.cpp @@ -24,7 +24,7 @@ std::string SpiralVase::process_layer(const std::string &gcode) // Get total XY length for this layer by summing all extrusion moves. float total_layer_length = 0; float layer_height = 0; - float z; + float z = 0.f; bool set_z = false; { diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index e25ad91fe4..f10b45723a 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -324,9 +324,8 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ m_layer_tools[j].has_wipe_tower = true; } else { LayerTools <_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new); - LayerTools <_prev = m_layer_tools[j - 1]; LayerTools <_next = m_layer_tools[j + 1]; - assert(! lt_prev.extruders.empty() && ! lt_next.extruders.empty()); + assert(! m_layer_tools[j - 1].extruders.empty() && ! lt_next.extruders.empty()); // FIXME: Following assert tripped when running combine_infill.t. I decided to comment it out for now. // If it is a bug, it's likely not critical, because this code is unchanged for a long time. It might // still be worth looking into it more and decide if it is a bug or an obsolete assert. @@ -495,9 +494,6 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int if (!is_overriddable(*fill, print.config(), *object, region)) continue; - // What extruder would this normally be printed with? - unsigned int correct_extruder = Print::get_extruder(*fill, region); - if (volume_to_wipe<=0) continue; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index accb4e2863..cc8a86a96c 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -912,7 +912,7 @@ MedialAxis::build(ThickPolylines* polylines) } */ - typedef const VD::vertex_type vert_t; + //typedef const VD::vertex_type vert_t; typedef const VD::edge_type edge_t; // collect valid edges (i.e. prune those not belonging to MAT) diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 19fbb3bb6e..caf8dc20f6 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -201,7 +201,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) size_t n_groups = 0; for (size_t i = 0; i < bridges.size(); ++ i) { // A grup id for this bridge. - size_t group_id = (bridge_group[i] == -1) ? (n_groups ++) : bridge_group[i]; + size_t group_id = (bridge_group[i] == size_t(-1)) ? (n_groups ++) : bridge_group[i]; bridge_group[i] = group_id; // For all possibly overlaping bridges: for (size_t j = i + 1; j < bridges.size(); ++ j) { @@ -210,7 +210,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) if (intersection(bridges_grown[i], bridges_grown[j], false).empty()) continue; // The two bridge regions intersect. Give them the same group id. - if (bridge_group[j] != -1) { + if (bridge_group[j] != size_t(-1)) { // The j'th bridge has been merged with some other bridge before. size_t group_id_new = bridge_group[j]; for (size_t k = 0; k < j; ++ k) diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 02f1cb7c2b..baa04795ad 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -22,7 +22,6 @@ Linef3 transform(const Linef3& line, const Transform3d& t) bool Line::intersection_infinite(const Line &other, Point* point) const { Vec2d a1 = this->a.cast(); - Vec2d a2 = other.a.cast(); Vec2d v12 = (other.a - this->a).cast(); Vec2d v1 = (this->b - this->a).cast(); Vec2d v2 = (other.b - other.a).cast(); diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp index 198c39f311..dd34438799 100644 --- a/src/libslic3r/MotionPlanner.cpp +++ b/src/libslic3r/MotionPlanner.cpp @@ -332,7 +332,7 @@ Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) c queue.pop(); map_node_to_queue_id[u] = size_t(-1); // Stop searching if we reached our destination. - if (u == node_end) + if (size_t(u) == node_end) break; // Visit each edge starting at node u. for (const Neighbor& neighbor : m_adjacency_list[u]) diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index de8aeeb2ab..4dac192adc 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -175,7 +175,7 @@ void PerimeterGenerator::process() const PerimeterGeneratorLoop &loop = contours_d[i]; // find the contour loop that contains it for (int t = d - 1; t >= 0; -- t) { - for (int j = 0; j < contours[t].size(); ++ j) { + for (size_t j = 0; j < contours[t].size(); ++ j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); @@ -397,7 +397,7 @@ static inline ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyli pp.push_back(line.b); width.push_back(line.b_width); - assert(pp.size() == segments + 1); + assert(pp.size() == segments + 1u); assert(width.size() == segments*2); } diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index ae66178aa3..2bfe9b7454 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -521,7 +521,6 @@ namespace client static void regex_op(expr &lhs, boost::iterator_range &rhs, char op) { const std::string *subject = nullptr; - const std::string *mask = nullptr; if (lhs.type == TYPE_STRING) { // One type is string, the other could be converted to string. subject = &lhs.s(); @@ -563,7 +562,6 @@ namespace client static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2) { - bool value = false; if (lhs.type != TYPE_BOOL) lhs.throw_exception("Not a boolean expression"); if (lhs.b()) @@ -975,7 +973,7 @@ namespace client // depending on the context->just_boolean_expression flag. This way a single static expression parser // could serve both purposes. start = eps[px::bind(&MyContext::evaluate_full_macro, _r1, _a)] > - ( eps(_a==true) > text_block(_r1) [_val=_1] + ( (eps(_a==true) > text_block(_r1) [_val=_1]) | conditional_expression(_r1) [ px::bind(&expr::evaluate_boolean_to_string, _1, _val) ] ) > eoi; start.name("start"); @@ -1245,7 +1243,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co std::string::const_iterator end = templ.end(); // Accumulator for the processed template. std::string output; - bool res = phrase_parse(iter, end, macro_processor_instance(&context), space, output); + phrase_parse(iter, end, macro_processor_instance(&context), space, output); if (!context.error_message.empty()) { if (context.error_message.back() != '\n' && context.error_message.back() != '\r') context.error_message += '\n'; diff --git a/src/libslic3r/PolylineCollection.cpp b/src/libslic3r/PolylineCollection.cpp index 1304161c3f..0c66c371a9 100644 --- a/src/libslic3r/PolylineCollection.cpp +++ b/src/libslic3r/PolylineCollection.cpp @@ -61,7 +61,7 @@ Polylines PolylineCollection::_chained_path_from( while (! endpoints.empty()) { // find nearest point int endpoint_index = nearest_point_index(endpoints, start_near, no_reverse); - assert(endpoint_index >= 0 && endpoint_index < endpoints.size() * 2); + assert(endpoint_index >= 0 && size_t(endpoint_index) < endpoints.size() * 2); if (move_from_src) { retval.push_back(std::move(src[endpoints[endpoint_index/2].idx])); } else { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index f9129f15a1..876523bf52 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -18,7 +18,7 @@ #include #include -//! macro used to mark string used at localization, +//! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) @@ -1116,7 +1116,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co region_id = map_volume_to_region[volume_id]; // Assign volume to a region. if (fresh) { - if (region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty()) + if ((size_t)region_id >= print_object.region_volumes.size() || print_object.region_volumes[region_id].empty()) ++ m_regions[region_id]->m_refcnt; print_object.add_region_volume(region_id, volume_id); } @@ -1259,11 +1259,10 @@ std::string Print::validate() const if (has_custom_layering) { const std::vector &layer_height_profile_tallest = layer_height_profiles[tallest_object_idx]; for (size_t idx_object = 0; idx_object < m_objects.size(); ++ idx_object) { - const PrintObject *object = m_objects[idx_object]; const std::vector &layer_height_profile = layer_height_profiles[idx_object]; bool failed = false; if (layer_height_profile_tallest.size() >= layer_height_profile.size()) { - int i = 0; + size_t i = 0; while (i < layer_height_profile.size() && i < layer_height_profile_tallest.size()) { if (std::abs(layer_height_profile_tallest[i] - layer_height_profile[i])) { failed = true; @@ -1628,7 +1627,7 @@ void Print::_make_skirt() } // Number of skirt loops per skirt layer. - int n_skirts = m_config.skirts.value; + size_t n_skirts = m_config.skirts.value; if (this->has_infinite_skirt() && n_skirts == 0) n_skirts = 1; @@ -1640,7 +1639,7 @@ void Print::_make_skirt() // Draw outlines from outside to inside. // Loop while we have less skirts than required or any extruder hasn't reached the min length if any. std::vector extruded_length(extruders.size(), 0.); - for (int i = n_skirts, extruder_idx = 0; i > 0; -- i) { + for (size_t i = n_skirts, extruder_idx = 0; i > 0; -- i) { this->throw_if_canceled(); // Offset the skirt outside. distance += coord_t(scale_(spacing)); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 53d6d692db..42430f59e5 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -120,7 +120,7 @@ public: void clear_support_layers(); SupportLayer* get_support_layer(int idx) { return m_support_layers[idx]; } SupportLayer* add_support_layer(int id, coordf_t height, coordf_t print_z); - SupportLayerPtrs::const_iterator insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z); + SupportLayerPtrs::const_iterator insert_support_layer(SupportLayerPtrs::const_iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z); void delete_support_layer(int idx); // Initialize the layer_height_profile from the model_object's layer_height_profile, from model_object's layer height table, or from slicing parameters. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 89e21934a1..fbf3c7ab6b 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -36,7 +36,6 @@ PrintConfigDef::PrintConfigDef() void PrintConfigDef::init_common_params() { - t_optiondef_map &Options = this->options; ConfigOptionDef* def; def = this->add("printer_technology", coEnum); @@ -102,7 +101,6 @@ void PrintConfigDef::init_common_params() void PrintConfigDef::init_fff_params() { - t_optiondef_map &Options = this->options; ConfigOptionDef* def; // Maximum extruder temperature, bumped to 1500 to support printing of glass. @@ -1085,16 +1083,16 @@ void PrintConfigDef::init_fff_params() // Add the machine feedrate limits for XYZE axes. (M203) def = this->add("machine_max_feedrate_" + axis.name, coFloats); def->full_label = (boost::format("Maximum feedrate %1%") % axis_upper).str(); - L("Maximum feedrate X"); - L("Maximum feedrate Y"); - L("Maximum feedrate Z"); - L("Maximum feedrate E"); + (void)L("Maximum feedrate X"); + (void)L("Maximum feedrate Y"); + (void)L("Maximum feedrate Z"); + (void)L("Maximum feedrate E"); def->category = L("Machine limits"); def->tooltip = (boost::format("Maximum feedrate of the %1% axis") % axis_upper).str(); - L("Maximum feedrate of the X axis"); - L("Maximum feedrate of the Y axis"); - L("Maximum feedrate of the Z axis"); - L("Maximum feedrate of the E axis"); + (void)L("Maximum feedrate of the X axis"); + (void)L("Maximum feedrate of the Y axis"); + (void)L("Maximum feedrate of the Z axis"); + (void)L("Maximum feedrate of the E axis"); def->sidetext = L("mm/s"); def->min = 0; def->width = machine_limits_opt_width; @@ -1103,16 +1101,16 @@ void PrintConfigDef::init_fff_params() // Add the machine acceleration limits for XYZE axes (M201) def = this->add("machine_max_acceleration_" + axis.name, coFloats); def->full_label = (boost::format("Maximum acceleration %1%") % axis_upper).str(); - L("Maximum acceleration X"); - L("Maximum acceleration Y"); - L("Maximum acceleration Z"); - L("Maximum acceleration E"); + (void)L("Maximum acceleration X"); + (void)L("Maximum acceleration Y"); + (void)L("Maximum acceleration Z"); + (void)L("Maximum acceleration E"); def->category = L("Machine limits"); def->tooltip = (boost::format("Maximum acceleration of the %1% axis") % axis_upper).str(); - L("Maximum acceleration of the X axis"); - L("Maximum acceleration of the Y axis"); - L("Maximum acceleration of the Z axis"); - L("Maximum acceleration of the E axis"); + (void)L("Maximum acceleration of the X axis"); + (void)L("Maximum acceleration of the Y axis"); + (void)L("Maximum acceleration of the Z axis"); + (void)L("Maximum acceleration of the E axis"); def->sidetext = L("mm/s²"); def->min = 0; def->width = machine_limits_opt_width; @@ -1121,16 +1119,16 @@ void PrintConfigDef::init_fff_params() // Add the machine jerk limits for XYZE axes (M205) def = this->add("machine_max_jerk_" + axis.name, coFloats); def->full_label = (boost::format("Maximum jerk %1%") % axis_upper).str(); - L("Maximum jerk X"); - L("Maximum jerk Y"); - L("Maximum jerk Z"); - L("Maximum jerk E"); + (void)L("Maximum jerk X"); + (void)L("Maximum jerk Y"); + (void)L("Maximum jerk Z"); + (void)L("Maximum jerk E"); def->category = L("Machine limits"); def->tooltip = (boost::format("Maximum jerk of the %1% axis") % axis_upper).str(); - L("Maximum jerk of the X axis"); - L("Maximum jerk of the Y axis"); - L("Maximum jerk of the Z axis"); - L("Maximum jerk of the E axis"); + (void)L("Maximum jerk of the X axis"); + (void)L("Maximum jerk of the Y axis"); + (void)L("Maximum jerk of the Z axis"); + (void)L("Maximum jerk of the E axis"); def->sidetext = L("mm/s"); def->min = 0; def->width = machine_limits_opt_width; @@ -2228,7 +2226,6 @@ void PrintConfigDef::init_fff_params() void PrintConfigDef::init_sla_params() { - t_optiondef_map &Options = this->options; ConfigOptionDef* def; // SLA Printer settings diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d99aceabfe..4b77d05615 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -18,7 +18,7 @@ #include -//! macro used to mark string used at localization, +//! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) @@ -430,7 +430,7 @@ SupportLayer* PrintObject::add_support_layer(int id, coordf_t height, coordf_t p return m_support_layers.back(); } -SupportLayerPtrs::const_iterator PrintObject::insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z) +SupportLayerPtrs::const_iterator PrintObject::insert_support_layer(SupportLayerPtrs::const_iterator pos, size_t id, coordf_t height, coordf_t print_z, coordf_t slice_z) { return m_support_layers.insert(pos, new SupportLayer(id, this, height, print_z, slice_z)); } @@ -620,7 +620,7 @@ void PrintObject::detect_surfaces_type() // should be visible. bool interface_shells = m_config.interface_shells.value; - for (int idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { + for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << idx_region << " in parallel - start"; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (Layer *layer : m_layers) @@ -806,8 +806,6 @@ void PrintObject::process_external_surfaces() BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..." << log_memory_info(); for (size_t region_id = 0; region_id < this->region_volumes.size(); ++region_id) { - const PrintRegion ®ion = *m_print->regions()[region_id]; - BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), @@ -1028,7 +1026,6 @@ void PrintObject::discover_vertical_shells() bool hole_first = true; for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) if (n >= 0 && n < (int)m_layers.size()) { - Layer &neighbor_layer = *m_layers[n]; const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[n]; if (hole_first) { hole_first = false; @@ -2154,7 +2151,7 @@ void PrintObject::discover_horizontal_shells() BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()"; for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { - for (int i = 0; i < int(m_layers.size()); ++ i) { + for (size_t i = 0; i < m_layers.size(); ++ i) { m_print->throw_if_canceled(); LayerRegion *layerm = m_layers[i]->regions()[region_id]; const PrintRegionConfig ®ion_config = layerm->region()->config(); @@ -2171,7 +2168,7 @@ void PrintObject::discover_horizontal_shells() if (region_config.ensure_vertical_shell_thickness.value) continue; - for (int idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { + for (size_t idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { m_print->throw_if_canceled(); SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge; // Find slices of current type for current layer. @@ -2345,7 +2342,7 @@ void PrintObject::combine_infill() // Work on each region separately. for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { const PrintRegion *region = this->print()->regions()[region_id]; - const int every = region->config().infill_every_layers.value; + const size_t every = region->config().infill_every_layers.value; if (every < 2 || region->config().fill_density == 0.) continue; // Limit the number of combined layers to the maximum height allowed by this regions' nozzle. diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index 0a2537b919..91b7dfe5d1 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -59,8 +59,6 @@ SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3 void SLAAutoSupports::project_onto_mesh(std::vector& points) const { // The function makes sure that all the points are really exactly placed on the mesh. - igl::Hit hit_up{0, 0, 0.f, 0.f, 0.f}; - igl::Hit hit_down{0, 0, 0.f, 0.f, 0.f}; // Use a reasonable granularity to account for the worker thread synchronization cost. tbb::parallel_for(tbb::blocked_range(0, points.size(), 64), @@ -140,7 +138,6 @@ static std::vector make_layers( SLAAutoSupports::MyLayer &layer_above = layers[layer_id]; SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1]; //FIXME WTF? - const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0])); const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]); const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle))); @@ -212,7 +209,7 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: for (Structure &top : layer_top->islands) for (Structure::Link &bottom_link : top.islands_below) { Structure &bottom = *bottom_link.island; - float centroids_dist = (bottom.centroid - top.centroid).norm(); + //float centroids_dist = (bottom.centroid - top.centroid).norm(); // Penalization resulting from centroid offset: // bottom.supports_force *= std::min(1.f, 1.f - std::min(1.f, (1600.f * layer_height) * centroids_dist * centroids_dist / bottom.area)); float &support_force = support_force_bottom[&bottom - layer_bottom->islands.data()]; @@ -239,7 +236,7 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: // s.supports_force_inherited /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area); s.supports_force_inherited /= std::max(1.f, 0.17f * (s.overhangs_area) / s.area); - float force_deficit = s.support_force_deficit(m_config.tear_pressure()); + //float force_deficit = s.support_force_deficit(m_config.tear_pressure()); if (s.islands_below.empty()) { // completely new island - needs support no doubt uniformly_cover({ *s.polygon }, s, point_grid, true); } else if (! s.dangling_areas.empty()) { @@ -380,7 +377,7 @@ static inline std::vector poisson_disk_from_samples(const std::vectorinvalidate_all_steps()); m_objects = print_objects_new; // Delete the PrintObjects marked as Unknown or Deleted. - bool deleted_objects = false; for (auto &pos : print_object_status) if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { update_apply_status(pos.print_object->invalidate_all_steps()); delete pos.print_object; - deleted_objects = true; } if (new_objects) update_apply_status(false); diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index e1bb4b3130..5e73c88118 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -187,7 +187,6 @@ std::vector layer_height_profile_from_ranges( coordf_t hi = it_range->first.second; coordf_t height = it_range->second; coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; - coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1]; if (lo > last_z + EPSILON) { // Insert a step of normal layer height. layer_height_profile.push_back(last_z); @@ -203,7 +202,6 @@ std::vector layer_height_profile_from_ranges( } coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; - coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1]; if (last_z < slicing_params.object_print_z_height()) { // Insert a step of normal layer height up to the object top. layer_height_profile.push_back(last_z); @@ -245,7 +243,6 @@ std::vector layer_height_profile_adaptive( } coordf_t slice_z = slicing_params.first_object_layer_height; coordf_t height = slicing_params.first_object_layer_height; - coordf_t cusp_height = 0.; int current_facet = 0; while ((slice_z - height) <= slicing_params.object_print_z_height()) { height = 999; @@ -401,7 +398,6 @@ void adjust_layer_height_profile( } // Adjust height by layer_thickness_delta. coordf_t weight = std::abs(zz - z) < 0.5 * band_width ? (0.5 + 0.5 * cos(2. * M_PI * (zz - z) / band_width)) : 0.; - coordf_t height_new = height; switch (action) { case LAYER_HEIGHT_EDIT_ACTION_INCREASE: case LAYER_HEIGHT_EDIT_ACTION_DECREASE: diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index fde35ca1e4..c1eb2f4d8e 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -361,17 +361,17 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare()); int layer_id = 0; assert(object.support_layers().empty()); - for (int i = 0; i < int(layers_sorted.size());) { + for (size_t i = 0; i < layers_sorted.size();) { // Find the last layer with roughly the same print_z, find the minimum layer height of all. // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. - int j = i + 1; + size_t j = i + 1; coordf_t zmax = layers_sorted[i]->print_z + EPSILON; for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; // Assign an average print_z to the set of layers with nearly equal print_z. coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); coordf_t height_min = layers_sorted[i]->height; bool empty = true; - for (int u = i; u < j; ++u) { + for (size_t u = i; u < j; ++u) { MyLayer &layer = *layers_sorted[u]; if (! layer.polygons.empty()) empty = false; @@ -1042,7 +1042,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ float fw = float(layerm->flow(frExternalPerimeter).scaled_width()); no_interface_offset = (no_interface_offset == 0.f) ? fw : std::min(no_interface_offset, fw); float lower_layer_offset = - (layer_id < m_object_config->support_material_enforce_layers.value) ? + (layer_id < (size_t)m_object_config->support_material_enforce_layers.value) ? // Enforce a full possible support, ignore the overhang angle. 0.f : (threshold_rad > 0. ? @@ -1352,7 +1352,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ { // Find the span of layers, which are to be printed at the first layer height. int j = 0; - for (; j < contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); + for (; j < (int)contact_out.size() && contact_out[j]->print_z < m_slicing_params.first_print_layer_height + this->m_support_layer_height_min - EPSILON; ++ j); if (j > 0) { // Merge the contact_out layers (0) to (j - 1) into the contact_out[0]. MyLayer &dst = *contact_out.front(); @@ -1377,7 +1377,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Find the span of layers closer than m_support_layer_height_min. int j = i + 1; coordf_t zmax = contact_out[i]->print_z + m_support_layer_height_min + EPSILON; - for (; j < contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; + for (; j < (int)contact_out.size() && contact_out[j]->print_z < zmax; ++ j) ; if (i + 1 < j) { // Merge the contact_out layers (i + 1) to (j - 1) into the contact_out[i]. MyLayer &dst = *contact_out[i]; @@ -1395,7 +1395,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ contact_out[k] = contact_out[i]; i = j; } - if (k < contact_out.size()) + if (k < (int)contact_out.size()) contact_out.erase(contact_out.begin() + k, contact_out.end()); } @@ -2566,11 +2566,11 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const { // make more loops Polygons loop_polygons = loops0; - for (size_t i = 1; i < n_contact_loops; ++ i) + for (int i = 1; i < n_contact_loops; ++ i) polygons_append(loop_polygons, offset2( loops0, - - int(i) * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), + - i * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), 0.5f * flow.scaled_spacing())); // Clip such loops to the side oriented towards the object. // Collect split points, so they will be recognized after the clipping. diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 3482f708ad..5197637310 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -414,13 +414,13 @@ std::string format_memsize_MB(size_t n) scale *= 1000; } char buf[8]; - sprintf(buf, "%d", n); + sprintf(buf, "%d", (int)n); out = buf; while (scale != 1) { scale /= 1000; n = n2 / scale; n2 = n2 % scale; - sprintf(buf, ",%03d", n); + sprintf(buf, ",%03d", (int)n); out += buf; } return out + "MB"; From 84027a3d3bbf48d61fc238621b1b0b16004f3280 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 25 Jun 2019 14:50:27 +0200 Subject: [PATCH 196/627] avrdude: warnings cleanup --- src/avrdude/arduino.c | 13 ++- src/avrdude/avr.c | 24 ++-- src/avrdude/avr910.c | 2 +- src/avrdude/avrdude-slic3r.cpp | 6 +- src/avrdude/avrdude.h | 2 + src/avrdude/avrpart.c | 4 +- src/avrdude/buspirate.c | 4 +- src/avrdude/butterfly.c | 2 +- src/avrdude/config.c | 6 +- src/avrdude/config_gram.c | 2 +- src/avrdude/config_gram.y | 2 +- src/avrdude/fileio.c | 43 +++---- src/avrdude/lists.c | 12 +- src/avrdude/main.c | 6 +- src/avrdude/pindefs.c | 2 +- src/avrdude/ser_win32.c | 12 +- src/avrdude/serbb_win32.c | 8 +- src/avrdude/stk500.c | 12 +- src/avrdude/stk500v2.c | 205 ++++++++++++++++++--------------- src/avrdude/term.c | 26 ++--- 20 files changed, 206 insertions(+), 187 deletions(-) diff --git a/src/avrdude/arduino.c b/src/avrdude/arduino.c index 53e5ed8225..e6008adeb8 100644 --- a/src/avrdude/arduino.c +++ b/src/avrdude/arduino.c @@ -41,6 +41,7 @@ static int arduino_read_sig_bytes(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m) { unsigned char buf[32]; + (void)p; /* Signature byte reads are always 3 bytes. */ @@ -83,9 +84,9 @@ static int arduino_read_sig_bytes(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m) static int prusa_init_external_flash(PROGRAMMER * pgm) { // Note: send/receive as in _the firmare_ send & receives - const char entry_magic_send [] = "start\n"; - const char entry_magic_receive[] = "w25x20cl_enter\n"; - const char entry_magic_cfm [] = "w25x20cl_cfm\n"; + const char entry_magic_send[] = "start\n"; + const unsigned char entry_magic_receive[] = "w25x20cl_enter\n"; + const char entry_magic_cfm[] = "w25x20cl_cfm\n"; const size_t buffer_len = 32; // Should be large enough for the above messages int res; @@ -94,7 +95,7 @@ static int prusa_init_external_flash(PROGRAMMER * pgm) // 1. receive the "start" command recv_size = sizeof(entry_magic_send) - 1; - res = serial_recv(&pgm->fd, buffer, recv_size); + res = serial_recv(&pgm->fd, (unsigned char *)buffer, recv_size); if (res < 0) { avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer did not boot up on time or serial communication failed\n", progname); return -1; @@ -111,7 +112,7 @@ static int prusa_init_external_flash(PROGRAMMER * pgm) // 3. Receive the entry confirmation command recv_size = sizeof(entry_magic_cfm) - 1; - res = serial_recv(&pgm->fd, buffer, recv_size); + res = serial_recv(&pgm->fd, (unsigned char *)buffer, recv_size); if (res < 0) { avrdude_message(MSG_INFO, "%s: prusa_init_external_flash(): MK3 printer did not boot up on time or serial communication failed\n", progname); return -1; @@ -142,7 +143,7 @@ static int arduino_open(PROGRAMMER * pgm, char * port) // Sometimes there may be line noise generating input on the printer's USB-to-serial IC // Here we try to clean its input buffer with a sequence of newlines (a minimum of 9 is needed): - const char cleanup_newlines[] = "\n\n\n\n\n\n\n\n\n\n"; + const unsigned char cleanup_newlines[] = "\n\n\n\n\n\n\n\n\n\n"; if (serial_send(&pgm->fd, cleanup_newlines, sizeof(cleanup_newlines) - 1) < 0) { return -1; } diff --git a/src/avrdude/avr.c b/src/avrdude/avr.c index 73dcaf4ffe..defae75d50 100644 --- a/src/avrdude/avr.c +++ b/src/avrdude/avr.c @@ -341,7 +341,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, avr_tpi_setup_rw(pgm, mem, 0, TPI_NVMCMD_NO_OPERATION); /* load bytes */ - for (lastaddr = i = 0; i < mem->size; i++) { + for (lastaddr = i = 0; i < (unsigned)mem->size; i++) { RETURN_IF_CANCEL(); if (vmem == NULL || (vmem->tags[i] & TAG_ALLOCATED) != 0) @@ -374,7 +374,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, /* quickly scan number of pages to be written to first */ for (pageaddr = 0, npages = 0; - pageaddr < mem->size; + pageaddr < (unsigned)mem->size; pageaddr += mem->page_size) { /* check whether this page must be read */ for (i = pageaddr; @@ -391,7 +391,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, } for (pageaddr = 0, failure = 0, nread = 0; - !failure && pageaddr < mem->size; + !failure && pageaddr < (unsigned)mem->size; pageaddr += mem->page_size) { RETURN_IF_CANCEL(); /* check whether this page must be read */ @@ -437,7 +437,7 @@ int avr_read(PROGRAMMER * pgm, AVRPART * p, char * memtype, } } - for (i=0; i < mem->size; i++) { + for (i = 0; i < (unsigned)mem->size; i++) { RETURN_IF_CANCEL(); if (vmem == NULL || (vmem->tags[i] & TAG_ALLOCATED) != 0) @@ -634,18 +634,18 @@ int avr_write_byte_default(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, writeop = mem->op[AVR_OP_WRITE_HI]; else writeop = mem->op[AVR_OP_WRITE_LO]; - caddr = addr / 2; + caddr = (unsigned short)(addr / 2); } else if (mem->paged && mem->op[AVR_OP_LOADPAGE_LO]) { if (addr & 0x01) writeop = mem->op[AVR_OP_LOADPAGE_HI]; else writeop = mem->op[AVR_OP_LOADPAGE_LO]; - caddr = addr / 2; + caddr = (unsigned short)(addr / 2); } else { writeop = mem->op[AVR_OP_WRITE]; - caddr = addr; + caddr = (unsigned short)addr; } if (writeop == NULL) { @@ -723,7 +723,7 @@ int avr_write_byte_default(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, gettimeofday (&tv, NULL); prog_time = (tv.tv_sec * 1000000) + tv.tv_usec; } while ((r != data) && - ((prog_time-start_time) < mem->max_write_delay)); + ((prog_time - start_time) < (unsigned long)mem->max_write_delay)); } /* @@ -878,7 +878,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size, } /* write words, low byte first */ - for (lastaddr = i = 0; i < wsize; i += 2) { + for (lastaddr = i = 0; i < (unsigned)wsize; i += 2) { RETURN_IF_CANCEL(); if ((m->tags[i] & TAG_ALLOCATED) != 0 || (m->tags[i + 1] & TAG_ALLOCATED) != 0) { @@ -915,7 +915,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size, /* quickly scan number of pages to be written to first */ for (pageaddr = 0, npages = 0; - pageaddr < wsize; + pageaddr < (unsigned)wsize; pageaddr += m->page_size) { /* check whether this page must be written to */ for (i = pageaddr; @@ -928,7 +928,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size, } for (pageaddr = 0, failure = 0, nwritten = 0; - !failure && pageaddr < wsize; + !failure && pageaddr < (unsigned)wsize; pageaddr += m->page_size) { RETURN_IF_CANCEL(); /* check whether this page must be written to */ @@ -968,7 +968,7 @@ int avr_write(PROGRAMMER * pgm, AVRPART * p, char * memtype, int size, page_tainted = 0; flush_page = 0; - for (i=0; ibuf[i]; report_progress(i, wsize, NULL); diff --git a/src/avrdude/avr910.c b/src/avrdude/avr910.c index aa5cc07a9d..17a4ab69fd 100644 --- a/src/avrdude/avr910.c +++ b/src/avrdude/avr910.c @@ -676,7 +676,7 @@ static int avr910_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, avr910_set_addr(pgm, addr / rd_size); while (addr < max_addr) { - if ((max_addr - addr) < blocksize) { + if ((max_addr - addr) < (unsigned)blocksize) { blocksize = max_addr - addr; } cmd[1] = (blocksize >> 8) & 0xff; diff --git a/src/avrdude/avrdude-slic3r.cpp b/src/avrdude/avrdude-slic3r.cpp index 0140d93ed8..7eff436e2e 100644 --- a/src/avrdude/avrdude-slic3r.cpp +++ b/src/avrdude/avrdude-slic3r.cpp @@ -93,7 +93,7 @@ void AvrDude::priv::unset_handlers() int AvrDude::priv::run_one(const std::vector &args) { - std::vector c_args {{ const_cast(PACKAGE) }}; + std::vector c_args { const_cast(PACKAGE) }; std::string command_line { PACKAGE }; for (const auto &arg : args) { @@ -105,7 +105,7 @@ int AvrDude::priv::run_one(const std::vector &args) { HandlerGuard guard(*this); - message_fn(command_line.c_str(), command_line.size()); + message_fn(command_line.c_str(), (unsigned)command_line.size()); const auto res = ::avrdude_main(static_cast(c_args.size()), c_args.data()); @@ -200,7 +200,7 @@ AvrDude::Ptr AvrDude::run() auto &message_fn = self->p->message_fn; if (message_fn) { message_fn(msg, sizeof(msg)); - message_fn(what, std::strlen(what)); + message_fn(what, (unsigned)std::strlen(what)); message_fn("\n", 1); } diff --git a/src/avrdude/avrdude.h b/src/avrdude/avrdude.h index ff464f46f2..bc784cec6d 100644 --- a/src/avrdude/avrdude.h +++ b/src/avrdude/avrdude.h @@ -64,6 +64,8 @@ int avrdude_main(int argc, char * argv []); #include #include +#define strdup _strdup + #ifdef UNICODE #error "UNICODE should not be defined for avrdude bits on Windows" #endif diff --git a/src/avrdude/avrpart.c b/src/avrdude/avrpart.c index d0bb951ee8..1c7d6af001 100644 --- a/src/avrdude/avrpart.c +++ b/src/avrdude/avrpart.c @@ -358,7 +358,7 @@ AVRMEM * avr_locate_mem(AVRPART * p, char * desc) int matches; int l; - l = strlen(desc); + l = (int)strlen(desc); matches = 0; match = NULL; for (ln=lfirst(p->mem); ln; ln=lnext(ln)) { @@ -662,7 +662,7 @@ void avr_display(FILE * f, AVRPART * p, const char * prefix, int verbose) prefix); px = prefix; - i = strlen(prefix) + 5; + i = (int)strlen(prefix) + 5; buf = (char *)malloc(i); if (buf == NULL) { /* ugh, this is not important enough to bail, just ignore it */ diff --git a/src/avrdude/buspirate.c b/src/avrdude/buspirate.c index 5875d42833..dc8c68fbeb 100644 --- a/src/avrdude/buspirate.c +++ b/src/avrdude/buspirate.c @@ -128,7 +128,7 @@ static int buspirate_recv_bin(struct programmer_t *pgm, unsigned char *buf, size avrdude_message(MSG_DEBUG, "%s: buspirate_recv_bin():\n", progname); dump_mem(MSG_DEBUG, buf, len); - return len; + return (int)len; } static int buspirate_expect_bin(struct programmer_t *pgm, @@ -249,7 +249,7 @@ static int buspirate_send(struct programmer_t *pgm, const char *str) static int buspirate_is_prompt(const char *str) { - int strlen_str = strlen(str); + int strlen_str = (int)strlen(str); /* Prompt ends with '>' or '> ' * all other input probably ends with '\n' */ return (str[strlen_str - 1] == '>' || str[strlen_str - 2] == '>'); diff --git a/src/avrdude/butterfly.c b/src/avrdude/butterfly.c index beb5e04dea..8f582e72a0 100644 --- a/src/avrdude/butterfly.c +++ b/src/avrdude/butterfly.c @@ -675,7 +675,7 @@ static int butterfly_paged_load(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, butterfly_set_addr(pgm, addr / rd_size); } while (addr < max_addr) { - if ((max_addr - addr) < blocksize) { + if ((max_addr - addr) < (unsigned)blocksize) { blocksize = max_addr - addr; }; cmd[1] = (blocksize >> 8) & 0xff; diff --git a/src/avrdude/config.c b/src/avrdude/config.c index 1c0ff55258..b82fb29cb7 100644 --- a/src/avrdude/config.c +++ b/src/avrdude/config.c @@ -240,7 +240,7 @@ TOKEN * string(char * text) return NULL; /* yyerror already called */ } - len = strlen(text); + len = (int)strlen(text); tkn->value.type = V_STR; tkn->value.string = (char *) malloc(len+1); @@ -351,7 +351,7 @@ int read_config(const char * file) } typedef struct yy_buffer_state *YY_BUFFER_STATE; -extern YY_BUFFER_STATE yy_scan_bytes(char *base, size_t size); +extern YY_BUFFER_STATE yy_scan_bytes(const char *base, size_t size); extern void yy_delete_buffer(YY_BUFFER_STATE b); int read_config_builtin() @@ -363,7 +363,7 @@ int read_config_builtin() // Note: Can't use yy_scan_buffer, it's buggy (?), leads to fread from a null FILE* // and so unfortunatelly we have to use the copying variant here - YY_BUFFER_STATE buffer = yy_scan_bytes(avrdude_slic3r_conf, avrdude_slic3r_conf_size); + YY_BUFFER_STATE buffer = yy_scan_bytes((const char *)avrdude_slic3r_conf, avrdude_slic3r_conf_size); if (buffer == NULL) { avrdude_message(MSG_INFO, "%s: read_config_builtin: Failed to initialize parsing buffer\n", progname); return -1; diff --git a/src/avrdude/config_gram.c b/src/avrdude/config_gram.c index c1a65b13ea..2d32fe7392 100644 --- a/src/avrdude/config_gram.c +++ b/src/avrdude/config_gram.c @@ -3640,7 +3640,7 @@ static int parse_cmdbits(OPCODE * op) break; } - len = strlen(s); + len = (int)strlen(s); if (len == 0) { yyerror("invalid bit specifier \"\""); diff --git a/src/avrdude/config_gram.y b/src/avrdude/config_gram.y index 0aa95a8e8c..6a062352b1 100644 --- a/src/avrdude/config_gram.y +++ b/src/avrdude/config_gram.y @@ -1493,7 +1493,7 @@ static int parse_cmdbits(OPCODE * op) break; } - len = strlen(s); + len = (int)strlen(s); if (len == 0) { yyerror("invalid bit specifier \"\""); diff --git a/src/avrdude/fileio.c b/src/avrdude/fileio.c index 7803497a0c..7f4c8edeeb 100644 --- a/src/avrdude/fileio.c +++ b/src/avrdude/fileio.c @@ -264,7 +264,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) unsigned char cksum; int rc; - len = strlen(rec); + len = (int)strlen(rec); offset = 1; cksum = 0; @@ -274,7 +274,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - ihex->reclen = strtoul(buf, &e, 16); + ihex->reclen = (unsigned char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -294,7 +294,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - ihex->rectyp = strtoul(buf, &e, 16); + ihex->rectyp = (unsigned char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -308,7 +308,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - ihex->data[j] = strtoul(buf, &e, 16); + ihex->data[j] = (char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; cksum += ihex->data[j]; @@ -320,7 +320,7 @@ static int ihex_readrec(struct ihexrec * ihex, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - ihex->cksum = strtoul(buf, &e, 16); + ihex->cksum = (char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -361,7 +361,7 @@ static int ihex2b(char * infile, FILE * inf, while (fgets((char *)buffer,MAX_LINE_LEN,inf)!=NULL) { lineno++; - len = strlen(buffer); + len = (int)strlen(buffer); if (buffer[len-1] == '\n') buffer[--len] = 0; if (buffer[0] != ':') @@ -388,7 +388,7 @@ static int ihex2b(char * infile, FILE * inf, return -1; } nextaddr = ihex.loadofs + baseaddr - fileoffset; - if (nextaddr + ihex.reclen > bufsize) { + if (nextaddr + ihex.reclen > (unsigned)bufsize) { avrdude_message(MSG_INFO, "%s: ERROR: address 0x%04x out of range at line %d of %s\n", progname, nextaddr+ihex.reclen, lineno, infile); return -1; @@ -502,10 +502,11 @@ static int b2srec(unsigned char * inbuf, int bufsize, cksum += n + addr_width + 1; - for (i=addr_width; i>0; i--) + for (i = addr_width; i>0; i--) { cksum += (nextaddr >> (i-1) * 8) & 0xff; + } - for (i=nextaddr; ireclen = strtoul(buf, &e, 16); + srec->reclen = (char)strtoul(buf, &e, 16); cksum += srec->reclen; srec->reclen -= (addr_width+1); if (e == buf || *e != 0) @@ -594,7 +595,7 @@ static int srec_readrec(struct ihexrec * srec, char * rec) for (i=0; iloadofs = strtoull(buf, &e, 16); + srec->loadofs = strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -608,7 +609,7 @@ static int srec_readrec(struct ihexrec * srec, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - srec->data[j] = strtoul(buf, &e, 16); + srec->data[j] = (char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; cksum += srec->data[j]; @@ -620,7 +621,7 @@ static int srec_readrec(struct ihexrec * srec, char * rec) for (i=0; i<2; i++) buf[i] = rec[offset++]; buf[i] = 0; - srec->cksum = strtoul(buf, &e, 16); + srec->cksum = (char)strtoul(buf, &e, 16); if (e == buf || *e != 0) return -1; @@ -650,7 +651,7 @@ static int srec2b(char * infile, FILE * inf, while (fgets((char *)buffer,MAX_LINE_LEN,inf)!=NULL) { lineno++; - len = strlen(buffer); + len = (int)strlen(buffer); if (buffer[len-1] == '\n') buffer[--len] = 0; if (buffer[0] != 0x53) @@ -729,7 +730,7 @@ static int srec2b(char * infile, FILE * inf, return -1; } nextaddr -= fileoffset; - if (nextaddr + srec.reclen > bufsize) { + if (nextaddr + srec.reclen > (unsigned)bufsize) { avrdude_message(MSG_INFO, msg, progname, nextaddr+srec.reclen, "", lineno, infile); return -1; @@ -740,7 +741,7 @@ static int srec2b(char * infile, FILE * inf, } if (nextaddr+srec.reclen > maxaddr) maxaddr = nextaddr+srec.reclen; - reccount++; + reccount++; } } @@ -1143,12 +1144,12 @@ static int fileio_rbin(struct fioparms * fio, switch (fio->op) { case FIO_READ: - rc = fread(buf, 1, size, f); + rc = (int)fread(buf, 1, size, f); if (rc > 0) memset(mem->tags, TAG_ALLOCATED, rc); break; case FIO_WRITE: - rc = fwrite(buf, 1, size, f); + rc = (int)fwrite(buf, 1, size, f); break; default: avrdude_message(MSG_INFO, "%s: fileio: invalid operation=%d\n", @@ -1190,7 +1191,7 @@ static int fileio_imm(struct fioparms * fio, progname, p); return -1; } - mem->buf[loc] = b; + mem->buf[loc] = (char)b; mem->tags[loc++] = TAG_ALLOCATED; p = strtok(NULL, " ,"); rc = loc; @@ -1452,7 +1453,7 @@ static int fmt_autodetect(char * fname, unsigned section) } buf[MAX_LINE_LEN-1] = 0; - len = strlen((char *)buf); + len = (int)strlen((char *)buf); if (buf[len-1] == '\n') buf[--len] = 0; diff --git a/src/avrdude/lists.c b/src/avrdude/lists.c index cab88364e7..063507ed32 100644 --- a/src/avrdude/lists.c +++ b/src/avrdude/lists.c @@ -444,7 +444,7 @@ lcreat ( void * liststruct, int elements ) l->poolsize = DEFAULT_POOLSIZE; } else { - l->poolsize = elements*sizeof(LISTNODE)+sizeof(NODEPOOL); + l->poolsize = (short)(elements*sizeof(LISTNODE)+sizeof(NODEPOOL)); } l->n_ln_pool = (l->poolsize-sizeof(NODEPOOL))/sizeof(LISTNODE); @@ -803,7 +803,7 @@ lget_n ( LISTID lid, unsigned int n ) CKLMAGIC(l); - if ((n<1)||(n>lsize(l))) { + if ((n < 1) || (n > (unsigned)lsize(l))) { return NULL; } @@ -844,7 +844,7 @@ lget_ln ( LISTID lid, unsigned int n ) CKLMAGIC(l); - if ((n<1)||(n>lsize(l))) { + if ((n < 1) || (n > (unsigned)lsize(l))) { return NULL; } @@ -941,7 +941,7 @@ insert_ln ( LIST * l, LISTNODE * ln, void * data_ptr ) | | Insert data before the nth item in the list. -----------------------------------------------------------------*/ -int +int lins_n ( LISTID lid, void * data_ptr, unsigned int n ) { int i; @@ -952,7 +952,7 @@ lins_n ( LISTID lid, void * data_ptr, unsigned int n ) CKLMAGIC(l); - if ((n<1)||(n>(l->num+1))) { + if ((n < 1) || (n > (unsigned)(l->num+1))) { return -1; } @@ -1193,7 +1193,7 @@ lrmv_n ( LISTID lid, unsigned int n ) CKLMAGIC(l); - if ((n<1)||(n>l->num)) { + if ((n < 1) || (n > (unsigned)l->num)) { return NULL; } diff --git a/src/avrdude/main.c b/src/avrdude/main.c index 8f90403495..60be7ec3a9 100644 --- a/src/avrdude/main.c +++ b/src/avrdude/main.c @@ -107,7 +107,7 @@ int avrdude_message(const int msglvl, const char *format, ...) if (rc > 0 && rc < MSGBUFFER_SIZE) { avrdude_message_handler(msgbuffer, rc, avrdude_message_handler_user_p); } else { - avrdude_message_handler(format_error, strlen(format_error), avrdude_message_handler_user_p); + avrdude_message_handler(format_error, (unsigned)strlen(format_error), avrdude_message_handler_user_p); } } @@ -567,7 +567,7 @@ int avrdude_main(int argc, char * argv []) // #endif - len = strlen(progname) + 2; + len = (int)strlen(progname) + 2; for (i=0; ifd.pfd = (void *)hComPort; @@ -326,8 +326,8 @@ static void serbb_close(PROGRAMMER *pgm) pgm->setpin(pgm, PIN_AVR_RESET, 1); CloseHandle (hComPort); } - avrdude_message(MSG_DEBUG, "%s: ser_close(): closed comm port handle 0x%x\n", - progname, (int)hComPort); + avrdude_message(MSG_DEBUG, "%s: ser_close(): closed comm port handle %p\n", + progname, (void *)hComPort); hComPort = INVALID_HANDLE_VALUE; } diff --git a/src/avrdude/stk500.c b/src/avrdude/stk500.c index efb7078bc9..256076cc6a 100644 --- a/src/avrdude/stk500.c +++ b/src/avrdude/stk500.c @@ -504,7 +504,7 @@ static int stk500_initialize(PROGRAMMER * pgm, AVRPART * p) } else { buf[9] = 0xff; - buf[10] = 0xff; + buf[10] = 0xff; buf[13] = 0; buf[14] = 0; buf[17] = 0; @@ -821,7 +821,7 @@ static int stk500_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, break; } - for (prusa3d_semicolon_workaround_round = 0; prusa3d_semicolon_workaround_round < (has_semicolon ? 2 : 1); ++ prusa3d_semicolon_workaround_round) { + for (prusa3d_semicolon_workaround_round = 0; prusa3d_semicolon_workaround_round < (has_semicolon ? 2u : 1u); prusa3d_semicolon_workaround_round++) { /* build command block and avoid multiple send commands as it leads to a crash of the silabs usb serial driver on mac os x */ i = 0; @@ -834,7 +834,7 @@ static int stk500_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, buf[i++] = block_size & 0x0f; buf[i++] = memtype; if (has_semicolon) { - for (j = 0; j < block_size; ++i, ++ j) { + for (j = 0; j < (unsigned)block_size; ++i, ++ j) { buf[i] = m->buf[addr + j]; if (buf[i] == ';') buf[i] |= (prusa3d_semicolon_workaround_round ? 0xf0 : 0x0f); @@ -1088,8 +1088,8 @@ static int stk500_set_sck_period(PROGRAMMER * pgm, double v) min = 8.0 / STK500_XTAL; max = 255 * min; - dur = v / min + 0.5; - + dur = (int)(v / min + 0.5); + if (v < min) { dur = 1; avrdude_message(MSG_INFO, "%s: stk500_set_sck_period(): p = %.1f us too small, using %.1f us\n", @@ -1099,7 +1099,7 @@ static int stk500_set_sck_period(PROGRAMMER * pgm, double v) avrdude_message(MSG_INFO, "%s: stk500_set_sck_period(): p = %.1f us too large, using %.1f us\n", progname, v / 1e-6, dur * min / 1e-6); } - + return stk500_setparm(pgm, Parm_STK_SCK_DURATION, dur); } diff --git a/src/avrdude/stk500v2.c b/src/avrdude/stk500v2.c index 9bc629ba41..33bdf8eeae 100644 --- a/src/avrdude/stk500v2.c +++ b/src/avrdude/stk500v2.c @@ -130,58 +130,58 @@ struct jtagispentry #define SZ_SPI_MULTI (USHRT_MAX - 1) }; -static const struct jtagispentry jtagispcmds[] = { - /* generic */ - { CMD_SET_PARAMETER, 2 }, - { CMD_GET_PARAMETER, 3 }, - { CMD_OSCCAL, 2 }, - { CMD_LOAD_ADDRESS, 2 }, - /* ISP mode */ - { CMD_ENTER_PROGMODE_ISP, 2 }, - { CMD_LEAVE_PROGMODE_ISP, 2 }, - { CMD_CHIP_ERASE_ISP, 2 }, - { CMD_PROGRAM_FLASH_ISP, 2 }, - { CMD_READ_FLASH_ISP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_EEPROM_ISP, 2 }, - { CMD_READ_EEPROM_ISP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_FUSE_ISP, 3 }, - { CMD_READ_FUSE_ISP, 4 }, - { CMD_PROGRAM_LOCK_ISP, 3 }, - { CMD_READ_LOCK_ISP, 4 }, - { CMD_READ_SIGNATURE_ISP, 4 }, - { CMD_READ_OSCCAL_ISP, 4 }, - { CMD_SPI_MULTI, SZ_SPI_MULTI }, - /* all HV modes */ - { CMD_SET_CONTROL_STACK, 2 }, - /* HVSP mode */ - { CMD_ENTER_PROGMODE_HVSP, 2 }, - { CMD_LEAVE_PROGMODE_HVSP, 2 }, - { CMD_CHIP_ERASE_HVSP, 2 }, - { CMD_PROGRAM_FLASH_HVSP, 2 }, - { CMD_READ_FLASH_HVSP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_EEPROM_HVSP, 2 }, - { CMD_READ_EEPROM_HVSP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_FUSE_HVSP, 2 }, - { CMD_READ_FUSE_HVSP, 3 }, - { CMD_PROGRAM_LOCK_HVSP, 2 }, - { CMD_READ_LOCK_HVSP, 3 }, - { CMD_READ_SIGNATURE_HVSP, 3 }, - { CMD_READ_OSCCAL_HVSP, 3 }, - /* PP mode */ - { CMD_ENTER_PROGMODE_PP, 2 }, - { CMD_LEAVE_PROGMODE_PP, 2 }, - { CMD_CHIP_ERASE_PP, 2 }, - { CMD_PROGRAM_FLASH_PP, 2 }, - { CMD_READ_FLASH_PP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_EEPROM_PP, 2 }, - { CMD_READ_EEPROM_PP, SZ_READ_FLASH_EE }, - { CMD_PROGRAM_FUSE_PP, 2 }, - { CMD_READ_FUSE_PP, 3 }, - { CMD_PROGRAM_LOCK_PP, 2 }, - { CMD_READ_LOCK_PP, 3 }, - { CMD_READ_SIGNATURE_PP, 3 }, - { CMD_READ_OSCCAL_PP, 3 }, -}; +// static const struct jtagispentry jtagispcmds[] = { +// /* generic */ +// { CMD_SET_PARAMETER, 2 }, +// { CMD_GET_PARAMETER, 3 }, +// { CMD_OSCCAL, 2 }, +// { CMD_LOAD_ADDRESS, 2 }, +// /* ISP mode */ +// { CMD_ENTER_PROGMODE_ISP, 2 }, +// { CMD_LEAVE_PROGMODE_ISP, 2 }, +// { CMD_CHIP_ERASE_ISP, 2 }, +// { CMD_PROGRAM_FLASH_ISP, 2 }, +// { CMD_READ_FLASH_ISP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_EEPROM_ISP, 2 }, +// { CMD_READ_EEPROM_ISP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_FUSE_ISP, 3 }, +// { CMD_READ_FUSE_ISP, 4 }, +// { CMD_PROGRAM_LOCK_ISP, 3 }, +// { CMD_READ_LOCK_ISP, 4 }, +// { CMD_READ_SIGNATURE_ISP, 4 }, +// { CMD_READ_OSCCAL_ISP, 4 }, +// { CMD_SPI_MULTI, SZ_SPI_MULTI }, +// /* all HV modes */ +// { CMD_SET_CONTROL_STACK, 2 }, +// /* HVSP mode */ +// { CMD_ENTER_PROGMODE_HVSP, 2 }, +// { CMD_LEAVE_PROGMODE_HVSP, 2 }, +// { CMD_CHIP_ERASE_HVSP, 2 }, +// { CMD_PROGRAM_FLASH_HVSP, 2 }, +// { CMD_READ_FLASH_HVSP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_EEPROM_HVSP, 2 }, +// { CMD_READ_EEPROM_HVSP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_FUSE_HVSP, 2 }, +// { CMD_READ_FUSE_HVSP, 3 }, +// { CMD_PROGRAM_LOCK_HVSP, 2 }, +// { CMD_READ_LOCK_HVSP, 3 }, +// { CMD_READ_SIGNATURE_HVSP, 3 }, +// { CMD_READ_OSCCAL_HVSP, 3 }, +// /* PP mode */ +// { CMD_ENTER_PROGMODE_PP, 2 }, +// { CMD_LEAVE_PROGMODE_PP, 2 }, +// { CMD_CHIP_ERASE_PP, 2 }, +// { CMD_PROGRAM_FLASH_PP, 2 }, +// { CMD_READ_FLASH_PP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_EEPROM_PP, 2 }, +// { CMD_READ_EEPROM_PP, SZ_READ_FLASH_EE }, +// { CMD_PROGRAM_FUSE_PP, 2 }, +// { CMD_READ_FUSE_PP, 3 }, +// { CMD_PROGRAM_LOCK_PP, 2 }, +// { CMD_READ_LOCK_PP, 3 }, +// { CMD_READ_SIGNATURE_PP, 3 }, +// { CMD_READ_OSCCAL_PP, 3 }, +// }; /* * From XML file: @@ -379,15 +379,15 @@ static void stk500v2_jtag3_teardown(PROGRAMMER * pgm) } -static unsigned short -b2_to_u16(unsigned char *b) -{ - unsigned short l; - l = b[0]; - l += (unsigned)b[1] << 8; +// static unsigned short +// b2_to_u16(unsigned char *b) +// { +// unsigned short l; +// l = b[0]; +// l += (unsigned)b[1] << 8; - return l; -} +// return l; +// } static int stk500v2_send_mk2(PROGRAMMER * pgm, unsigned char * data, size_t len) { @@ -399,16 +399,16 @@ static int stk500v2_send_mk2(PROGRAMMER * pgm, unsigned char * data, size_t len) return 0; } -static unsigned short get_jtagisp_return_size(unsigned char cmd) -{ - int i; +// static unsigned short get_jtagisp_return_size(unsigned char cmd) +// { +// int i; - for (i = 0; i < sizeof jtagispcmds / sizeof jtagispcmds[0]; i++) - if (jtagispcmds[i].cmd == cmd) - return jtagispcmds[i].size; +// for (i = 0; i < sizeof jtagispcmds / sizeof jtagispcmds[0]; i++) +// if (jtagispcmds[i].cmd == cmd) +// return jtagispcmds[i].size; - return 0; -} +// return 0; +// } /* * Send the data as a JTAG ICE mkII encapsulated ISP packet. @@ -504,7 +504,7 @@ static int stk500v2_send(PROGRAMMER * pgm, unsigned char * data, size_t len) buf[0] = MESSAGE_START; buf[1] = PDATA(pgm)->command_sequence; - buf[2] = len / 256; + buf[2] = (char)(len / 256); buf[3] = len % 256; buf[4] = TOKEN; memcpy(buf+5, data, len); @@ -1128,7 +1128,8 @@ static int stk500v2_program_enable(PROGRAMMER * pgm, AVRPART * p) { unsigned char buf[16]; char msg[100]; /* see remarks above about size needed */ - int rv, tries; + int rv; + // int tries; PDATA(pgm)->lastpart = p; @@ -1143,7 +1144,7 @@ static int stk500v2_program_enable(PROGRAMMER * pgm, AVRPART * p) /* Activate AVR-style (low active) RESET */ stk500v2_setparm_real(pgm, PARAM_RESET_POLARITY, 0x01); - tries = 0; + // tries = 0; // retry: buf[0] = CMD_ENTER_PROGMODE_ISP; buf[1] = p->timeout; @@ -1882,7 +1883,7 @@ static int stk500hv_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, if (stk500v2_loadaddr(pgm, use_ext_addr | (paddr >> addrshift)) < 0) return -1; } else { - buf[1] = addr; + buf[1] = (char)addr; } avrdude_message(MSG_NOTICE2, "%s: stk500hv_read_byte(): Sending read memory command: ", @@ -2137,7 +2138,7 @@ static int stk500hv_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, if (stk500v2_loadaddr(pgm, use_ext_addr | (paddr >> addrshift)) < 0) return -1; } else { - buf[1] = addr; + buf[1] = (char)addr; buf[2] = data; if (mode == PPMODE) { buf[3] = pulsewidth; @@ -2298,7 +2299,7 @@ static int stk500v2_paged_write(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m, unsigned int page_size, unsigned int addr, unsigned int n_bytes) { -static int page = 0; +// static int page = 0; unsigned int block_size, last_addr, addrshift, use_ext_addr; unsigned int maxaddr = addr + n_bytes; unsigned char commandbuf[10]; @@ -2833,10 +2834,10 @@ static int stk500v2_set_fosc(PROGRAMMER * pgm, double v) progname, v, unit, STK500V2_XTAL / 2e6); fosc = STK500V2_XTAL / 2; } else - fosc = (unsigned)v; + fosc = (int)v; for (idx = 0; idx < sizeof(ps) / sizeof(ps[0]); idx++) { - if (fosc >= STK500V2_XTAL / (256 * ps[idx] * 2)) { + if (fosc >= (int)(STK500V2_XTAL / (256 * ps[idx] * 2))) { /* this prescaler value can handle our frequency */ prescale = idx + 1; cmatch = (unsigned)(STK500V2_XTAL / (2 * fosc * ps[idx])) - 1; @@ -3065,8 +3066,8 @@ static int stk600_set_fosc(PROGRAMMER * pgm, double v) { unsigned int oct, dac; - oct = 1.443 * log(v / 1039.0); - dac = 2048 - (2078.0 * pow(2, (double)(10 + oct))) / v; + oct = (unsigned)(1.443 * log(v / 1039.0)); + dac = (unsigned)(2048.0 - (2078.0 * pow(2, (double)(10 + oct))) / v); return stk500v2_setparm2(pgm, PARAM2_CLOCK_CONF, (oct << 12) | (dac << 2)); } @@ -3075,7 +3076,7 @@ static int stk600_set_sck_period(PROGRAMMER * pgm, double v) { unsigned int sck; - sck = ceil((16e6 / (2 * 1.0 / v)) - 1); + sck = (unsigned)ceil((16e6 / (2 * 1.0 / v)) - 1); if (sck >= 4096) sck = 4095; @@ -3093,7 +3094,7 @@ static int stk500v2_jtag3_set_sck_period(PROGRAMMER * pgm, double v) else if (v > 1E-3) sck = 1; else - sck = 1.0 / (1000.0 * v); + sck = (unsigned)(1.0 / (1000.0 * v)); value[0] = CMD_SET_SCK; value[1] = sck & 0xff; @@ -3143,7 +3144,7 @@ static int stk500v2_setparm_real(PROGRAMMER * pgm, unsigned char parm, unsigned static int stk500v2_setparm(PROGRAMMER * pgm, unsigned char parm, unsigned char value) { - unsigned char current_value; + unsigned char current_value = 0; int res; res = stk500v2_getparm(pgm, parm, ¤t_value); @@ -3214,8 +3215,15 @@ static const char *stk600_get_cardname(const struct carddata *table, static void stk500v2_display(PROGRAMMER * pgm, const char * p) { - unsigned char maj, min, hdw, topcard, maj_s1, min_s1, maj_s2, min_s2; - unsigned int rev; + unsigned char maj = 0; + unsigned char min = 0; + unsigned char hdw = 0; + unsigned char topcard = 0; + unsigned char maj_s1 = 0; + unsigned char min_s1 = 0; + unsigned char maj_s2 = 0; + unsigned char min_s2 = 0; + unsigned int rev = 0; const char *topcard_name, *pgmname; switch (PDATA(pgm)->pgmtype) { @@ -3294,13 +3302,20 @@ f_to_kHz_MHz(double f, const char **unit) static void stk500v2_print_parms1(PROGRAMMER * pgm, const char * p) { - unsigned char vtarget, vadjust, osc_pscale, osc_cmatch, sck_duration =0; //XXX 0 is not correct, check caller - unsigned int sck_stk600, clock_conf, dac, oct, varef; - unsigned char vtarget_jtag[4]; + unsigned char vtarget = 0; + unsigned char vadjust = 0; + unsigned char sck_duration = 0; + unsigned char osc_pscale = 0; + unsigned char osc_cmatch = 0; + unsigned varef = 0; + unsigned sck_stk600 = 0; + unsigned clock_conf = 0; + unsigned dac, oct; + // unsigned char vtarget_jtag[4]; int prescale; double f; const char *unit; - void *mycookie; + // void *mycookie; if (PDATA(pgm)->pgmtype == PGMTYPE_JTAGICE_MKII) { return; @@ -3963,10 +3978,10 @@ static int stk600_xprog_write_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, b[0] = XPRG_CMD_WRITE_MEM; b[1] = memcode; b[2] = 0; /* pagemode: non-paged write */ - b[3] = addr >> 24; - b[4] = addr >> 16; - b[5] = addr >> 8; - b[6] = addr; + b[3] = (char)(addr >> 24); + b[4] = (char)(addr >> 16); + b[5] = (char)(addr >> 8); + b[6] = (char)addr; b[7] = 0; b[8] = write_size; b[9] = data; @@ -4011,10 +4026,10 @@ static int stk600_xprog_read_byte(PROGRAMMER * pgm, AVRPART * p, AVRMEM * mem, addr += mem->offset; b[0] = XPRG_CMD_READ_MEM; - b[2] = addr >> 24; - b[3] = addr >> 16; - b[4] = addr >> 8; - b[5] = addr; + b[2] = (char)(addr >> 24); + b[3] = (char)(addr >> 16); + b[4] = (char)(addr >> 8); + b[5] = (char)addr; b[6] = 0; b[7] = 1; if (stk600_xprog_command(pgm, b, 8, 3) < 0) { diff --git a/src/avrdude/term.c b/src/avrdude/term.c index 012f6f1a51..182367cf22 100644 --- a/src/avrdude/term.c +++ b/src/avrdude/term.c @@ -281,7 +281,7 @@ static int cmd_dump(PROGRAMMER * pgm, struct avrpart * p, maxsize = mem->size; - if (addr >= maxsize) { + if (addr >= (unsigned long)maxsize) { if (argc == 2) { /* wrap around */ addr = 0; @@ -294,7 +294,7 @@ static int cmd_dump(PROGRAMMER * pgm, struct avrpart * p, } /* trim len if nessary to not read past the end of memory */ - if ((addr + len) > maxsize) + if ((addr + len) > (unsigned long)maxsize) len = maxsize - addr; buf = malloc(len); @@ -303,7 +303,7 @@ static int cmd_dump(PROGRAMMER * pgm, struct avrpart * p, return -1; } - for (i=0; iread_byte(pgm, p, mem, addr+i, &buf[i]); if (rc != 0) { avrdude_message(MSG_INFO, "error reading %s address 0x%05lx of part %s\n", @@ -364,7 +364,7 @@ static int cmd_write(PROGRAMMER * pgm, struct avrpart * p, return -1; } - if (addr > maxsize) { + if (addr > (unsigned long)maxsize) { avrdude_message(MSG_INFO, "%s (write): address 0x%05lx is out of range for %s memory\n", progname, addr, memtype); return -1; @@ -373,7 +373,7 @@ static int cmd_write(PROGRAMMER * pgm, struct avrpart * p, /* number of bytes to write at the specified address */ len = argc - 3; - if ((addr + len) > maxsize) { + if ((addr + len) > (unsigned long)maxsize) { avrdude_message(MSG_INFO, "%s (write): selected address and # bytes exceed " "range for %s memory\n", progname, memtype); @@ -386,8 +386,8 @@ static int cmd_write(PROGRAMMER * pgm, struct avrpart * p, return -1; } - for (i=3; ierr_led(pgm, OFF); - for (werror=0, i=0; i Date: Tue, 25 Jun 2019 16:54:11 +0200 Subject: [PATCH 197/627] semver: warnings cleanup --- src/semver/semver.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/semver/semver.c b/src/semver/semver.c index 527738644d..e8bd6edcfc 100644 --- a/src/semver/semver.c +++ b/src/semver/semver.c @@ -22,6 +22,10 @@ static const size_t MAX_SIZE = sizeof(char) * 255; static const int MAX_SAFE_INT = (unsigned int) -1 >> 1; +#ifdef _WIN32 + #define strdup _strdup +#endif + /** * Define comparison operators, storing the * ASCII code per each symbol in hexadecimal notation. @@ -50,8 +54,8 @@ strcut (char *str, int begin, int len) { if((int)l < 0 || (int)l > MAX_SAFE_INT) return -1; - if (len < 0) len = l - begin + 1; - if (begin + len > (int)l) len = l - begin; + if (len < 0) len = (int)l - begin + 1; + if (begin + len > (int)l) len = (int)l - begin; memmove(str + begin, str + begin + len, l - len + 1 - begin); return len; @@ -104,7 +108,7 @@ parse_int (const char *s) { static char * parse_slice (char *buf, char sep) { char *pr, *part; - int plen; + size_t plen; /* Find separator in buf */ pr = strchr(buf, sep); @@ -210,8 +214,9 @@ semver_parse_version (const char *str, semver_t *ver) { static int compare_prerelease (char *x, char *y) { char *lastx, *lasty, *xptr, *yptr, *endptr; - int xlen, ylen, xisnum, yisnum, xnum, ynum; - int xn, yn, min, res; + size_t xlen, ylen, xn, yn, min; + int xisnum, yisnum, xnum, ynum; + int res; if (x == NULL && y == NULL) return 0; if (y == NULL && x) return -1; if (x == NULL && y) return 1; @@ -572,7 +577,7 @@ semver_clean (char *s) { for (i = 0; i < len; i++) { if (contains(s[i], VALID_CHARS, mlen) == 0) { - res = strcut(s, i, 1); + res = strcut(s, (int)i, 1); if(res == -1) return -1; --len; --i; } From 4fb904357f9b3d727bb1dd8130cc7252f50e2e1e Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 25 Jun 2019 17:23:53 +0200 Subject: [PATCH 198/627] Warnings cleanup in BonjourDialog, ConfigWizard, FirmwareDialog, GLGizmoCut, ImGuiWrapper --- src/slic3r/GUI/BonjourDialog.cpp | 2 +- src/slic3r/GUI/ConfigWizard.cpp | 6 ++-- src/slic3r/GUI/FirmwareDialog.cpp | 3 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 44 +--------------------------- src/slic3r/GUI/ImGuiWrapper.cpp | 4 +-- 5 files changed, 8 insertions(+), 51 deletions(-) diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index 1885dda7be..0e05a517c6 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -171,7 +171,7 @@ void BonjourDialog::on_reply(BonjourReplyEvent &e) // Filter replies based on selected technology const auto model = e.reply.txt_data.find("model"); const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1"; - if (tech == ptFFF && sl1 || tech == ptSLA && !sl1) { + if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) { return; } diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index aacbfdc52e..8b08f6f7fa 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -330,8 +330,8 @@ PagePrinters::PagePrinters(ConfigWizard *parent, wxString title, wxString shortn const auto families = vendor.families(); for (const auto &family : families) { const auto filter = [&](const VendorProfile::PrinterModel &model) { - return (model.technology == ptFFF && technology & T_FFF - || model.technology == ptSLA && technology & T_SLA) + return ((model.technology == ptFFF && technology & T_FFF) + || (model.technology == ptSLA && technology & T_SLA)) && model.family == family; }; @@ -810,7 +810,7 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) const Item& item = items[i]; unsigned x = em_w/2 + item.indent * em_w; - if (i == item_active || item_hover >= 0 && i == (size_t)item_hover) { + if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) { dc.DrawBitmap(bullet_blue.bmp(), x, y + yoff_icon, false); } else if (i < item_active) { dc.DrawBitmap(bullet_black.bmp(), x, y + yoff_icon, false); } diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index 15a09aa716..7865aecf2e 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -442,8 +442,7 @@ void FirmwareDialog::priv::avr109_lookup_port(Avr109Pid usb_pid) auto ports = Utils::scan_serial_ports_extended(); ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { return port.id_vendor != USB_VID_PRUSA || - port.id_product != usb_pid.boot && - port.id_product != usb_pid.app; + (port.id_product != usb_pid.boot && port.id_product != usb_pid.app); }), ports.end()); if (ports.size() == 0) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 8934bc52b4..17db953d46 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -15,48 +15,6 @@ namespace Slic3r { namespace GUI { - -class GLGizmoCutPanel : public wxPanel -{ -public: - GLGizmoCutPanel(wxWindow *parent); - - void display(bool display); -private: - bool m_active; - wxCheckBox *m_cb_rotate; - wxButton *m_btn_cut; - wxButton *m_btn_cancel; -}; - -GLGizmoCutPanel::GLGizmoCutPanel(wxWindow *parent) - : wxPanel(parent) - , m_active(false) - , m_cb_rotate(new wxCheckBox(this, wxID_ANY, _(L("Rotate lower part upwards")))) - , m_btn_cut(new wxButton(this, wxID_OK, _(L("Perform cut")))) - , m_btn_cancel(new wxButton(this, wxID_CANCEL, _(L("Cancel")))) -{ - enum { MARGIN = 5 }; - - auto *sizer = new wxBoxSizer(wxHORIZONTAL); - - auto *label = new wxStaticText(this, wxID_ANY, _(L("Cut object:"))); - sizer->Add(label, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->Add(m_cb_rotate, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->AddStretchSpacer(); - sizer->Add(m_btn_cut, 0, wxALL | wxALIGN_CENTER, MARGIN); - sizer->Add(m_btn_cancel, 0, wxALL | wxALIGN_CENTER, MARGIN); - - SetSizer(sizer); -} - -void GLGizmoCutPanel::display(bool display) -{ - Show(display); - GetParent()->Layout(); -} - - const double GLGizmoCut::Offset = 10.0; const double GLGizmoCut::Margin = 20.0; const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0 }; @@ -188,7 +146,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, co m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); ImGui::PushItemWidth(m_imgui->scaled(5.0f)); - bool _value_changed = ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); + ImGui::InputDouble("Z", &m_cut_z, 0.0f, 0.0f, "%.2f"); m_imgui->checkbox(_(L("Keep upper part")), m_keep_upper); m_imgui->checkbox(_(L("Keep lower part")), m_keep_lower); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index ca1538bf72..b6abf641ab 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -326,9 +326,9 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector& int selection_out = -1; bool res = false; - const char *selection_str = selection < options.size() ? options[selection].c_str() : ""; + const char *selection_str = selection < (int)options.size() ? options[selection].c_str() : ""; if (ImGui::BeginCombo("", selection_str)) { - for (int i = 0; i < options.size(); i++) { + for (int i = 0; i < (int)options.size(); i++) { if (ImGui::Selectable(options[i].c_str(), i == selection)) { selection_out = i; } From 85575e5615d44e5bc46963f446b2b406464c1150 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 25 Jun 2019 17:25:24 +0200 Subject: [PATCH 199/627] Fix: IsTriviallyCopyable on clang --- src/libslic3r/Utils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index e76fccdb8a..adf7f57a71 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -167,7 +167,7 @@ template size_t next_highest_power_of_2(T v, extern std::string xml_escape(std::string text); -#if defined __GNUC__ & __GNUC__ < 5 +#if defined __GNUC__ && __GNUC__ < 5 && !defined __clang__ // Older GCCs don't have std::is_trivially_copyable // cf. https://gcc.gnu.org/onlinedocs/gcc-4.9.4/libstdc++/manual/manual/status.html#status.iso.2011 #warning "GCC version < 5, faking std::is_trivially_copyable" From a38bcdde6681c81559e0aa7a01361d62ef2118eb Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 25 Jun 2019 17:41:24 +0200 Subject: [PATCH 200/627] avrdude: Fix types in embedded config --- src/avrdude/avrdude-slic3r.conf.h | 6 +++--- src/avrdude/conf-generate.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/avrdude/avrdude-slic3r.conf.h b/src/avrdude/avrdude-slic3r.conf.h index 7cc901336b..905b14ee8c 100644 --- a/src/avrdude/avrdude-slic3r.conf.h +++ b/src/avrdude/avrdude-slic3r.conf.h @@ -1,5 +1,5 @@ /* WARN: This file is auto-generated from `avrdude-slic3r.conf` */ -unsigned char avrdude_slic3r_conf[] = { +const unsigned char avrdude_slic3r_conf[] = { 0x0a, 0x23, 0x0a, 0x23, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x62, 0x61, 0x73, 0x69, 0x63, 0x20, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, @@ -1184,5 +1184,5 @@ unsigned char avrdude_slic3r_conf[] = { 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x0a, 0, 0 }; -size_t avrdude_slic3r_conf_size = 14178; -size_t avrdude_slic3r_conf_size_yy = 14180; +const size_t avrdude_slic3r_conf_size = 14178; +const size_t avrdude_slic3r_conf_size_yy = 14180; diff --git a/src/avrdude/conf-generate.cpp b/src/avrdude/conf-generate.cpp index f2761ba223..4aa80ae0af 100644 --- a/src/avrdude/conf-generate.cpp +++ b/src/avrdude/conf-generate.cpp @@ -21,7 +21,7 @@ int main(int argc, char const *argv[]) } std::cout << "/* WARN: This file is auto-generated from `" << filename << "` */" << std::endl; - std::cout << "unsigned char " << symbol << "[] = {"; + std::cout << "const unsigned char " << symbol << "[] = {"; char c; std::cout << std::hex; @@ -34,8 +34,8 @@ int main(int argc, char const *argv[]) std::cout << "\n 0, 0\n};\n"; std::cout << std::dec; - std::cout << "size_t " << symbol << "_size = " << size << ";" << std::endl; - std::cout << "size_t " << symbol << "_size_yy = " << size + 2 << ";" << std::endl; + std::cout << "const size_t " << symbol << "_size = " << size << ";" << std::endl; + std::cout << "const size_t " << symbol << "_size_yy = " << size + 2 << ";" << std::endl; return 0; } From d818d1b429044f547699f2625d2a74501528968f Mon Sep 17 00:00:00 2001 From: BeldrothTheGold Date: Sat, 15 Jun 2019 19:10:14 -0600 Subject: [PATCH 201/627] Add debug option to display picking pass to screen --- src/libslic3r/Technologies.hpp | 2 ++ src/slic3r/GUI/GLCanvas3D.cpp | 2 ++ src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a5f93b24f2..86a00480b2 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -15,6 +15,8 @@ #define ENABLE_RENDER_STATISTICS 0 // Shows an imgui dialog with camera related data #define ENABLE_CAMERA_STATISTICS 0 +// Render the picking pass instead of the main scene +#define ENABLE_RENDER_PICKING_PASS 0 //==================== diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c828e0ec66..5d73c9248c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1625,6 +1625,7 @@ void GLCanvas3D::render() _picking_pass(); } +#if !ENABLE_RENDER_PICKING_PASS // draw scene glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); _render_background(); @@ -1654,6 +1655,7 @@ void GLCanvas3D::render() _render_current_gizmo(); _render_selection_sidebar_hints(); +#endif // !ENABLE_RENDER_PICKING_PASS #if ENABLE_SHOW_CAMERA_TARGET _render_camera_target(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index cfe07a61cf..7e2b558d94 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -259,6 +259,10 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const { +#if ENABLE_RENDER_PICKING_PASS + m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); +#endif + glsafe(::glEnable(GL_DEPTH_TEST)); render_points(selection, true); } From ac82cbe0cc6d9123611e24a5aabbad1a1bb4c9de Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 26 Jun 2019 09:48:52 +0200 Subject: [PATCH 202/627] Fix of #2548 --- src/slic3r/GUI/Plater.cpp | 38 +++++++++++++++++++++++++++++++++++--- src/slic3r/GUI/Plater.hpp | 14 +++++++++++++- src/slic3r/GUI/Tab.cpp | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f68267cef0..ceedece607 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1260,6 +1260,7 @@ struct Plater::priv Preview *preview; BackgroundSlicingProcess background_process; + bool suppressed_backround_processing_update { false }; // A class to handle UI jobs like arranging and optimizing rotation. // These are not instant jobs, the user has to be informed about their @@ -1694,7 +1695,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) panels.push_back(preview); this->background_process_timer.SetOwner(this->q, 0); - this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->update_restart_background_process(false, false); }); + this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) + { + if (!this->suppressed_backround_processing_update) + this->update_restart_background_process(false, false); + }); update(); @@ -4176,9 +4181,25 @@ void Plater::changed_objects(const std::vector& object_idxs) this->p->schedule_background_process(); } -void Plater::schedule_background_process() +void Plater::schedule_background_process(bool schedule/* = true*/) { - this->p->schedule_background_process(); + if (schedule) + this->p->schedule_background_process(); + + this->p->suppressed_backround_processing_update = false; +} + +bool Plater::is_background_process_running() const +{ + return this->p->background_process_timer.IsRunning(); +} + +void Plater::suppress_background_process(const bool stop_background_process) +{ + if (stop_background_process) + this->p->background_process_timer.Stop(); + + this->p->suppressed_backround_processing_update = true; } void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); } @@ -4254,4 +4275,15 @@ bool Plater::can_copy_to_clipboard() const return true; } +SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() : + m_was_running(wxGetApp().plater()->is_background_process_running()) +{ + wxGetApp().plater()->suppress_background_process(m_was_running); +} + +SuppressBackgroundProcessingUpdate::~SuppressBackgroundProcessingUpdate() +{ + wxGetApp().plater()->schedule_background_process(m_was_running); +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 2851af6544..5b1347891e 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -175,7 +175,9 @@ public: void reslice_SLA_supports(const ModelObject &object); void changed_object(int obj_idx); void changed_objects(const std::vector& object_idxs); - void schedule_background_process(); + void schedule_background_process(bool schedule = true); + bool is_background_process_running() const; + void suppress_background_process(const bool stop_background_process) ; void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); void send_gcode(); @@ -219,8 +221,18 @@ public: private: struct priv; std::unique_ptr p; + + friend class SuppressBackgroundProcessingUpdate; }; +class SuppressBackgroundProcessingUpdate +{ +public: + SuppressBackgroundProcessingUpdate(); + ~SuppressBackgroundProcessingUpdate(); +private: + bool m_was_running; +}; }} diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 22aecf38d8..6e8b8a471e 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2314,6 +2314,40 @@ void TabPrinter::build_unregular_pages() auto optgroup = page->new_optgroup(_(L("Size"))); optgroup->append_single_option_line("nozzle_diameter", extruder_idx); + + optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value) + { + if (m_extruders_count > 1 && opt_key.find_first_of("nozzle_diameter") != std::string::npos) + { + SuppressBackgroundProcessingUpdate sbpu; + const double new_nd = boost::any_cast(value); + std::vector nozzle_diameters = static_cast(m_config->option("nozzle_diameter"))->values; + + // if value was changed + if (fabs(nozzle_diameters[extruder_idx == 0 ? 1 : 0] - new_nd) > EPSILON) + { + const wxString msg_text = _(L("Do you want to change the diameter for all extruders?")); + auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); + + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_YES) { + for (size_t i = 0; i < nozzle_diameters.size(); i++) { + if (i==extruder_idx) + continue; + nozzle_diameters[i] = new_nd; + } + } + else + nozzle_diameters[extruder_idx] = nozzle_diameters[extruder_idx == 0 ? 1 : 0]; + + new_conf.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameters)); + load_config(new_conf); + } + } + + update_dirty(); + update(); + }; optgroup = page->new_optgroup(_(L("Layer height limits"))); optgroup->append_single_option_line("min_layer_height", extruder_idx); From 14b32c4f166f094bc10fae06c88c5234cd94b292 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Jun 2019 10:33:42 +0200 Subject: [PATCH 203/627] Make an order in using scale and unscale, remove some warnings. --- src/libnest2d/tests/test.cpp | 552 +++++++++++++-------------- src/libslic3r/MTUtils.hpp | 91 +++++ src/libslic3r/MinAreaBoundingBox.cpp | 68 ++-- src/libslic3r/ModelArrange.cpp | 2 +- src/libslic3r/SLA/SLABasePool.cpp | 15 +- src/libslic3r/SLAPrint.cpp | 34 +- src/libslic3r/libslic3r.h | 14 - 7 files changed, 408 insertions(+), 368 deletions(-) diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 5741e87b4f..363a3930ce 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -10,10 +10,6 @@ #include "boost/multiprecision/integer.hpp" #include "boost/rational.hpp" -//#include "../tools/Int128.hpp" - -//#include "gte/Mathematics/GteMinimumAreaBox2.h" - //#include "../tools/libnfpglue.hpp" //#include "../tools/nfp_svgnest_glue.hpp" @@ -42,151 +38,151 @@ struct NfpImpl std::vector& prusaParts() { static std::vector ret; - + if(ret.empty()) { ret.reserve(PRINTER_PART_POLYGONS.size()); for(auto& inp : PRINTER_PART_POLYGONS) ret.emplace_back(inp); } - + return ret; } TEST(BasicFunctionality, Angles) { - + using namespace libnest2d; - + Degrees deg(180); Radians rad(deg); Degrees deg2(rad); - + ASSERT_DOUBLE_EQ(rad, Pi); ASSERT_DOUBLE_EQ(deg, 180); ASSERT_DOUBLE_EQ(deg2, 180); ASSERT_DOUBLE_EQ(rad, Radians(deg)); ASSERT_DOUBLE_EQ( Degrees(rad), deg); - + ASSERT_TRUE(rad == deg); - + Segment seg = {{0, 0}, {12, -10}}; - + ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 270 && Degrees(seg.angleToXaxis()) < 360); - + seg = {{0, 0}, {12, 10}}; - + ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 0 && Degrees(seg.angleToXaxis()) < 90); - + seg = {{0, 0}, {-12, 10}}; - + ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 90 && Degrees(seg.angleToXaxis()) < 180); - + seg = {{0, 0}, {-12, -10}}; - + ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 180 && Degrees(seg.angleToXaxis()) < 270); - + seg = {{0, 0}, {1, 0}}; - + ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 0); - + seg = {{0, 0}, {0, 1}}; - + ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 90); - - + + seg = {{0, 0}, {-1, 0}}; - + ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 180); - - + + seg = {{0, 0}, {0, -1}}; - + ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 270); - + } // Simple test, does not use gmock TEST(BasicFunctionality, creationAndDestruction) { using namespace libnest2d; - + Item sh = { {0, 0}, {1, 0}, {1, 1}, {0, 1} }; - + ASSERT_EQ(sh.vertexCount(), 4u); - + Item sh2 ({ {0, 0}, {1, 0}, {1, 1}, {0, 1} }); - + ASSERT_EQ(sh2.vertexCount(), 4u); - + // copy Item sh3 = sh2; - + ASSERT_EQ(sh3.vertexCount(), 4u); - + sh2 = {}; - + ASSERT_EQ(sh2.vertexCount(), 0u); ASSERT_EQ(sh3.vertexCount(), 4u); - + } TEST(GeometryAlgorithms, boundingCircle) { using namespace libnest2d; using placers::boundingCircle; - + PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}}; Circle c = boundingCircle(p); - + ASSERT_EQ(c.center().X, 0); ASSERT_EQ(c.center().Y, 0); ASSERT_DOUBLE_EQ(c.radius(), 10); - + shapelike::translate(p, PointImpl{10, 10}); c = boundingCircle(p); - + ASSERT_EQ(c.center().X, 10); ASSERT_EQ(c.center().Y, 10); ASSERT_DOUBLE_EQ(c.radius(), 10); - + auto parts = prusaParts(); - + int i = 0; for(auto& part : parts) { c = boundingCircle(part.transformedShape()); if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl; - + else for(auto v : shapelike::contour(part.transformedShape()) ) { - auto d = pointlike::distance(v, c.center()); - if(d > c.radius() ) { - auto e = std::abs( 1.0 - d/c.radius()); - ASSERT_LE(e, 1e-3); + auto d = pointlike::distance(v, c.center()); + if(d > c.radius() ) { + auto e = std::abs( 1.0 - d/c.radius()); + ASSERT_LE(e, 1e-3); + } } - } i++; } - + } TEST(GeometryAlgorithms, Distance) { using namespace libnest2d; - + Point p1 = {0, 0}; - + Point p2 = {10, 0}; Point p3 = {10, 10}; - + ASSERT_DOUBLE_EQ(pointlike::distance(p1, p2), 10); ASSERT_DOUBLE_EQ(pointlike::distance(p1, p3), sqrt(200)); - + Segment seg(p1, p3); - -// ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); - + + // ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); + auto result = pointlike::horizontalDistance(p2, seg); - + auto check = [](TCompute val, TCompute expected) { if(std::is_floating_point>::value) ASSERT_DOUBLE_EQ(static_cast(val), @@ -194,44 +190,44 @@ TEST(GeometryAlgorithms, Distance) { else ASSERT_EQ(val, expected); }; - + ASSERT_TRUE(result.second); check(result.first, 10); - + result = pointlike::verticalDistance(p2, seg); ASSERT_TRUE(result.second); check(result.first, -10); - + result = pointlike::verticalDistance(Point{10, 20}, seg); ASSERT_TRUE(result.second); check(result.first, 10); - - + + Point p4 = {80, 0}; Segment seg2 = { {0, 0}, {0, 40} }; - + result = pointlike::horizontalDistance(p4, seg2); - + ASSERT_TRUE(result.second); check(result.first, 80); - + result = pointlike::verticalDistance(p4, seg2); // Point should not be related to the segment ASSERT_FALSE(result.second); - + } TEST(GeometryAlgorithms, Area) { using namespace libnest2d; - + Rectangle rect(10, 10); - + ASSERT_EQ(rect.area(), 100); - + Rectangle rect2 = {100, 100}; - + ASSERT_EQ(rect2.area(), 10000); - + Item item = { {61, 97}, {70, 151}, @@ -242,33 +238,33 @@ TEST(GeometryAlgorithms, Area) { {61, 77}, {61, 97} }; - + ASSERT_TRUE(shapelike::area(item.transformedShape()) > 0 ); } TEST(GeometryAlgorithms, IsPointInsidePolygon) { using namespace libnest2d; - + Rectangle rect(10, 10); - + Point p = {1, 1}; - + ASSERT_TRUE(rect.isInside(p)); - + p = {11, 11}; - + ASSERT_FALSE(rect.isInside(p)); - - + + p = {11, 12}; - + ASSERT_FALSE(rect.isInside(p)); - - + + p = {3, 3}; - + ASSERT_TRUE(rect.isInside(p)); - + } //TEST(GeometryAlgorithms, Intersections) { @@ -294,21 +290,21 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) { using namespace libnest2d; using namespace libnest2d; - + Box bin(100, 100); BottomLeftPlacer placer(bin); - + Item item = {{70, 75}, {88, 60}, {65, 50}, {60, 30}, {80, 20}, {42, 20}, {35, 35}, {35, 55}, {40, 75}, {70, 75}}; - + Item leftControl = { {40, 75}, - {35, 55}, - {35, 35}, - {42, 20}, - {0, 20}, - {0, 75}, - {40, 75}}; - + {35, 55}, + {35, 35}, + {42, 20}, + {0, 20}, + {0, 75}, + {40, 75}}; + Item downControl = {{88, 60}, {88, 0}, {35, 0}, @@ -318,22 +314,22 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) {60, 30}, {65, 50}, {88, 60}}; - + Item leftp(placer.leftPoly(item)); - + ASSERT_TRUE(shapelike::isValid(leftp.rawShape()).first); ASSERT_EQ(leftp.vertexCount(), leftControl.vertexCount()); - + for(unsigned long i = 0; i < leftControl.vertexCount(); i++) { ASSERT_EQ(getX(leftp.vertex(i)), getX(leftControl.vertex(i))); ASSERT_EQ(getY(leftp.vertex(i)), getY(leftControl.vertex(i))); } - + Item downp(placer.downPoly(item)); - + ASSERT_TRUE(shapelike::isValid(downp.rawShape()).first); ASSERT_EQ(downp.vertexCount(), downControl.vertexCount()); - + for(unsigned long i = 0; i < downControl.vertexCount(); i++) { ASSERT_EQ(getX(downp.vertex(i)), getX(downControl.vertex(i))); ASSERT_EQ(getY(downp.vertex(i)), getY(downControl.vertex(i))); @@ -344,7 +340,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) TEST(GeometryAlgorithms, ArrangeRectanglesTight) { using namespace libnest2d; - + std::vector rects = { {80, 80}, {60, 90}, @@ -366,17 +362,17 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) {5, 5}, {5, 5}, {20, 20} }; - - + + Nester arrange(Box(210, 250)); - + auto groups = arrange(rects.begin(), rects.end()); - + ASSERT_EQ(groups.size(), 1u); ASSERT_EQ(groups[0].size(), rects.size()); - + // check for no intersections, no containment: - + for(auto result : groups) { bool valid = true; for(Item& r1 : result) { @@ -389,14 +385,14 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) } } } - + } TEST(GeometryAlgorithms, ArrangeRectanglesLoose) { using namespace libnest2d; - -// std::vector rects = { {40, 40}, {10, 10}, {20, 20} }; + + // std::vector rects = { {40, 40}, {10, 10}, {20, 20} }; std::vector rects = { {80, 80}, {60, 90}, @@ -418,17 +414,17 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) {5, 5}, {5, 5}, {20, 20} }; - + Coord min_obj_distance = 5; - + Nester arrange(Box(210, 250), - min_obj_distance); - + min_obj_distance); + auto groups = arrange(rects.begin(), rects.end()); - + ASSERT_EQ(groups.size(), 1u); ASSERT_EQ(groups[0].size(), rects.size()); - + // check for no intersections, no containment: auto result = groups[0]; bool valid = true; @@ -441,7 +437,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) } } } - + } namespace { @@ -449,68 +445,68 @@ using namespace libnest2d; template void exportSVG(std::vector>& result, const Bin& bin, int idx = 0) { - - + + std::string loc = "out"; - + static std::string svg_header = -R"raw( + R"raw( )raw"; - + int i = idx; auto r = result; -// for(auto r : result) { - std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out); - if(out.is_open()) { - out << svg_header; - Item rbin( Rectangle(bin.width(), bin.height()) ); - for(unsigned i = 0; i < rbin.vertexCount(); i++) { - auto v = rbin.vertex(i); - setY(v, -getY(v)/SCALE + 500 ); - setX(v, getX(v)/SCALE); - rbin.setVertex(i, v); - } - out << shapelike::serialize(rbin.rawShape()) << std::endl; - for(Item& sh : r) { - Item tsh(sh.transformedShape()); - for(unsigned i = 0; i < tsh.vertexCount(); i++) { - auto v = tsh.vertex(i); - setY(v, -getY(v)/SCALE + 500); - setX(v, getX(v)/SCALE); - tsh.setVertex(i, v); - } - out << shapelike::serialize(tsh.rawShape()) << std::endl; - } - out << "\n" << std::endl; + // for(auto r : result) { + std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out); + if(out.is_open()) { + out << svg_header; + Item rbin( Rectangle(bin.width(), bin.height()) ); + for(unsigned i = 0; i < rbin.vertexCount(); i++) { + auto v = rbin.vertex(i); + setY(v, -getY(v)/SCALE + 500 ); + setX(v, getX(v)/SCALE); + rbin.setVertex(i, v); } - out.close(); - -// i++; -// } + out << shapelike::serialize(rbin.rawShape()) << std::endl; + for(Item& sh : r) { + Item tsh(sh.transformedShape()); + for(unsigned i = 0; i < tsh.vertexCount(); i++) { + auto v = tsh.vertex(i); + setY(v, -getY(v)/SCALE + 500); + setX(v, getX(v)/SCALE); + tsh.setVertex(i, v); + } + out << shapelike::serialize(tsh.rawShape()) << std::endl; + } + out << "\n" << std::endl; + } + out.close(); + + // i++; + // } } } TEST(GeometryAlgorithms, BottomLeftStressTest) { using namespace libnest2d; - + const Coord SCALE = 1000000; auto& input = prusaParts(); - + Box bin(210*SCALE, 250*SCALE); BottomLeftPlacer placer(bin); - + auto it = input.begin(); auto next = it; int i = 0; while(it != input.end() && ++next != input.end()) { placer.pack(*it); placer.pack(*next); - + auto result = placer.getItems(); bool valid = true; - + if(result.size() == 2) { Item& r1 = result[0]; Item& r2 = result[1]; @@ -525,7 +521,7 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { std::cout << "something went terribly wrong!" << std::endl; FAIL(); } - + placer.clearItems(); it++; i++; @@ -534,9 +530,9 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { TEST(GeometryAlgorithms, convexHull) { using namespace libnest2d; - + ClipperLib::Path poly = PRINTER_PART_POLYGONS[0]; - + auto chull = sl::convexHull(poly); ASSERT_EQ(chull.size(), poly.size()); @@ -545,7 +541,7 @@ TEST(GeometryAlgorithms, convexHull) { TEST(GeometryAlgorithms, NestTest) { std::vector input = prusaParts(); - + PackGroup result = libnest2d::nest(input, Box(250000000, 210000000), [](unsigned cnt) { @@ -553,16 +549,17 @@ TEST(GeometryAlgorithms, NestTest) { << "parts left: " << cnt << std::endl; }); - + ASSERT_LE(result.size(), 2); - - int partsum = std::accumulate(result.begin(), - result.end(), - 0, - [](int s, - const decltype(result)::value_type &bin) { - return s += bin.size(); - }); + + size_t partsum = std::accumulate(result.begin(), + result.end(), + size_t(0), + [](int s, + const decltype( + result)::value_type &bin) { + return s += bin.size(); + }); ASSERT_EQ(input.size(), partsum); } @@ -647,7 +644,7 @@ std::vector nfp_testdata = { {118, 101}, {117, 103}, {117, 107} - }, + }, { {102, 116}, {111, 126}, @@ -658,7 +655,7 @@ std::vector nfp_testdata = { {147, 84}, {102, 84}, {102, 116}, - } + } }, { { @@ -674,7 +671,7 @@ std::vector nfp_testdata = { {108, 70}, {99, 102}, {99, 122}, - }, + }, { {107, 124}, {128, 125}, @@ -691,7 +688,7 @@ std::vector nfp_testdata = { {108, 85}, {107, 86}, {107, 124}, - } + } }, { { @@ -706,7 +703,7 @@ std::vector nfp_testdata = { {132, 57}, {91, 98}, {91, 100}, - }, + }, { {101, 90}, {103, 98}, @@ -724,74 +721,74 @@ std::vector nfp_testdata = { {102, 87}, {101, 89}, {101, 90}, - } + } } }; -std::vector nfp_concave_testdata = { - { // ItemPair - { - { - {533726, 142141}, - {532359, 143386}, - {530141, 142155}, - {528649, 160091}, - {533659, 157607}, - {538669, 160091}, - {537178, 142155}, - {534959, 143386}, - {533726, 142141}, - } - }, - { - { - {118305, 11603}, - {118311, 26616}, - {113311, 26611}, - {109311, 29604}, - {109300, 44608}, - {109311, 49631}, - {113300, 52636}, - {118311, 52636}, - {118308, 103636}, - {223830, 103636}, - {236845, 90642}, - {236832, 11630}, - {232825, 11616}, - {210149, 11616}, - {211308, 13625}, - {209315, 17080}, - {205326, 17080}, - {203334, 13629}, - {204493, 11616}, - {118305, 11603}, - } - }, - } + std::vector nfp_concave_testdata = { + { // ItemPair + { + { + {533726, 142141}, + {532359, 143386}, + {530141, 142155}, + {528649, 160091}, + {533659, 157607}, + {538669, 160091}, + {537178, 142155}, + {534959, 143386}, + {533726, 142141}, + } + }, + { + { + {118305, 11603}, + {118311, 26616}, + {113311, 26611}, + {109311, 29604}, + {109300, 44608}, + {109311, 49631}, + {113300, 52636}, + {118311, 52636}, + {118308, 103636}, + {223830, 103636}, + {236845, 90642}, + {236832, 11630}, + {232825, 11616}, + {210149, 11616}, + {211308, 13625}, + {209315, 17080}, + {205326, 17080}, + {203334, 13629}, + {204493, 11616}, + {118305, 11603}, + } + }, + } }; template void testNfp(const std::vector& testdata) { using namespace libnest2d; - + Box bin(210*SCALE, 250*SCALE); - + int testcase = 0; - + auto& exportfun = exportSVG; - + auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){ testcase++; - + orbiter.translate({210*SCALE, 0}); - + auto&& nfp = nfp::noFitPolygon(stationary.rawShape(), orbiter.transformedShape()); - + placers::correctNfpPosition(nfp, stationary, orbiter); - + auto valid = shapelike::isValid(nfp.first); - + /*Item infp(nfp.first); if(!valid.first) { std::cout << "test instance: " << testidx << " " @@ -799,46 +796,46 @@ void testNfp(const std::vector& testdata) { std::vector> inp = {std::ref(infp)}; exportfun(inp, bin, testidx); }*/ - + ASSERT_TRUE(valid.first); - + Item infp(nfp.first); - + int i = 0; auto rorbiter = orbiter.transformedShape(); auto vo = nfp::referenceVertex(rorbiter); - + ASSERT_TRUE(stationary.isInside(infp)); - + for(auto v : infp) { auto dx = getX(v) - getX(vo); auto dy = getY(v) - getY(vo); - + Item tmp = orbiter; - + tmp.translate({dx, dy}); - + bool touching = Item::touches(tmp, stationary); - + if(!touching || !valid.first) { std::vector> inp = { std::ref(stationary), std::ref(tmp), std::ref(infp) }; - + exportfun(inp, bin, testcase*i++); } - + ASSERT_TRUE(touching); } }; - + unsigned tidx = 0; for(auto& td : testdata) { auto orbiter = td.orbiter; auto stationary = td.stationary; onetest(orbiter, stationary, tidx++); } - + tidx = 0; for(auto& td : testdata) { auto orbiter = td.stationary; @@ -858,19 +855,19 @@ TEST(GeometryAlgorithms, nfpConvexConvex) { TEST(GeometryAlgorithms, pointOnPolygonContour) { using namespace libnest2d; - + Rectangle input(10, 10); - + placers::EdgeCache ecache(input); - + auto first = *input.begin(); ASSERT_TRUE(getX(first) == getX(ecache.coords(0))); ASSERT_TRUE(getY(first) == getY(ecache.coords(0))); - + auto last = *std::prev(input.end()); ASSERT_TRUE(getX(last) == getX(ecache.coords(1.0))); ASSERT_TRUE(getY(last) == getY(ecache.coords(1.0))); - + for(int i = 0; i <= 100; i++) { auto v = ecache.coords(i*(0.01)); ASSERT_TRUE(shapelike::touches(v, input.transformedShape())); @@ -879,24 +876,24 @@ TEST(GeometryAlgorithms, pointOnPolygonContour) { TEST(GeometryAlgorithms, mergePileWithPolygon) { using namespace libnest2d; - + Rectangle rect1(10, 15); Rectangle rect2(15, 15); Rectangle rect3(20, 15); - + rect2.translate({10, 0}); rect3.translate({25, 0}); - + TMultiShape pile; pile.push_back(rect1.transformedShape()); pile.push_back(rect2.transformedShape()); - + auto result = nfp::merge(pile, rect3.transformedShape()); - + ASSERT_EQ(result.size(), 1); - + Rectangle ref(45, 15); - + ASSERT_EQ(shapelike::area(result.front()), ref.area()); } @@ -908,7 +905,7 @@ long double refMinAreaBox(const PolygonImpl& p) { long double min_area = std::numeric_limits::max(); - + auto update_min = [&min_area, &it, &itx, &p]() { Segment s(*it, *itx); @@ -935,67 +932,39 @@ template struct BoostGCD { }; using Unit = int64_t; -using Ratio = boost::rational;// Rational; - -//double gteMinAreaBox(const PolygonImpl& p) { - -// using GteCoord = ClipperLib::cInt; -// using GtePoint = gte::Vector2; - -// gte::MinimumAreaBox2 mb; - -// std::vector points; -// points.reserve(p.Contour.size()); - -// for(auto& pt : p.Contour) points.emplace_back(GtePoint{GteCoord(pt.X), GteCoord(pt.Y)}); - -// mb(int(points.size()), points.data(), 0, nullptr, true); - -// auto min_area = double(mb.GetArea()); - -// return min_area; -//} +using Ratio = boost::rational; } TEST(RotatingCalipers, MinAreaBBCClk) { -// PolygonImpl poly({{-50, 30}, {-50, -50}, {50, -50}, {50, 50}, {-40, 50}}); - -// PolygonImpl poly({{-50, 0}, {50, 0}, {0, 100}}); - auto u = [](ClipperLib::cInt n) { return n*1000000; }; PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}}); - long double arearef = refMinAreaBox(poly); long double area = minAreaBoundingBox(poly).area(); -// double gtearea = gteMinAreaBox(poly); ASSERT_LE(std::abs(area - arearef), 500e6 ); -// ASSERT_LE(std::abs(gtearea - arearef), 500 ); -// ASSERT_DOUBLE_EQ(gtearea, arearef); } TEST(RotatingCalipers, AllPrusaMinBB) { - size_t idx = 0; + // /size_t idx = 0; long double err_epsilon = 500e6l; for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) { -// ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx]; -// rinput.pop_back(); -// std::reverse(rinput.begin(), rinput.end()); + // ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx]; + // rinput.pop_back(); + // std::reverse(rinput.begin(), rinput.end()); -// PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); + // PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); PolygonImpl poly(rinput); long double arearef = refMinAreaBox(poly); auto bb = minAreaBoundingBox(rinput); long double area = cast(bb.area()); -// double area = gteMinAreaBox(poly); bool succ = std::abs(arearef - area) < err_epsilon; - std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " - << arearef << " actual: " << area << std::endl; + // std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " +// << arearef << " actual: " << area << std::endl; ASSERT_TRUE(succ); } @@ -1006,21 +975,20 @@ TEST(RotatingCalipers, AllPrusaMinBB) { PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); - long double arearef = refMinAreaBox(poly); auto bb = minAreaBoundingBox(poly); long double area = cast(bb.area()); -// double area = gteMinAreaBox(poly); + bool succ = std::abs(arearef - area) < err_epsilon; - std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " - << arearef << " actual: " << area << std::endl; + // std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " +// << arearef << " actual: " << area << std::endl; ASSERT_TRUE(succ); } } int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index b261a79be8..df248ca5d9 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -7,6 +7,9 @@ #include // for std::forward #include +#include "libslic3r.h" +#include "Point.hpp" + namespace Slic3r { /// Handy little spin mutex for the cached meshes. @@ -248,6 +251,94 @@ template inline X ceil_i(X x, Y y) return (x % y) ? x / y + 1 : x / y; } +// A shorter C++14 style form of the enable_if metafunction +template +using enable_if_t = typename std::enable_if::type; + +// ///////////////////////////////////////////////////////////////////////////// +// Type safe conversions to and from scaled and unscaled coordinates +// ///////////////////////////////////////////////////////////////////////////// + +// A meta-predicate which is true for integers wider than or equal to coord_t +template struct is_scaled_coord +{ + static const SLIC3R_CONSTEXPR bool value = + std::is_integral::value && + std::numeric_limits::digits >= + std::numeric_limits::digits; +}; + +// Meta predicates for floating, 'scaled coord' and generic arithmetic types +template +using FloatingOnly = enable_if_t::value, T>; + +template +using ScaledCoordOnly = enable_if_t::value, T>; + +template +using ArithmeticOnly = enable_if_t::value, T>; + +// A shorter form for a generic Eigen vector which is widely used in PrusaSlicer +template +using EigenVec = Eigen::Matrix; + +// Semantics are the following: +// Upscaling (scaled()): only from floating point types (or Vec) to either +// floating point or integer 'scaled coord' coordinates. +// Downscaling (unscaled()): from arithmetic types (or Vec) to either +// floating point only + +// Conversion definition from unscaled to floating point scaled +template, + class = FloatingOnly> +inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_NOEXCEPT +{ + return static_cast(v / static_cast(SCALING_FACTOR)); +} + +// Conversion definition from unscaled to integer 'scaled coord'. +// TODO: is the rounding necessary ? Here it is to show that it can be different +// but it does not have to be. Using std::round means loosing noexcept and +// constexpr modifiers +template> +inline SLIC3R_CONSTEXPR ScaledCoordOnly scaled(const Tin &v) SLIC3R_NOEXCEPT +{ + //return static_cast(std::round(v / SCALING_FACTOR)); + return static_cast(v / static_cast(SCALING_FACTOR)); +} + +// Conversion for Eigen vectors (N dimensional points) +template> +inline EigenVec, N> scaled(const EigenVec &v) +{ + return v.template cast() / SCALING_FACTOR; +} + +// Conversion from arithmetic scaled type to floating point unscaled +template, + class = FloatingOnly> +inline SLIC3R_CONSTEXPR Tout unscaled(const Tin &v) SLIC3R_NOEXCEPT +{ + return static_cast(v * static_cast(SCALING_FACTOR)); +} + +// Unscaling for Eigen vectors. Input base type can be arithmetic, output base +// type can only be floating point. +template, + class = FloatingOnly> +inline SLIC3R_CONSTEXPR EigenVec unscaled( + const EigenVec &v) SLIC3R_NOEXCEPT +{ + return v.template cast() * SCALING_FACTOR; +} + } // namespace Slic3r #endif // MTUTILS_HPP diff --git a/src/libslic3r/MinAreaBoundingBox.cpp b/src/libslic3r/MinAreaBoundingBox.cpp index 6fc1b3327a..fafb54a585 100644 --- a/src/libslic3r/MinAreaBoundingBox.cpp +++ b/src/libslic3r/MinAreaBoundingBox.cpp @@ -39,7 +39,7 @@ template<> inline Slic3r::Points& contour(Slic3r::Polygon& sh) { return sh.point template<> inline const Slic3r::Points& contour(const Slic3r::Polygon& sh) { return sh.points; } template<> Slic3r::Points::iterator begin(Slic3r::Points& pts, const PathTag&) { return pts.begin();} -template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.begin(); } +template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.cbegin(); } template<> Slic3r::Points::iterator end(Slic3r::Points& pts, const PathTag&) { return pts.end();} template<> Slic3r::Points::const_iterator cend(const Slic3r::Points& pts, const PathTag&) { return pts.cend(); } @@ -71,62 +71,67 @@ using Rational = boost::rational<__int128>; MinAreaBoundigBox::MinAreaBoundigBox(const Polygon &p, PolygonLevel pc) { - const Polygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p); - - libnest2d::RotatedBox box = - libnest2d::minAreaBoundingBox(chull); - - m_right = box.right_extent(); - m_bottom = box.bottom_extent(); - m_axis = box.axis(); + const Polygon &chull = pc == pcConvex ? p : + libnest2d::sl::convexHull(p); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = libnest2d::cast(box.right_extent()); + m_bottom = libnest2d::cast(box.bottom_extent()); + m_axis = box.axis(); } MinAreaBoundigBox::MinAreaBoundigBox(const ExPolygon &p, PolygonLevel pc) { - const ExPolygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p); - - libnest2d::RotatedBox box = - libnest2d::minAreaBoundingBox(chull); - - m_right = box.right_extent(); - m_bottom = box.bottom_extent(); - m_axis = box.axis(); + const ExPolygon &chull = pc == pcConvex ? p : + libnest2d::sl::convexHull(p); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = libnest2d::cast(box.right_extent()); + m_bottom = libnest2d::cast(box.bottom_extent()); + m_axis = box.axis(); } MinAreaBoundigBox::MinAreaBoundigBox(const Points &pts, PolygonLevel pc) { - const Points& chull = pc == pcConvex ? pts : libnest2d::sl::convexHull(pts); - - libnest2d::RotatedBox box = - libnest2d::minAreaBoundingBox(chull); - - m_right = box.right_extent(); - m_bottom = box.bottom_extent(); - m_axis = box.axis(); + const Points &chull = pc == pcConvex ? pts : + libnest2d::sl::convexHull(pts); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = libnest2d::cast(box.right_extent()); + m_bottom = libnest2d::cast(box.bottom_extent()); + m_axis = box.axis(); } double MinAreaBoundigBox::angle_to_X() const { double ret = std::atan2(m_axis.y(), m_axis.x()); - auto s = std::signbit(ret); - if(s) ret += 2 * PI; + auto s = std::signbit(ret); + if (s) ret += 2 * PI; return -ret; } long double MinAreaBoundigBox::width() const { - return std::abs(m_bottom) / std::sqrt(libnest2d::pl::magnsq(m_axis)); + return std::abs(m_bottom) / + std::sqrt(libnest2d::pl::magnsq(m_axis)); } long double MinAreaBoundigBox::height() const { - return std::abs(m_right) / std::sqrt(libnest2d::pl::magnsq(m_axis)); + return std::abs(m_right) / + std::sqrt(libnest2d::pl::magnsq(m_axis)); } long double MinAreaBoundigBox::area() const { long double asq = libnest2d::pl::magnsq(m_axis); - return m_bottom * m_right / asq; + return m_bottom * m_right / asq; } void remove_collinear_points(Polygon &p) @@ -138,5 +143,4 @@ void remove_collinear_points(ExPolygon &p) { p = libnest2d::removeCollinearPoints(p, Unit(0)); } - -} +} // namespace Slic3r diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 36f7e39710..bc0f933d1d 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -610,7 +610,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, if(tolerance > EPSILON) { Polygons pp { p }; - pp = p.simplify(double(scaled(tolerance))); + pp = p.simplify(scaled(tolerance)); if (!pp.empty()) p = pp.front(); } diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 3b199c4ebb..04cbd78243 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -5,6 +5,7 @@ #include "SLABoostAdapter.hpp" #include "ClipperUtils.hpp" #include "Tesselate.hpp" +#include "MTUtils.hpp" // For debugging: //#include @@ -203,7 +204,7 @@ void offset(ExPolygon& sh, coord_t distance) { } ClipperOffset offs; - offs.ArcTolerance = 0.01*scaled(1.0); + offs.ArcTolerance = scaled(0.01); Paths result; offs.AddPath(ctour, jtRound, etClosedPolygon); offs.AddPaths(holes, jtRound, etClosedPolygon); @@ -351,7 +352,7 @@ Contour3D round_edges(const ExPolygon& base_plate, double x2 = xx*xx; double stepy = std::sqrt(r2 - x2); - offset(ob, s*scaled(xx)); + offset(ob, s * scaled(xx)); wh = ceilheight_mm - radius_mm + stepy; Contour3D pwalls; @@ -375,7 +376,7 @@ Contour3D round_edges(const ExPolygon& base_plate, double xx = radius_mm - i*stepx; double x2 = xx*xx; double stepy = std::sqrt(r2 - x2); - offset(ob, s*scaled(xx)); + offset(ob, s * scaled(xx)); wh = ceilheight_mm - radius_mm - stepy; Contour3D pwalls; @@ -476,7 +477,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, double dx = x(c) - x(cc), dy = y(c) - y(cc); double l = std::sqrt(dx * dx + dy * dy); double nx = dx / l, ny = dy / l; - double max_dist = scaled(max_dist_mm); + double max_dist = scaled(max_dist_mm); ExPolygon& expo = punion[idx++]; BoundingBox querybb(expo); @@ -492,7 +493,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, ctour.reserve(3); ctour.emplace_back(cc); - Point d(coord_t(scaled(1.)*nx), coord_t(scaled(1.)*ny)); + Point d(scaled(nx), scaled(ny)); ctour.emplace_back(c + Point( -y(d), x(d) )); ctour.emplace_back(c + Point( y(d), -x(d) )); offset(r, scaled(1.)); @@ -529,14 +530,14 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, ExPolygons tmp; tmp.reserve(count); for(ExPolygons& o : out) for(ExPolygon& e : o) { - auto&& exss = e.simplify(scaled(0.1)); + auto&& exss = e.simplify(scaled(0.1)); for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); } ExPolygons utmp = unify(tmp); for(auto& o : utmp) { - auto&& smp = o.simplify(scaled(0.1)); + auto&& smp = o.simplify(scaled(0.1)); output.insert(output.end(), smp.begin(), smp.end()); } } diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index c73ae5650d..7ae481ffbf 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -668,7 +668,7 @@ void SLAPrint::process() double ilhd = m_material_config.initial_layer_height.getFloat(); auto ilh = float(ilhd); - auto ilhs = scaled(ilhd); + coord_t ilhs = scaled(ilhd); const size_t objcount = m_objects.size(); static const unsigned min_objstatus = 0; // where the per object operations start @@ -694,17 +694,15 @@ void SLAPrint::process() // We need to prepare the slice index... - double lhd = m_objects.front()->m_config.layer_height.getFloat(); - float lh = float(lhd); - auto lhs = scaled(lhd); - - auto &&bb3d = mesh.bounding_box(); - double minZ = bb3d.min(Z) - po.get_elevation(); - double maxZ = bb3d.max(Z); - auto minZf = float(minZ); - - auto minZs = scaled(minZ); - auto maxZs = scaled(maxZ); + double lhd = m_objects.front()->m_config.layer_height.getFloat(); + float lh = float(lhd); + coord_t lhs = scaled(lhd); + auto && bb3d = mesh.bounding_box(); + double minZ = bb3d.min(Z) - po.get_elevation(); + double maxZ = bb3d.max(Z); + auto minZf = float(minZ); + coord_t minZs = scaled(minZ); + coord_t maxZs = scaled(maxZ); po.m_slice_index.clear(); @@ -1013,9 +1011,6 @@ void SLAPrint::process() using ClipperPolygons = std::vector; namespace sl = libnest2d::shapelike; // For algorithms - // If the raster has vertical orientation, we will flip the coordinates -// bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait; - // Set up custom union and diff functions for clipper polygons auto polyunion = [] (const ClipperPolygons& subjects) { @@ -1066,8 +1061,8 @@ void SLAPrint::process() const int fade_layers_cnt = m_default_object_config.faded_layers.getInt();// 10 // [3;20] - const double width = scaled(m_printer_config.display_width.getFloat()); - const double height = scaled(m_printer_config.display_height.getFloat()); + const auto width = scaled(m_printer_config.display_width.getFloat()); + const auto height = scaled(m_printer_config.display_height.getFloat()); const double display_area = width*height; // get polygons for all instances in the object @@ -1123,11 +1118,6 @@ void SLAPrint::process() sl::translate(poly, ClipperPoint{instances[i].shift(X), instances[i].shift(Y)}); -// if (flpXY) { -// for(auto& p : poly.Contour) std::swap(p.X, p.Y); -// for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y); -// } - polygons.emplace_back(std::move(poly)); } } diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 8cafae17cf..dc2b6a4ec0 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -61,20 +61,6 @@ typedef double coordf_t; #define SLIC3R_NOEXCEPT noexcept #endif -template inline SLIC3R_CONSTEXPR coord_t scaled(Tf val) -{ - static_assert (std::is_floating_point::value, "Floating point only"); - return coord_t(val / Tf(SCALING_FACTOR)); -} - -template inline SLIC3R_CONSTEXPR Tf unscaled(coord_t val) -{ - static_assert (std::is_floating_point::value, "Floating point only"); - return Tf(val * Tf(SCALING_FACTOR)); -} - -inline SLIC3R_CONSTEXPR float unscaledf(coord_t val) { return unscaled(val); } - inline std::string debug_out_path(const char *name, ...) { char buffer[2048]; From 6ff434aba3367e2111b1ff809ea744ca39347a56 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Jun 2019 11:10:41 +0200 Subject: [PATCH 204/627] Fixes some ModelArrange warnings --- src/libslic3r/MTUtils.hpp | 9 --------- src/libslic3r/ModelArrange.cpp | 18 ++++++++++-------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index df248ca5d9..70603cd15f 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -242,15 +242,6 @@ template bool all_of(const C &container) }); } -template inline X ceil_i(X x, Y y) -{ - static_assert(std::is_integral::value && - std::is_integral::value && sizeof(X) >= sizeof(Y), - ""); - - return (x % y) ? x / y + 1 : x / y; -} - // A shorter C++14 style form of the enable_if metafunction template using enable_if_t = typename std::enable_if::type; diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index bc0f933d1d..db36653b0f 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -62,10 +62,10 @@ std::string toString(const Model& model, bool holes = true) { objinst->transform_mesh(&tmpmesh); ExPolygons expolys = tmpmesh.horizontal_projection(); for(auto& expoly_complex : expolys) { - - auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR); + + ExPolygons tmp = expoly_complex.simplify(scaled(1.)); if(tmp.empty()) continue; - auto expoly = tmp.front(); + ExPolygon expoly = tmp.front(); expoly.contour.make_clockwise(); for(auto& h : expoly.holes) h.make_counter_clockwise(); @@ -633,8 +633,8 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, if(item.vertexCount() > 3) { item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation())); item.translation({ - ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), - ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) + scaled(objinst->get_offset(X)), + scaled(objinst->get_offset(Y)) }); ret.emplace_back(objinst, item); } @@ -658,8 +658,8 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, Item item(std::move(pn)); item.rotation(wti.rotation), item.translation({ - ClipperLib::cInt(wti.pos(0)/SCALING_FACTOR), - ClipperLib::cInt(wti.pos(1)/SCALING_FACTOR) + scaled(wti.pos(0)), + scaled(wti.pos(1)) }); ret.emplace_back(nullptr, item); } @@ -822,7 +822,9 @@ bool arrange(Model &model, // The model with the geometries auto& cfn = stopcondition; - coord_t md = ceil_i(min_obj_distance, 2) - SCALED_EPSILON; + // Integer ceiling the min distance from the bed perimeters + coord_t md = min_obj_distance - SCALED_EPSILON; + md = (md % 2) ? md / 2 + 1 : md / 2; auto binbb = Box({libnest2d::Coord{bbb.min(0)} - md, libnest2d::Coord{bbb.min(1)} - md}, From d845332de1e393e7a0d7f37379f6de0a31b4ee82 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 26 Jun 2019 11:49:02 +0200 Subject: [PATCH 205/627] Fixed a crash when using place to bed function with the layer editing active This was caused by trying to render a deleted layer height profile. Other gizmos were not affected because they are not dragging at the time of their action, so the profile was correctly recalculated for them. --- src/slic3r/GUI/GLCanvas3D.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c828e0ec66..bed3b754b3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -457,8 +457,10 @@ void GLCanvas3D::LayersEditing::_render_profile(const Rect& bar_rect) const { //FIXME show some kind of legend. + if (!m_slicing_parameters) + return; + // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. - assert(m_slicing_parameters != nullptr); float scale_x = bar_rect.get_width() / (float)(1.12 * m_slicing_parameters->max_layer_height); float scale_y = bar_rect.get_height() / m_object_max_z; float x = bar_rect.get_left() + (float)m_slicing_parameters->layer_height * scale_x; From f4ed0d81378d4272c20f23ca70a98134079b2e2c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Jun 2019 11:16:20 +0200 Subject: [PATCH 206/627] Working on `arrange selection only` feature. revert changes related to scale/unscale --- src/libslic3r/ModelArrange.cpp | 238 ++++++++++++++------------------- 1 file changed, 104 insertions(+), 134 deletions(-) diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index d3586651b6..4a3730847b 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -667,13 +667,12 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, return ret; } -// Apply the calculated translations and rotations (currently disabled) to the -// Model object instances. -void applyResult( - IndexedPackGroup::value_type& group, - Coord batch_offset, - ShapeData2D& shapemap, - WipeTowerInfo& wti) +// Apply the calculated translations and rotations (currently disabled) to +// the Model object instances. +void applyResult(IndexedPackGroup::value_type &group, + ClipperLib::cInt batch_offset, + ShapeData2D & shapemap, + WipeTowerInfo & wti) { for(auto& r : group) { auto idx = r.first; // get the original item index @@ -686,15 +685,15 @@ void applyResult( // appropriately auto off = item.translation(); Radians rot = item.rotation(); - - Vec3d foff(off.X*SCALING_FACTOR + batch_offset, - off.Y*SCALING_FACTOR, + + Vec3d foff(unscaled(off.X + batch_offset) , + unscaled(off.Y), inst_ptr ? inst_ptr->get_offset()(Z) : 0.); - if (inst_ptr) { - // write the transformation data into the model instance - inst_ptr->set_rotation(Z, rot); - inst_ptr->set_offset(foff); + if (inst_ptr) { + // write the transformation data into the model instance + inst_ptr->set_rotation(Z, rot); + inst_ptr->set_offset(foff); } else { // this is the wipe tower - we will modify the struct with the info // and leave it up to the called to actually move the wipe tower @@ -785,6 +784,72 @@ BedShapeHint bedShape(const Polyline &bed) { static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; +template +IndexedPackGroup _arrange(std::vector> &shapes, + const BinT & bin, + coord_t minobjd, + std::function prind, + std::function stopfn) +{ + AutoArranger arranger{bin, minobjd, prind, stopfn}; + return arranger(shapes.begin(), shapes.end()); +} + +template +IndexedPackGroup _arrange(std::vector> &shapes, + const PackGroup & preshapes, + std::vector &minstances, + const BinT & bin, + coord_t minobjd) +{ + + auto binbb = sl::boundingBox(bin); + + AutoArranger arranger{bin, minobjd}; + + if(!preshapes.front().empty()) { // If there is something on the plate + arranger.preload(preshapes); + + // Try to put the first item to the center, as the arranger will not + // do this for us. + auto shptrit = minstances.begin(); + for(auto shit = shapes.begin(); shit != shapes.end(); ++shit, ++shptrit) + { + // Try to place items to the center + Item& itm = *shit; + auto ibb = itm.boundingBox(); + auto d = binbb.center() - ibb.center(); + itm.translate(d); + if(!arranger.is_colliding(itm)) { + arranger.preload({{itm}}); + + auto offset = itm.translation(); + Radians rot = itm.rotation(); + ModelInstance *minst = *shptrit; + + Vec3d foffset(unscaled(offset.X), + unscaled(offset.Y), + minst->get_offset()(Z)); + + // write the transformation data into the model instance + minst->set_rotation(Z, rot); + minst->set_offset(foffset); + + shit = shapes.erase(shit); + shptrit = minstances.erase(shptrit); + break; + } + } + } + + return arranger(shapes.begin(), shapes.end()); +} + +inline SLIC3R_CONSTEXPR libnest2d::Coord stride_padding(Coord w) +{ + return w + w / 5; +} + // The final client function to arrange the Model. A progress indicator and // a stop predicate can be also be passed to control the process. bool arrange(Model &model, // The model with the geometries @@ -826,44 +891,28 @@ bool arrange(Model &model, // The model with the geometries coord_t md = min_obj_distance - SCALED_EPSILON; md = (md % 2) ? md / 2 + 1 : md / 2; - auto binbb = Box({libnest2d::Coord{bbb.min(0)} - md, - libnest2d::Coord{bbb.min(1)} - md}, - {libnest2d::Coord{bbb.max(0)} + md, - libnest2d::Coord{bbb.max(1)} + md}); + auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, + ClipperLib::cInt{bbb.min(1)} - md}, + {ClipperLib::cInt{bbb.max(0)} + md, + ClipperLib::cInt{bbb.max(1)} + md}); switch(bedhint.type) { case BedShapeType::BOX: { - // Create the arranger for the box shaped bed - AutoArranger arrange(binbb, min_obj_distance, progressind, cfn); - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); + result = _arrange(shapes, binbb, min_obj_distance, progressind, cfn); break; } case BedShapeType::CIRCLE: { - auto c = bedhint.shape.circ; auto cc = to_lnCircle(c); - - AutoArranger arrange(cc, min_obj_distance, progressind, cfn); - result = arrange(shapes.begin(), shapes.end()); + result = _arrange(shapes, cc, min_obj_distance, progressind, cfn); break; } case BedShapeType::IRREGULAR: case BedShapeType::WHO_KNOWS: { - - using P = libnest2d::PolygonImpl; - auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); - P irrbed = sl::create(std::move(ctour)); - - AutoArranger

arrange(irrbed, min_obj_distance, progressind, cfn); - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); + ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); + result = _arrange(shapes, irrbed, min_obj_distance, progressind, cfn); break; } }; @@ -873,12 +922,9 @@ bool arrange(Model &model, // The model with the geometries if(first_bin_only) { applyResult(result.front(), 0, shapemap, wti); } else { - - const auto STRIDE_PADDING = 1.2; - - Coord stride = static_cast(STRIDE_PADDING* - binbb.width()*SCALING_FACTOR); - Coord batch_offset = 0; + + ClipperLib::cInt stride = stride_padding(binbb.width()); + ClipperLib::cInt batch_offset = 0; for(auto& group : result) { applyResult(group, batch_offset, shapemap, wti); @@ -904,7 +950,7 @@ void find_new_position(const Model &model, // Get the 2D projected shapes with their 3D model instance pointers auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); - // Copy the references for the shapes only as the arranger expects a + // Copy the references for the shapes only, as the arranger expects a // sequence of objects convertible to Item or ClipperPolygon PackGroup preshapes; preshapes.emplace_back(); ItemGroup shapes; @@ -922,12 +968,15 @@ void find_new_position(const Model &model, coord_t md = min_obj_distance - SCALED_EPSILON; md = (md % 2) ? md / 2 + 1 : md / 2; - auto binbb = Box({libnest2d::Coord{bbb.min(0)} - md, - libnest2d::Coord{bbb.min(1)} - md}, - {libnest2d::Coord{bbb.max(0)} + md, - libnest2d::Coord{bbb.max(1)} + md}); + auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, + ClipperLib::cInt{bbb.min(1)} - md}, + {ClipperLib::cInt{bbb.max(0)} + md, + ClipperLib::cInt{bbb.max(1)} + md}); for(auto it = shapemap.begin(); it != shapemap.end(); ++it) { + // `toadd` vector contains the instance pointers which have to be + // considered by arrange. If `it` points to an ModelInstance, which + // is NOT in `toadd`, add it to preshapes. if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end()) { if(it->second.isInside(binbb)) // just ignore items which are outside preshapes.front().emplace_back(std::ref(it->second)); @@ -938,101 +987,23 @@ void find_new_position(const Model &model, } } - auto try_first_to_center = [&shapes, &shapes_ptr, &binbb] - (std::function is_colliding, - std::function preload) - { - // Try to put the first item to the center, as the arranger will not - // do this for us. - auto shptrit = shapes_ptr.begin(); - for(auto shit = shapes.begin(); shit != shapes.end(); ++shit, ++shptrit) - { - // Try to place items to the center - Item& itm = *shit; - auto ibb = itm.boundingBox(); - auto d = binbb.center() - ibb.center(); - itm.translate(d); - if(!is_colliding(itm)) { - preload(itm); - - auto offset = itm.translation(); - Radians rot = itm.rotation(); - ModelInstance *minst = *shptrit; - Vec3d foffset(offset.X*SCALING_FACTOR, - offset.Y*SCALING_FACTOR, - minst->get_offset()(Z)); - - // write the transformation data into the model instance - minst->set_rotation(Z, rot); - minst->set_offset(foffset); - - shit = shapes.erase(shit); - shptrit = shapes_ptr.erase(shptrit); - break; - } - } - }; - switch(bedhint.type) { case BedShapeType::BOX: { - // Create the arranger for the box shaped bed - AutoArranger arrange(binbb, min_obj_distance); - - if(!preshapes.front().empty()) { // If there is something on the plate - arrange.preload(preshapes); - try_first_to_center( - [&arrange](const Item& itm) {return arrange.is_colliding(itm);}, - [&arrange](Item& itm) { arrange.preload({{itm}}); } - ); - } - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); + result = _arrange(shapes, preshapes, shapes_ptr, binbb, min_obj_distance); break; } case BedShapeType::CIRCLE: { - auto c = bedhint.shape.circ; auto cc = to_lnCircle(c); - - // Create the arranger for the box shaped bed - AutoArranger arrange(cc, min_obj_distance); - - if(!preshapes.front().empty()) { // If there is something on the plate - arrange.preload(preshapes); - try_first_to_center( - [&arrange](const Item& itm) {return arrange.is_colliding(itm);}, - [&arrange](Item& itm) { arrange.preload({{itm}}); } - ); - } - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); + result = _arrange(shapes, preshapes, shapes_ptr, cc, min_obj_distance); break; } case BedShapeType::IRREGULAR: case BedShapeType::WHO_KNOWS: { - using P = libnest2d::PolygonImpl; - auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); - P irrbed = sl::create(std::move(ctour)); - - AutoArranger

arrange(irrbed, min_obj_distance); - - if(!preshapes.front().empty()) { // If there is something on the plate - arrange.preload(preshapes); - try_first_to_center( - [&arrange](const Item& itm) {return arrange.is_colliding(itm);}, - [&arrange](Item& itm) { arrange.preload({{itm}}); } - ); - } - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); + ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); + result = _arrange(shapes, preshapes, shapes_ptr, irrbed, min_obj_distance); break; } }; @@ -1040,9 +1011,8 @@ void find_new_position(const Model &model, // Now we go through the result which will contain the fixed and the moving // polygons as well. We will have to search for our item. - const auto STRIDE_PADDING = 1.2; - Coord stride = Coord(STRIDE_PADDING*binbb.width()*SCALING_FACTOR); - Coord batch_offset = 0; + ClipperLib::cInt stride = stride_padding(binbb.width()); + ClipperLib::cInt batch_offset = 0; for(auto& group : result) { for(auto& r : group) if(r.first < shapes.size()) { From d2136ab6253ced47b6ccca2a9256fb265083d734 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 26 Jun 2019 13:12:25 +0200 Subject: [PATCH 207/627] ObjectList no longer caps number of extruders to 9 (fixes https://github.com/prusa3d/PrusaSlicer/issues/2558) --- src/slic3r/GUI/GUI_ObjectList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d7d1a1af7b..e926dcf978 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -460,7 +460,7 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item) if (!m_config || selection.empty()) return; - const int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str()); + const int extruder = /*selection.size() > 1 ? 0 : */atoi(selection.c_str()); m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); // update scene From 7e52edb88c411339a0203df3049029f4394b8d7d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Jun 2019 13:23:08 +0200 Subject: [PATCH 208/627] Try to supress warnings from bundled IGL under Windows. --- src/libigl/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libigl/CMakeLists.txt b/src/libigl/CMakeLists.txt index 0852fad729..3daac757b1 100644 --- a/src/libigl/CMakeLists.txt +++ b/src/libigl/CMakeLists.txt @@ -10,5 +10,5 @@ if(libigl_FOUND) target_link_libraries(libigl INTERFACE igl::core) else() message(STATUS "IGL NOT found, using bundled version...") - target_include_directories(libigl INTERFACE SYSTEM ${LIBDIR}/libigl) + target_include_directories(libigl SYSTEM BEFORE INTERFACE ${LIBDIR}/libigl) endif() From a710e7e7e4392e1be26db508784b2a35c1e75743 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 26 Jun 2019 13:26:49 +0200 Subject: [PATCH 209/627] WIP: Undo / Redo stack. Integration of the "cereal" serialization library. Serialization / deserialization of the DynamicConfig / DynamicPrintConfig. DynamicPrintConfig serializes ordinal identifiers instead of the option key strings to conserve space. --- CMakeLists.txt | 4 + deps/CMakeLists.txt | 2 + deps/deps-unix-common.cmake | 10 ++ deps/deps-windows.cmake | 14 +++ src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/Config.cpp | 153 ++++++++++++++++++------- src/libslic3r/Config.hpp | 191 ++++++++++++++++++++++++++------ src/libslic3r/Format/3mf.cpp | 6 +- src/libslic3r/Format/AMF.cpp | 8 +- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/Layer.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 8 +- src/libslic3r/PrintConfig.hpp | 36 ++++++ src/slic3r/CMakeLists.txt | 2 +- src/slic3r/GUI/PresetBundle.cpp | 2 +- 15 files changed, 361 insertions(+), 80 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba264c8c36..9d6d754e2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -344,6 +344,10 @@ if (NOT GLEW_FOUND) endif () include_directories(${GLEW_INCLUDE_DIRS}) +# Find the Cereal serialization library +add_library(cereal INTERFACE) +target_include_directories(cereal INTERFACE include) + # l10n set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/localization") add_custom_target(pot diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 5bc33c896d..1c468607eb 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -88,6 +88,7 @@ if (MSVC) dep_libcurl dep_wxwidgets dep_gtest + dep_cereal dep_nlopt # dep_qhull # Experimental dep_zlib # on Windows we still need zlib @@ -102,6 +103,7 @@ else() dep_libcurl dep_wxwidgets dep_gtest + dep_cereal dep_nlopt dep_qhull dep_libigl diff --git a/deps/deps-unix-common.cmake b/deps/deps-unix-common.cmake index c44a6ec205..6d9d6fd75f 100644 --- a/deps/deps-unix-common.cmake +++ b/deps/deps-unix-common.cmake @@ -19,6 +19,16 @@ ExternalProject_Add(dep_gtest CMAKE_ARGS -DBUILD_GMOCK=OFF ${DEP_CMAKE_OPTS} -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local ) +ExternalProject_Add(dep_cereal + EXCLUDE_FROM_ALL 1 + URL "https://github.com/USCiLab/cereal/archive/v1.2.2.tar.gz" +# URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae + CMAKE_ARGS + -DJUST_INSTALL_CEREAL=on + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + ${DEP_CMAKE_OPTS} +) + ExternalProject_Add(dep_nlopt EXCLUDE_FROM_ALL 1 URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz" diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index d7daf84253..2595f94d8c 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -115,6 +115,20 @@ if (${DEP_DEBUG}) endif () +ExternalProject_Add(dep_cereal + EXCLUDE_FROM_ALL 1 + URL "https://github.com/USCiLab/cereal/archive/v1.2.2.tar.gz" +# URL_HASH SHA256=c6dd7a5701fff8ad5ebb45a3dc8e757e61d52658de3918e38bab233e7fd3b4ae + CMAKE_GENERATOR "${DEP_MSVC_GEN}" + CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" + CMAKE_ARGS + -DJUST_INSTALL_CEREAL=on + "-DCMAKE_INSTALL_PREFIX:PATH=${DESTDIR}\\usr\\local" + BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj + INSTALL_COMMAND "" +) + + ExternalProject_Add(dep_nlopt EXCLUDE_FROM_ALL 1 URL "https://github.com/stevengj/nlopt/archive/v2.5.0.tar.gz" diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index dc52257aa2..e1423b1e1a 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -189,6 +189,7 @@ target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNE target_link_libraries(libslic3r libnest2d admesh + cereal libigl miniz boost_libs diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 0738b77c6a..794beff215 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -209,6 +209,51 @@ std::vector ConfigOptionDef::cli_args(const std::string &key) const return args; } +ConfigOption* ConfigOptionDef::create_empty_option() const +{ + switch (this->type) { + case coFloat: return new ConfigOptionFloat(); + case coFloats: return new ConfigOptionFloats(); + case coInt: return new ConfigOptionInt(); + case coInts: return new ConfigOptionInts(); + case coString: return new ConfigOptionString(); + case coStrings: return new ConfigOptionStrings(); + case coPercent: return new ConfigOptionPercent(); + case coPercents: return new ConfigOptionPercents(); + case coFloatOrPercent: return new ConfigOptionFloatOrPercent(); + case coPoint: return new ConfigOptionPoint(); + case coPoints: return new ConfigOptionPoints(); + case coPoint3: return new ConfigOptionPoint3(); +// case coPoint3s: return new ConfigOptionPoint3s(); + case coBool: return new ConfigOptionBool(); + case coBools: return new ConfigOptionBools(); + case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map); + default: throw std::runtime_error(std::string("Unknown option type for option ") + this->label); + } +} + +ConfigOption* ConfigOptionDef::create_default_option() const +{ + if (this->default_value) + return (this->default_value->type() == coEnum) ? + // Special case: For a DynamicConfig, convert a templated enum to a generic enum. + new ConfigOptionEnumGeneric(this->enum_keys_map, this->default_value->getInt()) : + this->default_value->clone(); + return this->create_empty_option(); +} + +// Assignment of the serialization IDs is not thread safe. The Defs shall be initialized from the main thread! +ConfigOptionDef* ConfigDef::add(const t_config_option_key &opt_key, ConfigOptionType type) +{ + static size_t serialization_key_ordinal_last = 0; + ConfigOptionDef *opt = &this->options[opt_key]; + opt->opt_key = opt_key; + opt->type = type; + opt->serialization_key_ordinal = ++ serialization_key_ordinal_last; + this->by_serialization_key_ordinal[opt->serialization_key_ordinal] = opt; + return opt; +} + std::string ConfigOptionDef::nocli = "~~~noCLI"; std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function filter) const @@ -358,7 +403,7 @@ t_config_option_keys ConfigBase::equal(const ConfigBase &other) const return equal; } -std::string ConfigBase::serialize(const t_config_option_key &opt_key) const +std::string ConfigBase::opt_serialize(const t_config_option_key &opt_key) const { const ConfigOption* opt = this->option(opt_key); assert(opt != nullptr); @@ -469,7 +514,7 @@ void ConfigBase::setenv_() const for (size_t i = 0; i < envname.size(); ++i) envname[i] = (envname[i] <= 'z' && envname[i] >= 'a') ? envname[i]-('a'-'A') : envname[i]; - boost::nowide::setenv(envname.c_str(), this->serialize(*it).c_str(), 1); + boost::nowide::setenv(envname.c_str(), this->opt_serialize(*it).c_str(), 1); } } @@ -593,16 +638,16 @@ void ConfigBase::save(const std::string &file) const c.open(file, std::ios::out | std::ios::trunc); c << "# " << Slic3r::header_slic3r_generated() << std::endl; for (const std::string &opt_key : this->keys()) - c << opt_key << " = " << this->serialize(opt_key) << std::endl; + c << opt_key << " = " << this->opt_serialize(opt_key) << std::endl; c.close(); } bool DynamicConfig::operator==(const DynamicConfig &rhs) const { - t_options_map::const_iterator it1 = this->options.begin(); - t_options_map::const_iterator it1_end = this->options.end(); - t_options_map::const_iterator it2 = rhs.options.begin(); - t_options_map::const_iterator it2_end = rhs.options.end(); + auto it1 = this->options.begin(); + auto it1_end = this->options.end(); + auto it2 = rhs.options.begin(); + auto it2_end = rhs.options.end(); for (; it1 != it1_end && it2 != it2_end; ++ it1, ++ it2) if (it1->first != it2->first || *it1->second != *it2->second) // key or value differ @@ -612,10 +657,10 @@ bool DynamicConfig::operator==(const DynamicConfig &rhs) const ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) { - t_options_map::iterator it = options.find(opt_key); + auto it = options.find(opt_key); if (it != options.end()) // Option was found. - return it->second; + return it->second.get(); if (! create) // Option was not found and a new option shall not be created. return nullptr; @@ -628,34 +673,8 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre // throw std::runtime_error(std::string("Invalid option name: ") + opt_key); // Let the parent decide what to do if the opt_key is not defined by this->def(). return nullptr; - ConfigOption *opt = nullptr; - if (optdef->default_value) { - opt = (optdef->default_value->type() == coEnum) ? - // Special case: For a DynamicConfig, convert a templated enum to a generic enum. - new ConfigOptionEnumGeneric(optdef->enum_keys_map, optdef->default_value->getInt()) : - optdef->default_value->clone(); - } else { - switch (optdef->type) { - case coFloat: opt = new ConfigOptionFloat(); break; - case coFloats: opt = new ConfigOptionFloats(); break; - case coInt: opt = new ConfigOptionInt(); break; - case coInts: opt = new ConfigOptionInts(); break; - case coString: opt = new ConfigOptionString(); break; - case coStrings: opt = new ConfigOptionStrings(); break; - case coPercent: opt = new ConfigOptionPercent(); break; - case coPercents: opt = new ConfigOptionPercents(); break; - case coFloatOrPercent: opt = new ConfigOptionFloatOrPercent(); break; - case coPoint: opt = new ConfigOptionPoint(); break; - case coPoints: opt = new ConfigOptionPoints(); break; - case coPoint3: opt = new ConfigOptionPoint3(); break; - // case coPoint3s: opt = new ConfigOptionPoint3s(); break; - case coBool: opt = new ConfigOptionBool(); break; - case coBools: opt = new ConfigOptionBools(); break; - case coEnum: opt = new ConfigOptionEnumGeneric(optdef->enum_keys_map); break; - default: throw std::runtime_error(std::string("Unknown option type for option ") + opt_key); - } - } - this->options[opt_key] = opt; + ConfigOption *opt = optdef->create_default_option(); + this->options.insert(it, std::make_pair(opt_key, opt)); return opt; } @@ -802,3 +821,63 @@ t_config_option_keys StaticConfig::keys() const } } + +CEREAL_REGISTER_TYPE(Slic3r::ConfigOption) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVectorBase) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloat) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloats) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInt) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInts) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionString) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionStrings) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercent) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercents) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatOrPercent) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoints) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint3) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBool) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBools) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionEnumGeneric) +CEREAL_REGISTER_TYPE(Slic3r::ConfigBase) +CEREAL_REGISTER_TYPE(Slic3r::DynamicConfig) + +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionSingle) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOption, Slic3r::ConfigOptionVectorBase) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionFloat) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionFloats) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionInt) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionInts) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionString) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionStrings) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloat, Slic3r::ConfigOptionPercent) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercents) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionPercent, Slic3r::ConfigOptionFloatOrPercent) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionPoint) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionPoints) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionPoint3) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionBool) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionBools) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionInt, Slic3r::ConfigOptionEnumGeneric) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigBase, Slic3r::DynamicConfig) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index ee4bc4e463..7b3c5c73aa 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -18,6 +18,12 @@ #include #include +#include +#include +#include +#include +#include + namespace Slic3r { // Name of the configuration option. @@ -152,6 +158,10 @@ public: bool operator==(const T &rhs) const { return this->value == rhs; } bool operator!=(const T &rhs) const { return this->value != rhs; } + +private: + friend class cereal::access; + template void serialize(Archive & ar) { ar(this->value); } }; // Value of a vector valued option (bools, ints, floats, strings, points) @@ -290,6 +300,10 @@ public: bool operator==(const std::vector &rhs) const { return this->values == rhs; } bool operator!=(const std::vector &rhs) const { return this->values != rhs; } + +private: + friend class cereal::access; + template void serialize(Archive & ar) { ar(this->values); } }; class ConfigOptionFloat : public ConfigOptionSingle @@ -324,6 +338,10 @@ public: this->set(opt); return *this; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionFloats : public ConfigOptionVector @@ -382,6 +400,10 @@ public: this->set(opt); return *this; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionInt : public ConfigOptionSingle @@ -418,6 +440,10 @@ public: this->set(opt); return *this; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionInts : public ConfigOptionVector @@ -468,6 +494,10 @@ public: } return true; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionString : public ConfigOptionSingle @@ -492,6 +522,10 @@ public: UNUSED(append); return unescape_string_cstyle(str, this->value); } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; // semicolon-separated strings @@ -526,6 +560,10 @@ public: this->values.clear(); return unescape_strings_cstyle(str, this->values); } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionPercent : public ConfigOptionFloat @@ -558,6 +596,10 @@ public: iss >> this->value; return !iss.fail(); } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class(this)); } }; class ConfigOptionPercents : public ConfigOptionFloats @@ -612,6 +654,10 @@ public: } return true; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class(this)); } }; class ConfigOptionFloatOrPercent : public ConfigOptionPercent @@ -661,6 +707,10 @@ public: iss >> this->value; return !iss.fail(); } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class(this), percent); } }; class ConfigOptionPoint : public ConfigOptionSingle @@ -691,6 +741,10 @@ public: return sscanf(str.data(), " %lf , %lf %c", &this->value(0), &this->value(1), &dummy) == 2 || sscanf(str.data(), " %lf x %lf %c", &this->value(0), &this->value(1), &dummy) == 2; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(this->value.x(), this->value.y()); } }; class ConfigOptionPoints : public ConfigOptionVector @@ -750,8 +804,21 @@ public: } return true; } -}; +private: + friend class cereal::access; + template void save(Archive& archive) const { + size_t cnt = this->values.size(); + archive(cnt); + archive.saveBinary((const char*)this->values.data(), sizeof(Vec2d) * cnt); + } + template void load(Archive& archive) { + size_t cnt; + archive(cnt); + this->values.assign(cnt, Vec2d()); + archive.loadBinary((char*)this->values.data(), sizeof(Vec2d) * cnt); + } +}; class ConfigOptionPoint3 : public ConfigOptionSingle { @@ -783,6 +850,10 @@ public: return sscanf(str.data(), " %lf , %lf , %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 2 || sscanf(str.data(), " %lf x %lf x %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 2; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(this->value.x(), this->value.y(), this->value.z()); } }; class ConfigOptionBool : public ConfigOptionSingle @@ -809,6 +880,10 @@ public: this->value = (str.compare("1") == 0); return true; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionBools : public ConfigOptionVector @@ -864,6 +939,10 @@ public: } return true; } + +private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; // Map from an enum integer value to an enum name. @@ -1002,19 +1081,73 @@ public: this->value = it->second; return true; } + +private: + friend class cereal::access; + template void serialize(Archive& ar) { ar(cereal::base_class(this)); } }; // Definition of a configuration value for the purpose of GUI presentation, editing, value mapping and config file handling. class ConfigOptionDef { public: + // Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map. + t_config_option_key opt_key; // What type? bool, int, string etc. ConfigOptionType type = coNone; // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor. Slic3r::clonable_ptr default_value; - void set_default_value(const ConfigOption* ptr) { this->default_value = Slic3r::clonable_ptr(ptr); } - template - const T* get_default_value() const { return static_cast(this->default_value.get()); } + void set_default_value(const ConfigOption* ptr) { this->default_value = Slic3r::clonable_ptr(ptr); } + template const T* get_default_value() const { return static_cast(this->default_value.get()); } + + // Create an empty option to be used as a base for deserialization of DynamicConfig. + ConfigOption* create_empty_option() const; + // Create a default option to be inserted into a DynamicConfig. + ConfigOption* create_default_option() const; + + template ConfigOption* load_option_from_archive(Archive &archive) const { + switch (this->type) { + case coFloat: { auto opt = new ConfigOptionFloat(); archive(*opt); return opt; } + case coFloats: { auto opt = new ConfigOptionFloats(); archive(*opt); return opt; } + case coInt: { auto opt = new ConfigOptionInt(); archive(*opt); return opt; } + case coInts: { auto opt = new ConfigOptionInts(); archive(*opt); return opt; } + case coString: { auto opt = new ConfigOptionString(); archive(*opt); return opt; } + case coStrings: { auto opt = new ConfigOptionStrings(); archive(*opt); return opt; } + case coPercent: { auto opt = new ConfigOptionPercent(); archive(*opt); return opt; } + case coPercents: { auto opt = new ConfigOptionPercents(); archive(*opt); return opt; } + case coFloatOrPercent: { auto opt = new ConfigOptionFloatOrPercent(); archive(*opt); return opt; } + case coPoint: { auto opt = new ConfigOptionPoint(); archive(*opt); return opt; } + case coPoints: { auto opt = new ConfigOptionPoints(); archive(*opt); return opt; } + case coPoint3: { auto opt = new ConfigOptionPoint3(); archive(*opt); return opt; } + case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; } + case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; } + case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; } + default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); + } + } + + template ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const { + switch (this->type) { + case coFloat: archive(*static_cast(opt)); break; + case coFloats: archive(*static_cast(opt)); break; + case coInt: archive(*static_cast(opt)); break; + case coInts: archive(*static_cast(opt)); break; + case coString: archive(*static_cast(opt)); break; + case coStrings: archive(*static_cast(opt)); break; + case coPercent: archive(*static_cast(opt)); break; + case coPercents: archive(*static_cast(opt)); break; + case coFloatOrPercent: archive(*static_cast(opt)); break; + case coPoint: archive(*static_cast(opt)); break; + case coPoints: archive(*static_cast(opt)); break; + case coPoint3: archive(*static_cast(opt)); break; + case coBool: archive(*static_cast(opt)); break; + case coBools: archive(*static_cast(opt)); break; + case coEnum: archive(*static_cast(opt)); break; + default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); + } + // Make the compiler happy, shut up the warnings. + return nullptr; + } // Usually empty. // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection, @@ -1084,6 +1217,9 @@ public: return false; } + // 0 is an invalid key. + size_t serialization_key_ordinal = 0; + // Returns the alternative CLI arguments for the given option. // If there are no cli arguments defined, use the key and replace underscores with dashes. std::vector cli_args(const std::string &key) const; @@ -1103,7 +1239,8 @@ typedef std::map t_optiondef_map; class ConfigDef { public: - t_optiondef_map options; + t_optiondef_map options; + std::map by_serialization_key_ordinal; bool has(const t_config_option_key &opt_key) const { return this->options.count(opt_key) > 0; } const ConfigOptionDef* get(const t_config_option_key &opt_key) const { @@ -1124,11 +1261,7 @@ public: std::function filter = [](const ConfigOptionDef &){ return true; }) const; protected: - ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type) { - ConfigOptionDef* opt = &this->options[opt_key]; - opt->type = type; - return opt; - } + ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type); }; // An abstract configuration store. @@ -1197,7 +1330,7 @@ public: bool equals(const ConfigBase &other) const { return this->diff(other).empty(); } t_config_option_keys diff(const ConfigBase &other) const; t_config_option_keys equal(const ConfigBase &other) const; - std::string serialize(const t_config_option_key &opt_key) const; + std::string opt_serialize(const t_config_option_key &opt_key) const; // Set a configuration value from a string, it will call an overridable handle_legacy() // to resolve renamed and removed configuration keys. bool set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false); @@ -1235,7 +1368,7 @@ public: assert(this->def() == nullptr || this->def() == rhs.def()); this->clear(); for (const auto &kvp : rhs.options) - this->options[kvp.first] = kvp.second->clone(); + this->options[kvp.first].reset(kvp.second->clone()); return *this; } @@ -1258,15 +1391,13 @@ public: for (const auto &kvp : rhs.options) { auto it = this->options.find(kvp.first); if (it == this->options.end()) - this->options[kvp.first] = kvp.second->clone(); + this->options[kvp.first].reset(kvp.second->clone()); else { assert(it->second->type() == kvp.second->type()); if (it->second->type() == kvp.second->type()) *it->second = *kvp.second; - else { - delete it->second; - it->second = kvp.second->clone(); - } + else + it->second.reset(kvp.second->clone()); } } return *this; @@ -1277,14 +1408,13 @@ public: DynamicConfig& operator+=(DynamicConfig &&rhs) { assert(this->def() == nullptr || this->def() == rhs.def()); - for (const auto &kvp : rhs.options) { + for (auto &kvp : rhs.options) { auto it = this->options.find(kvp.first); if (it == this->options.end()) { - this->options[kvp.first] = kvp.second; + this->options.insert(std::make_pair(kvp.first, std::move(kvp.second))); } else { assert(it->second->type() == kvp.second->type()); - delete it->second; - it->second = kvp.second; + it->second = std::move(kvp.second); } } rhs.options.clear(); @@ -1301,8 +1431,6 @@ public: void clear() { - for (auto &opt : this->options) - delete opt.second; this->options.clear(); } @@ -1311,7 +1439,6 @@ public: auto it = this->options.find(opt_key); if (it == this->options.end()) return false; - delete it->second; this->options.erase(it); return true; } @@ -1336,11 +1463,10 @@ public: { auto it = this->options.find(opt_key); if (it == this->options.end()) { - this->options[opt_key] = opt; + this->options[opt_key].reset(opt); return true; } else { - delete it->second; - it->second = opt; + it->second.reset(opt); return false; } } @@ -1370,12 +1496,15 @@ public: void read_cli(const std::vector &tokens, t_config_option_keys* extra, t_config_option_keys* keys = nullptr); bool read_cli(int argc, char** argv, t_config_option_keys* extra, t_config_option_keys* keys = nullptr); - typedef std::map t_options_map; - t_options_map::const_iterator cbegin() const { return options.cbegin(); } - t_options_map::const_iterator cend() const { return options.cend(); } + std::map>::const_iterator cbegin() const { return options.cbegin(); } + std::map>::const_iterator cend() const { return options.cend(); } + size_t size() const { return options.size(); } private: - t_options_map options; + std::map> options; + + friend class cereal::access; + template void serialize(Archive &ar) { ar(options); } }; /// Configuration store with a static definition of configuration values. diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 4793586e3c..0cb0af119a 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -2060,7 +2060,7 @@ namespace Slic3r { for (const std::string &key : config.keys()) if (key != "compatible_printers") - out += "; " + key + " = " + config.serialize(key) + "\n"; + out += "; " + key + " = " + config.opt_serialize(key) + "\n"; if (!out.empty()) { @@ -2094,7 +2094,7 @@ namespace Slic3r { // stores object's config data for (const std::string& key : obj->config.keys()) { - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.serialize(key) << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.opt_serialize(key) << "\"/>\n"; } for (const ModelVolume* volume : obj_metadata.second.object->volumes) @@ -2124,7 +2124,7 @@ namespace Slic3r { // stores volume's config data for (const std::string& key : volume->config.keys()) { - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.serialize(key) << "\"/>\n"; + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n"; } stream << " \n"; diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index a33d21c9fa..0228bd906f 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -873,7 +873,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) std::string str_config = "\n"; for (const std::string &key : config->keys()) if (key != "compatible_printers") - str_config += "; " + key + " = " + config->serialize(key) + "\n"; + str_config += "; " + key + " = " + config->opt_serialize(key) + "\n"; stream << "" << xml_escape(str_config) << "\n"; } @@ -885,7 +885,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) for (const auto &attr : material.second->attributes) stream << " " << attr.second << "\n"; for (const std::string &key : material.second->config.keys()) - stream << " " << material.second->config.serialize(key) << "\n"; + stream << " " << material.second->config.opt_serialize(key) << "\n"; stream << " \n"; } std::string instances; @@ -893,7 +893,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) ModelObject *object = model->objects[object_id]; stream << " \n"; for (const std::string &key : object->config.keys()) - stream << " " << object->config.serialize(key) << "\n"; + stream << " " << object->config.opt_serialize(key) << "\n"; if (!object->name.empty()) stream << " " << xml_escape(object->name) << "\n"; const std::vector &layer_height_profile = object->layer_height_profile; @@ -952,7 +952,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) else stream << " material_id() << "\">\n"; for (const std::string &key : volume->config.keys()) - stream << " " << volume->config.serialize(key) << "\n"; + stream << " " << volume->config.opt_serialize(key) << "\n"; if (!volume->name.empty()) stream << " " << xml_escape(volume->name) << "\n"; if (volume->is_modifier()) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c42669de0d..f868aa0795 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1749,7 +1749,7 @@ void GCode::append_full_config(const Print& print, std::string& str) const StaticPrintConfig *cfg = configs[i]; for (const std::string &key : cfg->keys()) if (key != "compatible_printers") - str += "; " + key + " = " + cfg->serialize(key) + "\n"; + str += "; " + key + " = " + cfg->opt_serialize(key) + "\n"; } const DynamicConfig &full_config = print.placeholder_parser().config(); for (const char *key : { diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index c1d92c6bbd..a8160867af 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -128,7 +128,7 @@ void Layer::make_perimeters() && config.external_perimeter_speed == other_config.external_perimeter_speed && config.gap_fill_speed == other_config.gap_fill_speed && config.overhangs == other_config.overhangs - && config.serialize("perimeter_extrusion_width").compare(other_config.serialize("perimeter_extrusion_width")) == 0 + && config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width") && config.thin_walls == other_config.thin_walls && config.external_perimeters_first == other_config.external_perimeters_first) { layerms.push_back(other_layerm); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 89e21934a1..97787fff6a 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -406,10 +406,13 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionEnum(ipRectilinear)); def = this->add("bottom_fill_pattern", coEnum); - *def = *def_top_fill_pattern; def->label = L("Bottom fill pattern"); + def->category = L("Infill"); def->tooltip = L("Fill pattern for bottom infill. This only affects the bottom external visible layer, and not its adjacent solid shells."); def->cli = "bottom-fill-pattern|external-fill-pattern|solid-fill-pattern"; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values = def_top_fill_pattern->enum_values; + def->aliases = def_top_fill_pattern->aliases; def->set_default_value(new ConfigOptionEnum(ipRectilinear)); def = this->add("external_perimeter_extrusion_width", coFloatOrPercent); @@ -3194,3 +3197,6 @@ void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std:: } } + +CEREAL_REGISTER_TYPE(Slic3r::DynamicPrintConfig) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::DynamicConfig, Slic3r::DynamicPrintConfig) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 248b89e321..8dbce9ea3b 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1190,6 +1190,8 @@ private: this->options.insert(cli_actions_config_def.options.begin(), cli_actions_config_def.options.end()); this->options.insert(cli_transform_config_def.options.begin(), cli_transform_config_def.options.end()); this->options.insert(cli_misc_config_def.options.begin(), cli_misc_config_def.options.end()); + for (const auto &kvp : this->options) + this->by_serialization_key_ordinal[kvp.second.serialization_key_ordinal] = &kvp.second; } // Do not release the default values, they are handled by print_config_def & cli_actions_config_def / cli_transform_config_def / cli_misc_config_def. ~PrintAndCLIConfigDef() { this->options.clear(); } @@ -1199,4 +1201,38 @@ private: } // namespace Slic3r +// Serialization through the Cereal library +namespace cereal { + // Let cereal know that there are load / save non-member functions declared for DynamicPrintConfig, ignore serialize / load / save from parent class DynamicConfig. + template struct specialize {}; + + template void load(Archive& archive, Slic3r::DynamicPrintConfig &config) + { + size_t cnt; + archive(cnt); + config.clear(); + for (size_t i = 0; i < cnt; ++ i) { + size_t serialization_key_ordinal; + archive(serialization_key_ordinal); + assert(serialization_key_ordinal > 0); + auto it = Slic3r::print_config_def.by_serialization_key_ordinal.find(serialization_key_ordinal); + assert(it != Slic3r::print_config_def.by_serialization_key_ordinal.end()); + config.set_key_value(it->second->opt_key, it->second->load_option_from_archive(archive)); + } + } + + template void save(Archive& archive, const Slic3r::DynamicPrintConfig &config) + { + size_t cnt = config.size(); + archive(cnt); + for (auto it = config.cbegin(); it != config.cend(); ++it) { + const Slic3r::ConfigOptionDef* optdef = Slic3r::print_config_def.get(it->first); + assert(optdef != nullptr); + assert(optdef->serialization_key_ordinal > 0); + archive(optdef->serialization_key_ordinal); + optdef->save_option_to_archive(archive, it->second.get()); + } + } +} + #endif diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 570e23baa6..1867a8186f 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -159,7 +159,7 @@ endif () add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES}) -target_link_libraries(libslic3r_gui libslic3r avrdude imgui ${GLEW_LIBRARIES}) +target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui ${GLEW_LIBRARIES}) if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) endif () diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index b28cb2eda7..00c1f81685 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -1366,7 +1366,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst continue; c << std::endl << "[" << presets->section_name() << ":" << preset.name << "]" << std::endl; for (const std::string &opt_key : preset.config.keys()) - c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl; + c << opt_key << " = " << preset.config.opt_serialize(opt_key) << std::endl; } } From 624a6aefb4b9dc73aae2e5e95cecd62fc1a78f56 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 26 Jun 2019 13:29:49 +0200 Subject: [PATCH 210/627] Fixed crashes after loading some AMFs. --- src/libslic3r/Model.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 596f806710..9d68f7141d 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1564,8 +1564,10 @@ void ModelVolume::center_geometry_after_creation() Vec3d shift = this->mesh().bounding_box().center(); if (!shift.isApprox(Vec3d::Zero())) { - m_mesh->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); - m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + if (m_mesh) + m_mesh->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + if (m_convex_hull) + m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); translate(shift); } } From 104a289cfe48d20cfdea03cac0e10c1e7ca7beee Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 26 Jun 2019 13:30:20 +0200 Subject: [PATCH 211/627] Implemented interface for updating 3DScene after set a focus to some editor --- src/slic3r/GUI/GLCanvas3D.cpp | 5 +++ src/slic3r/GUI/GLCanvas3D.hpp | 2 ++ src/slic3r/GUI/GUI_ObjectLayers.cpp | 50 ++++++++++++++++++----------- src/slic3r/GUI/GUI_ObjectLayers.hpp | 16 ++++++--- 4 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ffaa2d20c5..138946166e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3310,6 +3310,11 @@ void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool foc } } +void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type) +{ + printf("min_z = %.2f, max_z = %.2f, type=%d\n", range.first, range.second, type); +} + void GLCanvas3D::update_ui_from_settings() { #if ENABLE_RETINA_GL diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index d39a910b3b..95fd123ebc 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -12,6 +12,7 @@ #include "Camera.hpp" #include "Selection.hpp" #include "Gizmos/GLGizmosManager.hpp" +#include "GUI_ObjectLayers.hpp" #include @@ -606,6 +607,7 @@ public: void reset_all_gizmos() { m_gizmos.reset_all_states(); } void handle_sidebar_focus_event(const std::string& opt_key, bool focus_on); + void handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type); void update_ui_from_settings(); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 1426ccf029..d854e54b3e 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -4,6 +4,7 @@ #include "OptionsGroup.hpp" #include "PresetBundle.hpp" #include "libslic3r/Model.hpp" +#include "GLCanvas3D.hpp" #include @@ -62,13 +63,13 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) { const bool is_last_edited_range = range == m_selectable_range; - auto set_focus_fn = [range, this](const EditorType type) + auto set_focus_data = [range, this](const EditorType type) { m_selectable_range = range; m_selection_type = type; }; - auto set_focus = [range, this](const t_layer_height_range& new_range, EditorType type, bool enter_pressed) + auto update_focus_data = [range, this](const t_layer_height_range& new_range, EditorType type, bool enter_pressed) { // change selectable range for new one, if enter was pressed or if same range was selected if (enter_pressed || m_selectable_range == range) @@ -79,8 +80,8 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) // Add control for the "Min Z" - auto editor = new LayerRangeEditor(m_parent, double_to_string(range.first), etMinZ, - set_focus_fn, [range, set_focus, this](coordf_t min_z, bool enter_pressed) + auto editor = new LayerRangeEditor(this, double_to_string(range.first), etMinZ, + set_focus_data, [range, update_focus_data, this](coordf_t min_z, bool enter_pressed) { if (fabs(min_z - range.first) < EPSILON) { m_selection_type = etUndef; @@ -89,8 +90,8 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) // data for next focusing coordf_t max_z = min_z < range.second ? range.second : min_z + 0.5; - const t_layer_height_range& new_range = { min_z, max_z/*range.second*/ }; - set_focus(new_range, etMinZ, enter_pressed); + const t_layer_height_range& new_range = { min_z, max_z }; + update_focus_data(new_range, etMinZ, enter_pressed); return wxGetApp().obj_list()->edit_layer_range(range, new_range); }); @@ -100,8 +101,8 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) // Add control for the "Max Z" - editor = new LayerRangeEditor(m_parent, double_to_string(range.second), etMaxZ, - set_focus_fn, [range, set_focus, this](coordf_t max_z, bool enter_pressed) + editor = new LayerRangeEditor(this, double_to_string(range.second), etMaxZ, + set_focus_data, [range, update_focus_data, this](coordf_t max_z, bool enter_pressed) { if (fabs(max_z - range.second) < EPSILON || range.first > max_z) { m_selection_type = etUndef; @@ -110,7 +111,7 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) // data for next focusing const t_layer_height_range& new_range = { range.first, max_z }; - set_focus(new_range, etMaxZ, enter_pressed); + update_focus_data(new_range, etMaxZ, enter_pressed); return wxGetApp().obj_list()->edit_layer_range(range, new_range); }); @@ -120,9 +121,9 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range) // Add control for the "Layer height" - editor = new LayerRangeEditor(m_parent, + editor = new LayerRangeEditor(this, double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()), - etLayerHeight, set_focus_fn, [range, this](coordf_t layer_height, bool) + etLayerHeight, set_focus_data, [range, this](coordf_t layer_height, bool) { return wxGetApp().obj_list()->edit_layer_range(range, layer_height); }); @@ -203,6 +204,12 @@ void ObjectLayers::update_layers_list() m_parent->Layout(); } +void ObjectLayers::update_scene_from_editor_selection() const +{ + // needed to show the visual hints in 3D scene + wxGetApp().plater()->canvas3D()->handle_layers_data_focus_event(m_selectable_range, m_selection_type); +} + void ObjectLayers::UpdateAndShow(const bool show) { if (show) @@ -217,17 +224,17 @@ void ObjectLayers::msw_rescale() m_bmp_add.msw_rescale(); } -LayerRangeEditor::LayerRangeEditor( wxWindow* parent, +LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, const wxString& value, EditorType type, - std::function set_focus_fn, + std::function set_focus_data_fn, std::function edit_fn ) : m_valid_value(value), m_type(type), - m_set_focus(set_focus_fn), - wxTextCtrl(parent, wxID_ANY, value, wxDefaultPosition, - wxSize(8 * em_unit(parent), wxDefaultCoord), wxTE_PROCESS_ENTER) + m_set_focus_data(set_focus_data_fn), + wxTextCtrl(parent->m_parent, wxID_ANY, value, wxDefaultPosition, + wxSize(8 * em_unit(parent->m_parent), wxDefaultCoord), wxTE_PROCESS_ENTER) { this->SetFont(wxGetApp().normal_font()); @@ -258,7 +265,7 @@ LayerRangeEditor::LayerRangeEditor( wxWindow* parent, * */ LayerRangeEditor* new_editor = dynamic_cast(e.GetWindow()); if (new_editor) - new_editor->set_focus(); + new_editor->set_focus_data(); #endif // not __WXGTK__ // If LayersList wasn't updated/recreated, we should call e.Skip() if (m_type & etLayerHeight) { @@ -279,10 +286,17 @@ LayerRangeEditor::LayerRangeEditor( wxWindow* parent, } }, this->GetId()); + this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e) + { + set_focus_data(); + parent->update_scene_from_editor_selection(); + e.Skip(); + }, this->GetId()); + #ifdef __WXGTK__ // Workaround! To take information about selectable range this->Bind(wxEVT_LEFT_DOWN, [this](wxEvent& e) { - set_focus(); + set_focus_data(); e.Skip(); }, this->GetId()); #endif //__WXGTK__ diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index e3366e03ea..f19217fbeb 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -19,6 +19,8 @@ class ConfigOptionsGroup; typedef double coordf_t; typedef std::pair t_layer_height_range; +class ObjectLayers; + enum EditorType { etUndef = 0, @@ -34,19 +36,19 @@ class LayerRangeEditor : public wxTextCtrl wxString m_valid_value; EditorType m_type; - std::function m_set_focus; + std::function m_set_focus_data; public: - LayerRangeEditor( wxWindow* parent, + LayerRangeEditor( ObjectLayers* parent, const wxString& value = wxEmptyString, EditorType type = etUndef, - std::function set_focus_fn = [](EditorType) {;}, - std::function edit_fn = [](coordf_t, bool) {return false; } + std::function set_focus_data_fn = [](EditorType) {;}, + std::function edit_fn = [](coordf_t, bool) {return false; } ); ~LayerRangeEditor() {} EditorType type() const {return m_type;} - void set_focus() const { m_set_focus(m_type);} + void set_focus_data() const { m_set_focus_data(m_type);} private: coordf_t get_value(); @@ -71,8 +73,12 @@ public: void create_layers_list(); void update_layers_list(); + void update_scene_from_editor_selection() const; + void UpdateAndShow(const bool show) override; void msw_rescale(); + + friend class LayerRangeEditor; }; }} From a07088a8d99499ef3dd770fb95a83d52e10b60b5 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 26 Jun 2019 14:25:05 +0200 Subject: [PATCH 212/627] #2561 - Fixed freezing of perspective camera when zooming-in --- src/slic3r/GUI/Camera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 6cb8ff5201..242d00a071 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -24,7 +24,7 @@ namespace GUI { const double Camera::DefaultDistance = 1000.0; double Camera::FrustrumMinZSize = 50.0; double Camera::FrustrumZMargin = 10.0; -double Camera::FovMinDeg = 5.0; +double Camera::FovMinDeg = 0.5; double Camera::FovMaxDeg = 75.0; Camera::Camera() From 4b9e366f00ad07aaba6fabf4c759e1dccc090f7e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 26 Jun 2019 14:50:12 +0200 Subject: [PATCH 213/627] Multimaterial print - making sure that temperatures will be changed with SE printer without the wipe tower --- src/libslic3r/GCode.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index dadf9f26e1..0628e4022a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2821,6 +2821,14 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) // user provided his own toolchange gcode, no need to do anything } + // Set the temperature if the wipe tower didn't (not needed for non-single extruder MM) + if (m_config.single_extruder_multi_material && !m_config.wipe_tower) { + int temp = (m_layer_index == 0 ? m_config.first_layer_temperature.get_at(extruder_id) : + m_config.temperature.get_at(extruder_id)); + + gcode += m_writer.set_temperature(temp, false); + } + m_placeholder_parser.set("current_extruder", extruder_id); // Append the filament start G-code. From dd108f4513c8dcd105dfd8ecd399c36eaa615cfe Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Jun 2019 14:59:39 +0200 Subject: [PATCH 214/627] Hotfix for inconsistent slice index --- src/libslic3r/MTUtils.hpp | 4 ++-- src/libslic3r/SLAPrint.cpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 70603cd15f..ce26887f2c 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -286,7 +286,7 @@ template> inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_NOEXCEPT { - return static_cast(v / static_cast(SCALING_FACTOR)); + return static_cast(v / static_cast(SCALING_FACTOR)); } // Conversion definition from unscaled to integer 'scaled coord'. @@ -297,7 +297,7 @@ template> inline SLIC3R_CONSTEXPR ScaledCoordOnly scaled(const Tin &v) SLIC3R_NOEXCEPT { //return static_cast(std::round(v / SCALING_FACTOR)); - return static_cast(v / static_cast(SCALING_FACTOR)); + return static_cast(v / static_cast(SCALING_FACTOR)); } // Conversion for Eigen vectors (N dimensional points) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 7ae481ffbf..1902e74ae6 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -720,8 +720,9 @@ void SLAPrint::process() if(slindex_it == po.m_slice_index.end()) //TRN To be shown at the status bar on SLA slicing error. - throw std::runtime_error(L("Slicing had to be stopped " - "due to an internal error.")); + throw std::runtime_error( + L("Slicing had to be stopped due to an internal error: " + "Inconsistent slice index.")); po.m_model_height_levels.clear(); po.m_model_height_levels.reserve(po.m_slice_index.size()); From d99e932ee807afd053bec0d3bffd3c934f19a9dd Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 26 Jun 2019 16:29:12 +0200 Subject: [PATCH 215/627] WIP Undo / Redo: Serialization of the Model / ModelObject / Model instance using the cereal framework. --- src/libslic3r/BoundingBox.hpp | 11 +++++ src/libslic3r/Config.hpp | 4 +- src/libslic3r/Geometry.hpp | 23 +++++++++++ src/libslic3r/Model.cpp | 13 ++++++ src/libslic3r/Model.hpp | 77 +++++++++++++++++++++++++++-------- src/libslic3r/Point.hpp | 24 ++++++++++- src/libslic3r/pchheader.hpp | 9 +++- 7 files changed, 139 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 26302f7027..b12ed55517 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -161,4 +161,15 @@ inline bool empty(const BoundingBox3Base &bb) } // namespace Slic3r +#include +#include + +// Serialization through the Cereal library +namespace cereal { + template void serialize(Archive& archive, Slic3r::BoundingBox &bb) { archive(bb.min, bb.max, bb.defined); } + template void serialize(Archive& archive, Slic3r::BoundingBox3 &bb) { archive(bb.min, bb.max, bb.defined); } + template void serialize(Archive& archive, Slic3r::BoundingBoxf &bb) { archive(bb.min, bb.max, bb.defined); } + template void serialize(Archive& archive, Slic3r::BoundingBoxf3 &bb) { archive(bb.min, bb.max, bb.defined); } +} + #endif diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 7b3c5c73aa..d1fb9b7f49 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -744,7 +744,7 @@ public: private: friend class cereal::access; - template void serialize(Archive &ar) { ar(this->value.x(), this->value.y()); } + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionPoints : public ConfigOptionVector @@ -853,7 +853,7 @@ public: private: friend class cereal::access; - template void serialize(Archive &ar) { ar(this->value.x(), this->value.y(), this->value.z()); } + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; class ConfigOptionBool : public ConfigOptionSingle diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index bcbe80a0d8..f1987734f9 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -7,6 +7,10 @@ #include "Polygon.hpp" #include "Polyline.hpp" +// Serialization through the Cereal library +#include +#include + #include "boost/polygon/voronoi.hpp" using boost::polygon::voronoi_builder; using boost::polygon::voronoi_diagram; @@ -263,6 +267,25 @@ public: // as possible in least squares norm in regard to the 8 corners of bbox. // Bounding box is expected to be centered around zero in all axes. static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox); + +private: + template void load(Archive& archive, Slic3r::Geometry::Transformation &t) { + archive.loadBinary((char*)m.data(), sizeof(float) * 4); + } + template void save(Archive& archive, const Slic3r::Geometry::Transformation &t) const { + archive.saveBinary((char*)m.data(), sizeof(float) * 4); + } + +private: + friend class cereal::access; + template void serialize(Archive & ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); } + explicit Transformation(int) : m_dirty(true) {} + template static void load_and_construct(Archive & ar, cereal::construct &construct) + { + // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. + construct(1); + ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, m_mirror); + } }; // Rotation when going from the first coordinate system with rotation rot_xyz_from applied diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 596f806710..da7d9e4d3b 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1910,3 +1910,16 @@ void check_model_ids_equal(const Model &model1, const Model &model2) #endif /* NDEBUG */ } + +#if 0 +CEREAL_REGISTER_TYPE(Slic3r::ModelBase) +CEREAL_REGISTER_TYPE(Slic3r::ModelObject) +CEREAL_REGISTER_TYPE(Slic3r::ModelVolume) +CEREAL_REGISTER_TYPE(Slic3r::ModelInstance) +CEREAL_REGISTER_TYPE(Slic3r::Model) + +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ModelBase, Slic3r::ModelObject) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ModelBase, Slic3r::ModelVolume) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ModelBase, Slic3r::ModelInstance) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ModelBase, Slic3r::Model) +#endif \ No newline at end of file diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 0fd1140f0a..3330f140de 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -40,8 +40,9 @@ typedef std::vector ModelInstancePtrs; // Valid IDs are strictly positive (non zero). // It is declared as an object, as some compilers (notably msvcc) consider a typedef size_t equivalent to size_t // for parameter overload. -struct ModelID +class ModelID { +public: ModelID(size_t id) : id(id) {} bool operator==(const ModelID &rhs) const { return this->id == rhs.id; } @@ -54,6 +55,12 @@ struct ModelID bool valid() const { return id != 0; } size_t id; + +private: + ModelID() {} + + friend class cereal::access; + template void serialize(Archive &ar) { ar(id); } }; // Unique object / instance ID for the wipe tower. @@ -76,6 +83,8 @@ protected: // Constructor with ignored int parameter to assign an invalid ID, to be replaced // by an existing ID copied from elsewhere. ModelBase(int) : m_id(ModelID(0)) {} + // The class tree will have virtual tables and type information. + virtual ~ModelBase() {} // Use with caution! void set_new_unique_id() { m_id = generate_new_id(); } @@ -94,6 +103,11 @@ private: friend ModelID wipe_tower_object_id(); friend ModelID wipe_tower_instance_id(); + + friend class cereal::access; + template void serialize(Archive &ar) { ar(m_id); } + ModelBase(const ModelID id) : m_id(id) {} + template static void load_and_construct(Archive & ar, cereal::construct &construct) { ModelID id; ar(id); construct(id); } }; #define MODELBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ @@ -155,10 +169,13 @@ private: // Parent, owning this material. Model *m_model; - ModelMaterial() = delete; ModelMaterial(ModelMaterial &&rhs) = delete; ModelMaterial& operator=(const ModelMaterial &rhs) = delete; ModelMaterial& operator=(ModelMaterial &&rhs) = delete; + + friend class cereal::access; + ModelMaterial() : m_model(nullptr) {} + template void serialize(Archive &ar) { ar(cereal::base_class(this)); ar(attributes, config); } }; // A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), @@ -323,7 +340,15 @@ private: mutable BoundingBoxf3 m_raw_bounding_box; mutable bool m_raw_bounding_box_valid; mutable BoundingBoxf3 m_raw_mesh_bounding_box; - mutable bool m_raw_mesh_bounding_box_valid; + mutable bool m_raw_mesh_bounding_box_valid; + + friend class cereal::access; + ModelObject() : m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + ar(name, input_file, instances, volumes, config, layer_height_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation, + m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); + } }; // Declared outside of ModelVolume, so it could be forward declared. @@ -459,7 +484,7 @@ private: // -1 -> is unknown value (before first cheking) // 0 -> is not splittable // 1 -> is splittable - mutable int m_is_splittable{ -1 }; + mutable int m_is_splittable{ -1 }; ModelVolume(ModelObject *object, const TriangleMesh &mesh) : m_mesh(new TriangleMesh(mesh)), m_type(ModelVolumeType::MODEL_PART), object(object) { @@ -486,6 +511,13 @@ private: } ModelVolume& operator=(ModelVolume &rhs) = delete; + + friend class cereal::access; + ModelVolume() : object(nullptr) {} + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + ar(name, config, m_mesh, m_type, m_material_id, m_convex_hull, m_transformation, m_is_splittable); + } }; // A single instance of a ModelObject. @@ -571,10 +603,16 @@ private: explicit ModelInstance(ModelObject *object, const ModelInstance &other) : m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) {} - ModelInstance() = delete; explicit ModelInstance(ModelInstance &&rhs) = delete; ModelInstance& operator=(const ModelInstance &rhs) = delete; ModelInstance& operator=(ModelInstance &&rhs) = delete; + + friend class cereal::access; + ModelInstance() : object(nullptr) {} + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + ar(m_transformation, print_volume_state); + } }; // The print bed content. @@ -633,24 +671,24 @@ public: BoundingBoxf3 bounding_box() const; // Set the print_volume_state of PrintObject::instances, // return total number of printable objects. - unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume); + unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume); // Returns true if any ModelObject was modified. - bool center_instances_around_point(const Vec2d &point); - void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } - TriangleMesh mesh() const; - bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); + bool center_instances_around_point(const Vec2d &point); + void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } + TriangleMesh mesh() const; + bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); // Croaks if the duplicated objects do not fit the print bed. - void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); - void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); - void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); + void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); + void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); + void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); - bool looks_like_multipart_object() const; - void convert_multipart_object(unsigned int max_extruders); + bool looks_like_multipart_object() const; + void convert_multipart_object(unsigned int max_extruders); // Ensures that the min z of the model is not negative - void adjust_min_z(); + void adjust_min_z(); - void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } + void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } static unsigned int get_auto_extruder_id(unsigned int max_extruders); static std::string get_auto_extruder_id_as_string(unsigned int max_extruders); @@ -663,6 +701,11 @@ public: private: MODELBASE_DERIVED_PRIVATE_COPY_MOVE(Model) + + friend class cereal::access; + template void serialize(Archive &ar) { + ar(cereal::base_class(this), materials, objects); + } }; #undef MODELBASE_DERIVED_COPY_MOVE_CLONE diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index b02ead2994..8979523b7e 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -62,8 +62,8 @@ inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); } inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); } inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); } -inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); } -inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); } +inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); } +inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); } inline Vec3i64 to_3d(const Vec2i64 &v, float z) { return Vec3i64(int64_t(v(0)), int64_t(v(1)), int64_t(z)); } inline Vec3crd to_3d(const Vec3crd &p, coord_t z) { return Vec3crd(p(0), p(1), z); } @@ -291,4 +291,24 @@ namespace boost { namespace polygon { } } // end Boost +#include +#include + +// Serialization through the Cereal library +namespace cereal { +// template void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); } +// template void serialize(Archive& archive, Slic3r::Vec3crd &v) { archive(v.x(), v.y(), v.z()); } + template void serialize(Archive& archive, Slic3r::Vec2i &v) { archive(v.x(), v.y()); } + template void serialize(Archive& archive, Slic3r::Vec3i &v) { archive(v.x(), v.y(), v.z()); } +// template void serialize(Archive& archive, Slic3r::Vec2i64 &v) { archive(v.x(), v.y()); } +// template void serialize(Archive& archive, Slic3r::Vec3i64 &v) { archive(v.x(), v.y(), v.z()); } + template void serialize(Archive& archive, Slic3r::Vec2f &v) { archive(v.x(), v.y()); } + template void serialize(Archive& archive, Slic3r::Vec3f &v) { archive(v.x(), v.y(), v.z()); } + template void serialize(Archive& archive, Slic3r::Vec2d &v) { archive(v.x(), v.y()); } + template void serialize(Archive& archive, Slic3r::Vec3d &v) { archive(v.x(), v.y(), v.z()); } + + template void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); } + template void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); } +} + #endif diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index b27dfe6a2d..54b563defb 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -100,7 +100,14 @@ #include #include -#include +#include + +#include +#include +#include +#include +#include +#include #include "BoundingBox.hpp" #include "ClipperUtils.hpp" From 74b420d608623793f00a94f1cde97c3c1e3c0306 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Jun 2019 17:06:41 +0200 Subject: [PATCH 216/627] Remove option for igl static build due to Eigen version mismatch . --- deps/CMakeLists.txt | 9 +++++---- deps/deps-unix-common.cmake | 2 +- deps/deps-windows.cmake | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 5bc33c896d..d8e72370b5 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -36,10 +36,11 @@ set(DESTDIR "${CMAKE_CURRENT_BINARY_DIR}/destdir" CACHE PATH "Destination direct option(DEP_DEBUG "Build debug variants (only applicable on Windows)" ON) option(DEP_WX_STABLE "Build against wxWidgets stable 3.0 as opposed to default 3.1 (Linux only)" OFF) -# IGL static library in release mode produces 50MB binary. On the build server, it should be -# disabled and used in header-only mode. On developer machines, it can be enabled to speed -# up conpilation and suppress warnings coming from IGL. -option(DEP_BUILD_IGL_STATIC "Build IGL as a static library. Might cause link errors and increase binary size." OFF) +# On developer machines, it can be enabled to speed up compilation and suppress warnings coming from IGL. +# FIXME: +# Enabling this option is not safe. IGL will compile itself with its own version of Eigen while +# Slic3r compiles with a different version which will cause runtime errors. +# option(DEP_BUILD_IGL_STATIC "Build IGL as a static library. Might cause link errors and increase binary size." OFF) message(STATUS "PrusaSlicer deps DESTDIR: ${DESTDIR}") message(STATUS "PrusaSlicer deps debug build: ${DEP_DEBUG}") diff --git a/deps/deps-unix-common.cmake b/deps/deps-unix-common.cmake index c44a6ec205..3614e94448 100644 --- a/deps/deps-unix-common.cmake +++ b/deps/deps-unix-common.cmake @@ -54,7 +54,7 @@ ExternalProject_Add(dep_libigl -DLIBIGL_BUILD_PYTHON=OFF -DLIBIGL_BUILD_TESTS=OFF -DLIBIGL_BUILD_TUTORIALS=OFF - -DLIBIGL_USE_STATIC_LIBRARY=${DEP_BUILD_IGL_STATIC} + -DLIBIGL_USE_STATIC_LIBRARY=OFF #${DEP_BUILD_IGL_STATIC} -DLIBIGL_WITHOUT_COPYLEFT=OFF -DLIBIGL_WITH_CGAL=OFF -DLIBIGL_WITH_COMISO=OFF diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index d7daf84253..0b3fcb13c8 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -264,7 +264,7 @@ ExternalProject_Add(dep_libigl -DLIBIGL_BUILD_PYTHON=OFF -DLIBIGL_BUILD_TESTS=OFF -DLIBIGL_BUILD_TUTORIALS=OFF - -DLIBIGL_USE_STATIC_LIBRARY=${DEP_BUILD_IGL_STATIC} + -DLIBIGL_USE_STATIC_LIBRARY=OFF #${DEP_BUILD_IGL_STATIC} -DLIBIGL_WITHOUT_COPYLEFT=OFF -DLIBIGL_WITH_CGAL=OFF -DLIBIGL_WITH_COMISO=OFF From e1d612d05fdd98b8489929f564b3a86b6871ac46 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Jun 2019 17:09:26 +0200 Subject: [PATCH 217/627] work in progress on new ModelArrange interface --- src/libslic3r/ModelArrange.cpp | 766 ++++++++++++++++----------------- src/libslic3r/ModelArrange.hpp | 53 ++- src/slic3r/GUI/Plater.cpp | 235 ++++++---- 3 files changed, 567 insertions(+), 487 deletions(-) diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 4a3730847b..d50e03bf20 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -1,5 +1,5 @@ #include "ModelArrange.hpp" -#include "Model.hpp" +//#include "Model.hpp" #include "Geometry.hpp" #include "SVG.hpp" #include "MTUtils.hpp" @@ -43,87 +43,87 @@ namespace arr { using namespace libnest2d; // Only for debugging. Prints the model object vertices on stdout. -std::string toString(const Model& model, bool holes = true) { - std::stringstream ss; +//std::string toString(const Model& model, bool holes = true) { +// std::stringstream ss; - ss << "{\n"; +// ss << "{\n"; - for(auto objptr : model.objects) { - if(!objptr) continue; +// for(auto objptr : model.objects) { +// if(!objptr) continue; - auto rmesh = objptr->raw_mesh(); +// auto rmesh = objptr->raw_mesh(); - for(auto objinst : objptr->instances) { - if(!objinst) continue; +// for(auto objinst : objptr->instances) { +// if(!objinst) continue; - Slic3r::TriangleMesh tmpmesh = rmesh; - // CHECK_ME -> Is the following correct ? - tmpmesh.scale(objinst->get_scaling_factor()); - objinst->transform_mesh(&tmpmesh); - ExPolygons expolys = tmpmesh.horizontal_projection(); - for(auto& expoly_complex : expolys) { +// Slic3r::TriangleMesh tmpmesh = rmesh; +// // CHECK_ME -> Is the following correct ? +// tmpmesh.scale(objinst->get_scaling_factor()); +// objinst->transform_mesh(&tmpmesh); +// ExPolygons expolys = tmpmesh.horizontal_projection(); +// for(auto& expoly_complex : expolys) { - ExPolygons tmp = expoly_complex.simplify(scaled(1.)); - if(tmp.empty()) continue; - ExPolygon expoly = tmp.front(); - expoly.contour.make_clockwise(); - for(auto& h : expoly.holes) h.make_counter_clockwise(); +// ExPolygons tmp = expoly_complex.simplify(scaled(1.)); +// if(tmp.empty()) continue; +// ExPolygon expoly = tmp.front(); +// expoly.contour.make_clockwise(); +// for(auto& h : expoly.holes) h.make_counter_clockwise(); - ss << "\t{\n"; - ss << "\t\t{\n"; +// ss << "\t{\n"; +// ss << "\t\t{\n"; - for(auto v : expoly.contour.points) ss << "\t\t\t{" - << v(0) << ", " - << v(1) << "},\n"; - { - auto v = expoly.contour.points.front(); - ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; - } - ss << "\t\t},\n"; +// for(auto v : expoly.contour.points) ss << "\t\t\t{" +// << v(0) << ", " +// << v(1) << "},\n"; +// { +// auto v = expoly.contour.points.front(); +// ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; +// } +// ss << "\t\t},\n"; - // Holes: - ss << "\t\t{\n"; - if(holes) for(auto h : expoly.holes) { - ss << "\t\t\t{\n"; - for(auto v : h.points) ss << "\t\t\t\t{" - << v(0) << ", " - << v(1) << "},\n"; - { - auto v = h.points.front(); - ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; - } - ss << "\t\t\t},\n"; - } - ss << "\t\t},\n"; +// // Holes: +// ss << "\t\t{\n"; +// if(holes) for(auto h : expoly.holes) { +// ss << "\t\t\t{\n"; +// for(auto v : h.points) ss << "\t\t\t\t{" +// << v(0) << ", " +// << v(1) << "},\n"; +// { +// auto v = h.points.front(); +// ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; +// } +// ss << "\t\t\t},\n"; +// } +// ss << "\t\t},\n"; - ss << "\t},\n"; - } - } - } +// ss << "\t},\n"; +// } +// } +// } - ss << "}\n"; +// ss << "}\n"; - return ss.str(); -} +// return ss.str(); +//} // Debugging: Save model to svg file. -void toSVG(SVG& svg, const Model& model) { - for(auto objptr : model.objects) { - if(!objptr) continue; +//void toSVG(SVG& svg, const Model& model) { +// for(auto objptr : model.objects) { +// if(!objptr) continue; - auto rmesh = objptr->raw_mesh(); +// auto rmesh = objptr->raw_mesh(); - for(auto objinst : objptr->instances) { - if(!objinst) continue; +// for(auto objinst : objptr->instances) { +// if(!objinst) continue; - Slic3r::TriangleMesh tmpmesh = rmesh; - tmpmesh.scale(objinst->get_scaling_factor()); - objinst->transform_mesh(&tmpmesh); - ExPolygons expolys = tmpmesh.horizontal_projection(); - svg.draw(expolys); - } - } -} +// Slic3r::TriangleMesh tmpmesh = rmesh; +// tmpmesh.scale(objinst->get_scaling_factor()); +// objinst->transform_mesh(&tmpmesh); +// ExPolygons expolys = tmpmesh.horizontal_projection(); +// svg.draw(expolys); +// } +// } +//} namespace bgi = boost::geometry::index; @@ -565,143 +565,143 @@ public: // A container which stores a pointer to the 3D object and its projected // 2D shape from top view. -using ShapeData2D = std::vector>; +//using ShapeData2D = std::vector>; -ShapeData2D projectModelFromTop(const Slic3r::Model &model, - const WipeTowerInfo &wti, - double tolerance) -{ - ShapeData2D ret; +//ShapeData2D projectModelFromTop(const Slic3r::Model &model, +// const WipeTowerInfo &wti, +// double tolerance) +//{ +// ShapeData2D ret; - // Count all the items on the bin (all the object's instances) - auto s = std::accumulate(model.objects.begin(), model.objects.end(), - size_t(0), [](size_t s, ModelObject* o) - { - return s + o->instances.size(); - }); +// // Count all the items on the bin (all the object's instances) +// auto s = std::accumulate(model.objects.begin(), model.objects.end(), +// size_t(0), [](size_t s, ModelObject* o) +// { +// return s + o->instances.size(); +// }); - ret.reserve(s); +// ret.reserve(s); + +// for(ModelObject* objptr : model.objects) { +// if (! objptr->instances.empty()) { - for(ModelObject* objptr : model.objects) { - if (! objptr->instances.empty()) { +// // TODO export the exact 2D projection. Cannot do it as libnest2d +// // does not support concave shapes (yet). +// ClipperLib::Path clpath; - // TODO export the exact 2D projection. Cannot do it as libnest2d - // does not support concave shapes (yet). - ClipperLib::Path clpath; - - // Object instances should carry the same scaling and - // x, y rotation that is why we use the first instance. - { - ModelInstance *finst = objptr->instances.front(); - Vec3d rotation = finst->get_rotation(); - rotation.z() = 0.; - Transform3d trafo_instance = Geometry::assemble_transform( - Vec3d::Zero(), - rotation, - finst->get_scaling_factor(), - finst->get_mirror()); - Polygon p = objptr->convex_hull_2d(trafo_instance); +// // Object instances should carry the same scaling and +// // x, y rotation that is why we use the first instance. +// { +// ModelInstance *finst = objptr->instances.front(); +// Vec3d rotation = finst->get_rotation(); +// rotation.z() = 0.; +// Transform3d trafo_instance = Geometry::assemble_transform( +// Vec3d::Zero(), +// rotation, +// finst->get_scaling_factor(), +// finst->get_mirror()); +// Polygon p = objptr->convex_hull_2d(trafo_instance); - assert(!p.points.empty()); +// assert(!p.points.empty()); - // this may happen for malformed models, see: - // https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (p.points.empty()) continue; +// // this may happen for malformed models, see: +// // https://github.com/prusa3d/PrusaSlicer/issues/2209 +// if (p.points.empty()) continue; - if(tolerance > EPSILON) { - Polygons pp { p }; - pp = p.simplify(scaled(tolerance)); - if (!pp.empty()) p = pp.front(); - } +// if(tolerance > EPSILON) { +// Polygons pp { p }; +// pp = p.simplify(scaled(tolerance)); +// if (!pp.empty()) p = pp.front(); +// } - p.reverse(); - assert(!p.is_counter_clockwise()); - clpath = Slic3rMultiPoint_to_ClipperPath(p); - auto firstp = clpath.front(); clpath.emplace_back(firstp); - } +// p.reverse(); +// assert(!p.is_counter_clockwise()); +// clpath = Slic3rMultiPoint_to_ClipperPath(p); +// auto firstp = clpath.front(); clpath.emplace_back(firstp); +// } - Vec3d rotation0 = objptr->instances.front()->get_rotation(); - rotation0(2) = 0.; - for(ModelInstance* objinst : objptr->instances) { - ClipperLib::Polygon pn; - pn.Contour = clpath; +// Vec3d rotation0 = objptr->instances.front()->get_rotation(); +// rotation0(2) = 0.; +// for(ModelInstance* objinst : objptr->instances) { +// ClipperLib::Polygon pn; +// pn.Contour = clpath; - // Efficient conversion to item. - Item item(std::move(pn)); +// // Efficient conversion to item. +// Item item(std::move(pn)); - // Invalid geometries would throw exceptions when arranging - if(item.vertexCount() > 3) { - item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation())); - item.translation({ - scaled(objinst->get_offset(X)), - scaled(objinst->get_offset(Y)) - }); - ret.emplace_back(objinst, item); - } - } - } - } +// // Invalid geometries would throw exceptions when arranging +// if(item.vertexCount() > 3) { +// item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation())); +// item.translation({ +// scaled(objinst->get_offset(X)), +// scaled(objinst->get_offset(Y)) +// }); +// ret.emplace_back(objinst, item); +// } +// } +// } +// } - // The wipe tower is a separate case (in case there is one), let's duplicate the code - if (wti.is_wipe_tower) { - Points pts; - pts.emplace_back(coord_t(scale_(0.)), coord_t(scale_(0.))); - pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(0.))); - pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(wti.bb_size(1)))); - pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(wti.bb_size(1)))); - pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(0.))); - Polygon p(std::move(pts)); - ClipperLib::Path clpath = Slic3rMultiPoint_to_ClipperPath(p); - ClipperLib::Polygon pn; - pn.Contour = clpath; - // Efficient conversion to item. - Item item(std::move(pn)); - item.rotation(wti.rotation), - item.translation({ - scaled(wti.pos(0)), - scaled(wti.pos(1)) - }); - ret.emplace_back(nullptr, item); - } +// // The wipe tower is a separate case (in case there is one), let's duplicate the code +// if (wti.is_wipe_tower) { +// Points pts; +// pts.emplace_back(coord_t(scale_(0.)), coord_t(scale_(0.))); +// pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(0.))); +// pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(wti.bb_size(1)))); +// pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(wti.bb_size(1)))); +// pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(0.))); +// Polygon p(std::move(pts)); +// ClipperLib::Path clpath = Slic3rMultiPoint_to_ClipperPath(p); +// ClipperLib::Polygon pn; +// pn.Contour = clpath; +// // Efficient conversion to item. +// Item item(std::move(pn)); +// item.rotation(wti.rotation), +// item.translation({ +// scaled(wti.pos(0)), +// scaled(wti.pos(1)) +// }); +// ret.emplace_back(nullptr, item); +// } - return ret; -} +// return ret; +//} // Apply the calculated translations and rotations (currently disabled) to // the Model object instances. -void applyResult(IndexedPackGroup::value_type &group, - ClipperLib::cInt batch_offset, - ShapeData2D & shapemap, - WipeTowerInfo & wti) -{ - for(auto& r : group) { - auto idx = r.first; // get the original item index - Item& item = r.second; // get the item itself +//void applyResult(IndexedPackGroup::value_type &group, +// ClipperLib::cInt batch_offset, +// ShapeData2D & shapemap, +// WipeTowerInfo & wti) +//{ +// for(auto& r : group) { +// auto idx = r.first; // get the original item index +// Item& item = r.second; // get the item itself - // Get the model instance from the shapemap using the index - ModelInstance *inst_ptr = shapemap[idx].first; +// // Get the model instance from the shapemap using the index +// ModelInstance *inst_ptr = shapemap[idx].first; - // Get the transformation data from the item object and scale it - // appropriately - auto off = item.translation(); - Radians rot = item.rotation(); +// // Get the transformation data from the item object and scale it +// // appropriately +// auto off = item.translation(); +// Radians rot = item.rotation(); - Vec3d foff(unscaled(off.X + batch_offset) , - unscaled(off.Y), - inst_ptr ? inst_ptr->get_offset()(Z) : 0.); +// Vec3d foff(unscaled(off.X + batch_offset), +// unscaled(off.Y), +// inst_ptr ? inst_ptr->get_offset()(Z) : 0.); - if (inst_ptr) { - // write the transformation data into the model instance - inst_ptr->set_rotation(Z, rot); - inst_ptr->set_offset(foff); - } - else { // this is the wipe tower - we will modify the struct with the info - // and leave it up to the called to actually move the wipe tower - wti.pos = Vec2d(foff(0), foff(1)); - wti.rotation = rot; - } - } -} +// if (inst_ptr) { +// // write the transformation data into the model instance +// inst_ptr->set_rotation(Z, rot); +// inst_ptr->set_offset(foff); +// } +// else { // this is the wipe tower - we will modify the struct with the info +// // and leave it up to the called to actually move the wipe tower +// wti.pos = Vec2d(foff(0), foff(1)); +// wti.rotation = rot; +// } +// } +//} // Get the type of bed geometry from a simple vector of points. BedShapeHint bedShape(const Polyline &bed) { @@ -784,254 +784,254 @@ BedShapeHint bedShape(const Polyline &bed) { static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; -template -IndexedPackGroup _arrange(std::vector> &shapes, - const BinT & bin, - coord_t minobjd, - std::function prind, - std::function stopfn) -{ - AutoArranger arranger{bin, minobjd, prind, stopfn}; - return arranger(shapes.begin(), shapes.end()); -} +//template +//IndexedPackGroup _arrange(std::vector> &shapes, +// const BinT & bin, +// coord_t minobjd, +// std::function prind, +// std::function stopfn) +//{ +// AutoArranger arranger{bin, minobjd, prind, stopfn}; +// return arranger(shapes.begin(), shapes.end()); +//} -template -IndexedPackGroup _arrange(std::vector> &shapes, - const PackGroup & preshapes, - std::vector &minstances, - const BinT & bin, - coord_t minobjd) -{ +//template +//IndexedPackGroup _arrange(std::vector> &shapes, +// const PackGroup & preshapes, +// std::vector &minstances, +// const BinT & bin, +// coord_t minobjd) +//{ - auto binbb = sl::boundingBox(bin); +// auto binbb = sl::boundingBox(bin); - AutoArranger arranger{bin, minobjd}; +// AutoArranger arranger{bin, minobjd}; - if(!preshapes.front().empty()) { // If there is something on the plate - arranger.preload(preshapes); +// if(!preshapes.front().empty()) { // If there is something on the plate +// arranger.preload(preshapes); - // Try to put the first item to the center, as the arranger will not - // do this for us. - auto shptrit = minstances.begin(); - for(auto shit = shapes.begin(); shit != shapes.end(); ++shit, ++shptrit) - { - // Try to place items to the center - Item& itm = *shit; - auto ibb = itm.boundingBox(); - auto d = binbb.center() - ibb.center(); - itm.translate(d); - if(!arranger.is_colliding(itm)) { - arranger.preload({{itm}}); +// // Try to put the first item to the center, as the arranger will not +// // do this for us. +// auto shptrit = minstances.begin(); +// for(auto shit = shapes.begin(); shit != shapes.end(); ++shit, ++shptrit) +// { +// // Try to place items to the center +// Item& itm = *shit; +// auto ibb = itm.boundingBox(); +// auto d = binbb.center() - ibb.center(); +// itm.translate(d); +// if(!arranger.is_colliding(itm)) { +// arranger.preload({{itm}}); - auto offset = itm.translation(); - Radians rot = itm.rotation(); - ModelInstance *minst = *shptrit; +// auto offset = itm.translation(); +// Radians rot = itm.rotation(); +// ModelInstance *minst = *shptrit; - Vec3d foffset(unscaled(offset.X), - unscaled(offset.Y), - minst->get_offset()(Z)); +// Vec3d foffset(unscaled(offset.X), +// unscaled(offset.Y), +// minst->get_offset()(Z)); - // write the transformation data into the model instance - minst->set_rotation(Z, rot); - minst->set_offset(foffset); +// // write the transformation data into the model instance +// minst->set_rotation(Z, rot); +// minst->set_offset(foffset); - shit = shapes.erase(shit); - shptrit = minstances.erase(shptrit); - break; - } - } - } +// shit = shapes.erase(shit); +// shptrit = minstances.erase(shptrit); +// break; +// } +// } +// } - return arranger(shapes.begin(), shapes.end()); -} +// return arranger(shapes.begin(), shapes.end()); +//} inline SLIC3R_CONSTEXPR libnest2d::Coord stride_padding(Coord w) { return w + w / 5; } -// The final client function to arrange the Model. A progress indicator and -// a stop predicate can be also be passed to control the process. -bool arrange(Model &model, // The model with the geometries - WipeTowerInfo& wti, // Wipe tower info - coord_t min_obj_distance, // Has to be in scaled (clipper) measure - const Polyline &bed, // The bed geometry. - BedShapeHint bedhint, // Hint about the bed geometry type. - bool first_bin_only, // What to do is not all items fit. +//// The final client function to arrange the Model. A progress indicator and +//// a stop predicate can be also be passed to control the process. +//bool arrange(Model &model, // The model with the geometries +// WipeTowerInfo& wti, // Wipe tower info +// coord_t min_obj_distance, // Has to be in scaled (clipper) measure +// const Polyline &bed, // The bed geometry. +// BedShapeHint bedhint, // Hint about the bed geometry type. +// bool first_bin_only, // What to do is not all items fit. - // Controlling callbacks. - std::function progressind, - std::function stopcondition) -{ - bool ret = true; +// // Controlling callbacks. +// std::function progressind, +// std::function stopcondition) +//{ +// bool ret = true; - // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); +// // Get the 2D projected shapes with their 3D model instance pointers +// auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); - // Copy the references for the shapes only as the arranger expects a - // sequence of objects convertible to Item or ClipperPolygon - std::vector> shapes; - shapes.reserve(shapemap.size()); - std::for_each(shapemap.begin(), shapemap.end(), - [&shapes] (ShapeData2D::value_type& it) - { - shapes.push_back(std::ref(it.second)); - }); +// // Copy the references for the shapes only as the arranger expects a +// // sequence of objects convertible to Item or ClipperPolygon +// std::vector> shapes; +// shapes.reserve(shapemap.size()); +// std::for_each(shapemap.begin(), shapemap.end(), +// [&shapes] (ShapeData2D::value_type& it) +// { +// shapes.push_back(std::ref(it.second)); +// }); - IndexedPackGroup result; +// IndexedPackGroup result; - // If there is no hint about the shape, we will try to guess - if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); +// // If there is no hint about the shape, we will try to guess +// if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); - BoundingBox bbb(bed); +// BoundingBox bbb(bed); - auto& cfn = stopcondition; +// auto& cfn = stopcondition; - // Integer ceiling the min distance from the bed perimeters - coord_t md = min_obj_distance - SCALED_EPSILON; - md = (md % 2) ? md / 2 + 1 : md / 2; +// // Integer ceiling the min distance from the bed perimeters +// coord_t md = min_obj_distance - SCALED_EPSILON; +// md = (md % 2) ? md / 2 + 1 : md / 2; - auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, - ClipperLib::cInt{bbb.min(1)} - md}, - {ClipperLib::cInt{bbb.max(0)} + md, - ClipperLib::cInt{bbb.max(1)} + md}); +// auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, +// ClipperLib::cInt{bbb.min(1)} - md}, +// {ClipperLib::cInt{bbb.max(0)} + md, +// ClipperLib::cInt{bbb.max(1)} + md}); - switch(bedhint.type) { - case BedShapeType::BOX: { - // Create the arranger for the box shaped bed - result = _arrange(shapes, binbb, min_obj_distance, progressind, cfn); - break; - } - case BedShapeType::CIRCLE: { - auto c = bedhint.shape.circ; - auto cc = to_lnCircle(c); - result = _arrange(shapes, cc, min_obj_distance, progressind, cfn); - break; - } - case BedShapeType::IRREGULAR: - case BedShapeType::WHO_KNOWS: { - auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); - ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); - result = _arrange(shapes, irrbed, min_obj_distance, progressind, cfn); - break; - } - }; +// switch(bedhint.type) { +// case BedShapeType::BOX: { +// // Create the arranger for the box shaped bed +// result = _arrange(shapes, binbb, min_obj_distance, progressind, cfn); +// break; +// } +// case BedShapeType::CIRCLE: { +// auto c = bedhint.shape.circ; +// auto cc = to_lnCircle(c); +// result = _arrange(shapes, cc, min_obj_distance, progressind, cfn); +// break; +// } +// case BedShapeType::IRREGULAR: +// case BedShapeType::WHO_KNOWS: { +// auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); +// ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); +// result = _arrange(shapes, irrbed, min_obj_distance, progressind, cfn); +// break; +// } +// }; - if(result.empty() || stopcondition()) return false; +// if(result.empty() || stopcondition()) return false; - if(first_bin_only) { - applyResult(result.front(), 0, shapemap, wti); - } else { +// if(first_bin_only) { +// applyResult(result.front(), 0, shapemap, wti); +// } else { - ClipperLib::cInt stride = stride_padding(binbb.width()); - ClipperLib::cInt batch_offset = 0; +// ClipperLib::cInt stride = stride_padding(binbb.width()); +// ClipperLib::cInt batch_offset = 0; - for(auto& group : result) { - applyResult(group, batch_offset, shapemap, wti); +// for(auto& group : result) { +// applyResult(group, batch_offset, shapemap, wti); - // Only the first pack group can be placed onto the print bed. The - // other objects which could not fit will be placed next to the - // print bed - batch_offset += stride; - } - } +// // Only the first pack group can be placed onto the print bed. The +// // other objects which could not fit will be placed next to the +// // print bed +// batch_offset += stride; +// } +// } - for(auto objptr : model.objects) objptr->invalidate_bounding_box(); +// for(auto objptr : model.objects) objptr->invalidate_bounding_box(); - return ret && result.size() == 1; -} +// return ret && result.size() == 1; +//} -void find_new_position(const Model &model, - ModelInstancePtrs toadd, - coord_t min_obj_distance, - const Polyline &bed, - WipeTowerInfo& wti) -{ - // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); +//void find_new_position(const Model &model, +// ModelInstancePtrs toadd, +// coord_t min_obj_distance, +// const Polyline &bed, +// WipeTowerInfo& wti) +//{ +// // Get the 2D projected shapes with their 3D model instance pointers +// auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); - // Copy the references for the shapes only, as the arranger expects a - // sequence of objects convertible to Item or ClipperPolygon - PackGroup preshapes; preshapes.emplace_back(); - ItemGroup shapes; - preshapes.front().reserve(shapemap.size()); +// // Copy the references for the shapes only, as the arranger expects a +// // sequence of objects convertible to Item or ClipperPolygon +// PackGroup preshapes; preshapes.emplace_back(); +// ItemGroup shapes; +// preshapes.front().reserve(shapemap.size()); - std::vector shapes_ptr; shapes_ptr.reserve(toadd.size()); - IndexedPackGroup result; +// std::vector shapes_ptr; shapes_ptr.reserve(toadd.size()); +// IndexedPackGroup result; - // If there is no hint about the shape, we will try to guess - BedShapeHint bedhint = bedShape(bed); +// // If there is no hint about the shape, we will try to guess +// BedShapeHint bedhint = bedShape(bed); - BoundingBox bbb(bed); +// BoundingBox bbb(bed); - // Integer ceiling the min distance from the bed perimeters - coord_t md = min_obj_distance - SCALED_EPSILON; - md = (md % 2) ? md / 2 + 1 : md / 2; +// // Integer ceiling the min distance from the bed perimeters +// coord_t md = min_obj_distance - SCALED_EPSILON; +// md = (md % 2) ? md / 2 + 1 : md / 2; - auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, - ClipperLib::cInt{bbb.min(1)} - md}, - {ClipperLib::cInt{bbb.max(0)} + md, - ClipperLib::cInt{bbb.max(1)} + md}); +// auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, +// ClipperLib::cInt{bbb.min(1)} - md}, +// {ClipperLib::cInt{bbb.max(0)} + md, +// ClipperLib::cInt{bbb.max(1)} + md}); - for(auto it = shapemap.begin(); it != shapemap.end(); ++it) { - // `toadd` vector contains the instance pointers which have to be - // considered by arrange. If `it` points to an ModelInstance, which - // is NOT in `toadd`, add it to preshapes. - if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end()) { - if(it->second.isInside(binbb)) // just ignore items which are outside - preshapes.front().emplace_back(std::ref(it->second)); - } - else { - shapes_ptr.emplace_back(it->first); - shapes.emplace_back(std::ref(it->second)); - } - } +// for(auto it = shapemap.begin(); it != shapemap.end(); ++it) { +// // `toadd` vector contains the instance pointers which have to be +// // considered by arrange. If `it` points to an ModelInstance, which +// // is NOT in `toadd`, add it to preshapes. +// if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end()) { +// if(it->second.isInside(binbb)) // just ignore items which are outside +// preshapes.front().emplace_back(std::ref(it->second)); +// } +// else { +// shapes_ptr.emplace_back(it->first); +// shapes.emplace_back(std::ref(it->second)); +// } +// } - switch(bedhint.type) { - case BedShapeType::BOX: { - // Create the arranger for the box shaped bed - result = _arrange(shapes, preshapes, shapes_ptr, binbb, min_obj_distance); - break; - } - case BedShapeType::CIRCLE: { - auto c = bedhint.shape.circ; - auto cc = to_lnCircle(c); - result = _arrange(shapes, preshapes, shapes_ptr, cc, min_obj_distance); - break; - } - case BedShapeType::IRREGULAR: - case BedShapeType::WHO_KNOWS: { - auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); - ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); - result = _arrange(shapes, preshapes, shapes_ptr, irrbed, min_obj_distance); - break; - } - }; +// switch(bedhint.type) { +// case BedShapeType::BOX: { +// // Create the arranger for the box shaped bed +// result = _arrange(shapes, preshapes, shapes_ptr, binbb, min_obj_distance); +// break; +// } +// case BedShapeType::CIRCLE: { +// auto c = bedhint.shape.circ; +// auto cc = to_lnCircle(c); +// result = _arrange(shapes, preshapes, shapes_ptr, cc, min_obj_distance); +// break; +// } +// case BedShapeType::IRREGULAR: +// case BedShapeType::WHO_KNOWS: { +// auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); +// ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); +// result = _arrange(shapes, preshapes, shapes_ptr, irrbed, min_obj_distance); +// break; +// } +// }; - // Now we go through the result which will contain the fixed and the moving - // polygons as well. We will have to search for our item. +// // Now we go through the result which will contain the fixed and the moving +// // polygons as well. We will have to search for our item. - ClipperLib::cInt stride = stride_padding(binbb.width()); - ClipperLib::cInt batch_offset = 0; +// ClipperLib::cInt stride = stride_padding(binbb.width()); +// ClipperLib::cInt batch_offset = 0; - for(auto& group : result) { - for(auto& r : group) if(r.first < shapes.size()) { - Item& resultitem = r.second; - unsigned idx = r.first; - auto offset = resultitem.translation(); - Radians rot = resultitem.rotation(); - ModelInstance *minst = shapes_ptr[idx]; - Vec3d foffset(offset.X*SCALING_FACTOR + batch_offset, - offset.Y*SCALING_FACTOR, - minst->get_offset()(Z)); +// for(auto& group : result) { +// for(auto& r : group) if(r.first < shapes.size()) { +// Item& resultitem = r.second; +// unsigned idx = r.first; +// auto offset = resultitem.translation(); +// Radians rot = resultitem.rotation(); +// ModelInstance *minst = shapes_ptr[idx]; +// Vec3d foffset(unscaled(offset.X + batch_offset), +// unscaled(offset.Y), +// minst->get_offset()(Z)); - // write the transformation data into the model instance - minst->set_rotation(Z, rot); - minst->set_offset(foffset); - } - batch_offset += stride; - } -} +// // write the transformation data into the model instance +// minst->set_rotation(Z, rot); +// minst->set_offset(foffset); +// } +// batch_offset += stride; +// } +//} } diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index b61443da07..cccf1bd575 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -1,7 +1,9 @@ #ifndef MODELARRANGE_HPP #define MODELARRANGE_HPP -#include "Model.hpp" +//#include "Model.hpp" +#include "Polygon.hpp" +#include "BoundingBox.hpp" namespace Slic3r { @@ -40,13 +42,25 @@ struct BedShapeHint { BedShapeHint bedShape(const Polyline& bed); -struct WipeTowerInfo { - bool is_wipe_tower = false; - Vec2d pos; - Vec2d bb_size; - double rotation; +class ArrangeItem { +public: + + virtual ~ArrangeItem() = default; + + virtual void transform(Vec2d offset, double rotation_rads) = 0; + + virtual Polygon silhouette() const = 0; }; +using ArrangeItems = std::vector>; + +//struct WipeTowerInfo { +// bool is_wipe_tower = false; +// Vec2d pos; +// Vec2d bb_size; +// double rotation; +//}; + /** * \brief Arranges the model objects on the screen. * @@ -73,22 +87,33 @@ struct WipeTowerInfo { * packed. The unsigned argument is the number of items remaining to pack. * \param stopcondition A predicate returning true if abort is needed. */ -bool arrange(Model &model, - WipeTowerInfo& wipe_tower_info, +//bool arrange(Model &model, +// WipeTowerInfo& wipe_tower_info, +// coord_t min_obj_distance, +// const Slic3r::Polyline& bed, +// BedShapeHint bedhint, +// bool first_bin_only, +// std::function progressind, +// std::function stopcondition); + +bool arrange(ArrangeItems &items, coord_t min_obj_distance, - const Slic3r::Polyline& bed, BedShapeHint bedhint, - bool first_bin_only, std::function progressind, std::function stopcondition); /// This will find a suitable position for a new object instance and leave the /// old items untouched. -void find_new_position(const Model& model, - ModelInstancePtrs instances_to_add, +//void find_new_position(const Model& model, +// ModelInstancePtrs instances_to_add, +// coord_t min_obj_distance, +// const Slic3r::Polyline& bed, +// WipeTowerInfo& wti); +void find_new_position(ArrangeItems &items, + const ArrangeItems &instances_to_add, coord_t min_obj_distance, - const Slic3r::Polyline& bed, - WipeTowerInfo& wti); + BedShapeHint bedhint); + } // arr } // Slic3r diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 472394d436..b4cfa33687 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -2400,131 +2401,185 @@ void Plater::priv::sla_optimize_rotation() { } void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { + static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; + + class ArrItemModelInstance: public arr::ArrangeItem { + ModelInstance *m_inst = nullptr; + public: + + ArrItemModelInstance() = default; + ArrItemModelInstance(ModelInstance *inst) : m_inst(inst) {} + + virtual void transform(Vec2d offs, double rot_rads) override { + assert(m_inst); + + // write the transformation data into the model instance + m_inst->set_rotation(Z, rot_rads); + m_inst->set_offset(offs); + } + + virtual Polygon silhouette() const override { + assert(m_inst); + + Vec3d rotation = m_inst->get_rotation(); + rotation.z() = 0.; + Transform3d trafo_instance = Geometry::assemble_transform( + Vec3d::Zero(), + rotation, + m_inst->get_scaling_factor(), + m_inst->get_mirror()); + + Polygon p = m_inst->get_object()->convex_hull_2d(trafo_instance); + + assert(!p.points.empty()); + + // this may happen for malformed models, see: + // https://github.com/prusa3d/PrusaSlicer/issues/2209 + if (p.points.empty()) return {}; + + Polygons pp { p }; + pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); + if (!pp.empty()) p = pp.front(); + + return p; + } + }; + + // Count all the items on the bin (all the object's instances) + auto count = std::accumulate(plater().model.objects.begin(), + plater().model.objects.end(), + size_t(0), [](size_t s, ModelObject* o) + { + return s + o->instances.size(); + }); + +// std::vector items(size_t); + // TODO: we should decide whether to allow arrange when the search is // running we should probably disable explicit slicing and background // processing - static const auto arrangestr = _(L("Arranging")); +// static const auto arrangestr = _(L("Arranging")); - auto &config = plater().config; - auto &view3D = plater().view3D; - auto &model = plater().model; +// auto &config = plater().config; +// auto &view3D = plater().view3D; +// auto &model = plater().model; - // 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(config); - } +// // 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(config); +// } - auto min_obj_distance = coord_t(dist / SCALING_FACTOR); +// auto min_obj_distance = coord_t(dist / SCALING_FACTOR); - const auto *bed_shape_opt = config->opt( - "bed_shape"); +// const auto *bed_shape_opt = config->opt( +// "bed_shape"); - assert(bed_shape_opt); - auto & bedpoints = bed_shape_opt->values; - Polyline bed; - bed.points.reserve(bedpoints.size()); - for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); +// assert(bed_shape_opt); +// auto & bedpoints = bed_shape_opt->values; +// Polyline bed; +// bed.points.reserve(bedpoints.size()); +// for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - update_status(0, arrangestr); +// update_status(0, arrangestr); - arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); +// arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); - try { - arr::BedShapeHint hint; +// try { +// arr::BedShapeHint hint; - // TODO: from Sasha from GUI or - hint.type = arr::BedShapeType::WHO_KNOWS; +// // TODO: from Sasha from GUI or +// hint.type = arr::BedShapeType::WHO_KNOWS; - arr::arrange(model, - wti, - min_obj_distance, - bed, - hint, - false, // create many piles not just one pile - [this](unsigned st) { - if (st > 0) - update_status(count - int(st), arrangestr); - }, - [this]() { return was_canceled(); }); - } catch (std::exception & /*e*/) { - GUI::show_error(plater().q, - L("Could not arrange model objects! " - "Some geometries may be invalid.")); - } +// arr::arrange(model, +// wti, +// min_obj_distance, +// bed, +// hint, +// false, // create many piles not just one pile +// [this](unsigned st) { +// if (st > 0) +// update_status(count - int(st), arrangestr); +// }, +// [this]() { return was_canceled(); }); +// } catch (std::exception & /*e*/) { +// GUI::show_error(plater().q, +// L("Could not arrange model objects! " +// "Some geometries may be invalid.")); +// } - update_status(count, - was_canceled() ? _(L("Arranging canceled.")) - : _(L("Arranging done."))); +// update_status(count, +// was_canceled() ? _(L("Arranging canceled.")) +// : _(L("Arranging done."))); - // it remains to move the wipe tower: - view3D->get_canvas3d()->arrange_wipe_tower(wti); +// // it remains to move the wipe tower: +// view3D->get_canvas3d()->arrange_wipe_tower(wti); } void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() { - int obj_idx = plater().get_selected_object_idx(); - if (obj_idx < 0) { return; } +// int obj_idx = plater().get_selected_object_idx(); +// if (obj_idx < 0) { return; } - ModelObject *o = plater().model.objects[size_t(obj_idx)]; +// 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(); }); +// 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(); }); - const auto *bed_shape_opt = - plater().config->opt("bed_shape"); +// const auto *bed_shape_opt = +// plater().config->opt("bed_shape"); - assert(bed_shape_opt); +// assert(bed_shape_opt); - auto & bedpoints = bed_shape_opt->values; - Polyline bed; - bed.points.reserve(bedpoints.size()); - for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); +// auto & bedpoints = bed_shape_opt->values; +// Polyline bed; +// bed.points.reserve(bedpoints.size()); +// for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - double mindist = 6.0; // FIXME +// double mindist = 6.0; // FIXME - if (!was_canceled()) { - for(ModelInstance * oi : o->instances) { - oi->set_rotation({r[X], r[Y], r[Z]}); +// 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); +// 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(); +// MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); +// double r = rotbb.angle_to_X(); - // The box should be landscape - if(rotbb.width() < rotbb.height()) r += PI / 2; +// // The box should be landscape +// if(rotbb.width() < rotbb.height()) r += PI / 2; - Vec3d rt = oi->get_rotation(); rt(Z) += r; +// Vec3d rt = oi->get_rotation(); rt(Z) += r; - oi->set_rotation(rt); - } +// oi->set_rotation(rt); +// } - arr::WipeTowerInfo wti; // useless in SLA context - arr::find_new_position(plater().model, - o->instances, - coord_t(mindist / SCALING_FACTOR), - bed, - wti); +// arr::WipeTowerInfo wti; // useless in SLA context +// arr::find_new_position(plater().model, +// o->instances, +// coord_t(mindist / SCALING_FACTOR), +// bed, +// wti); - // Correct the z offset of the object which was corrupted be - // the rotation - o->ensure_on_bed(); - } +// // 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."))); +// update_status(100, +// was_canceled() ? _(L("Orientation search canceled.")) +// : _(L("Orientation found."))); } void Plater::priv::split_object() From ec28e55ff00617b711cd46f0302d93219e3c3702 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 26 Jun 2019 18:03:13 +0200 Subject: [PATCH 218/627] Get rid of the test.cpp warning --- src/libnest2d/tests/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 363a3930ce..2f2b9beb59 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -555,7 +555,7 @@ TEST(GeometryAlgorithms, NestTest) { size_t partsum = std::accumulate(result.begin(), result.end(), size_t(0), - [](int s, + [](size_t s, const decltype( result)::value_type &bin) { return s += bin.size(); From da3583b1db45ede894b5c6345b3fb853b0bb6eac Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 27 Jun 2019 09:48:19 +0200 Subject: [PATCH 219/627] Fix of https://github.com/prusa3d/PrusaSlicer/issues/2516 --- src/libslic3r/Config.cpp | 8 ++++---- src/libslic3r/Config.hpp | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 0738b77c6a..76329cceed 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -732,18 +732,18 @@ bool DynamicConfig::read_cli(int argc, char** argv, t_config_option_keys* extra, } // Store the option value. const bool existing = this->has(opt_key); - if (keys != nullptr && !existing) { + if (keys != nullptr && ! existing) { // Save the order of detected keys. keys->push_back(opt_key); } ConfigOption *opt_base = this->option(opt_key, true); ConfigOptionVectorBase *opt_vector = opt_base->is_vector() ? static_cast(opt_base) : nullptr; if (opt_vector) { + if (! existing) + // remove the default values + opt_vector->clear(); // Vector values will be chained. Repeated use of a parameter will append the parameter or parameters // to the end of the value. - if (!existing) - // remove the default values - opt_vector->deserialize("", true); if (opt_base->type() == coBools) static_cast(opt_base)->values.push_back(!no); else diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index ee4bc4e463..a7192a5583 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -167,8 +167,10 @@ public: // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions. // This function is useful to split values from multiple extrder / filament settings into separate configurations. virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0; - + // Resize the vector of values, copy the newly added values from opt_default if provided. virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0; + // Clear the values vector. + virtual void clear() = 0; // Get size of this vector. virtual size_t size() const = 0; @@ -277,6 +279,8 @@ public: } } + // Clear the values vector. + void clear() override { this->values.clear(); } size_t size() const override { return this->values.size(); } bool empty() const override { return this->values.empty(); } From 27ee68d2f9666f5ad48d5ffacf1d6ea404156dda Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 27 Jun 2019 11:02:45 +0200 Subject: [PATCH 220/627] WIP Undo / Redo: ModelID / ModelBase renamed to ObjectID / ObjectBase --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Model.cpp | 25 ++-- src/libslic3r/Model.hpp | 140 +++++-------------- src/libslic3r/ObjectID.cpp | 9 ++ src/libslic3r/ObjectID.hpp | 87 ++++++++++++ src/libslic3r/Print.cpp | 8 +- src/libslic3r/PrintBase.hpp | 2 +- src/libslic3r/SLAPrint.cpp | 8 +- src/libslic3r/SLAPrint.hpp | 4 +- src/slic3r/GUI/GLCanvas3D.cpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 2 +- src/slic3r/GUI/Selection.hpp | 2 +- xs/t/15_config.t | 29 ++-- xs/xsp/Config.xsp | 4 +- 15 files changed, 179 insertions(+), 155 deletions(-) create mode 100644 src/libslic3r/ObjectID.cpp create mode 100644 src/libslic3r/ObjectID.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index e1423b1e1a..1bb9401f52 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -114,6 +114,8 @@ add_library(libslic3r STATIC MultiPoint.cpp MultiPoint.hpp MutablePriorityQueue.hpp + ObjectID.cpp + ObjectID.hpp PerimeterGenerator.cpp PerimeterGenerator.hpp PlaceholderParser.cpp diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index da7d9e4d3b..2533f288ba 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -22,18 +22,16 @@ namespace Slic3r { unsigned int Model::s_auto_extruder_id = 1; -size_t ModelBase::s_last_id = 0; - // Unique object / instance ID for the wipe tower. -ModelID wipe_tower_object_id() +ObjectID wipe_tower_object_id() { - static ModelBase mine; + static ObjectBase mine; return mine.id(); } -ModelID wipe_tower_instance_id() +ObjectID wipe_tower_instance_id() { - static ModelBase mine; + static ObjectBase mine; return mine.id(); } @@ -221,7 +219,7 @@ bool Model::delete_object(ModelObject* object) return false; } -bool Model::delete_object(ModelID id) +bool Model::delete_object(ObjectID id) { if (id.id != 0) { size_t idx = 0; @@ -1865,8 +1863,8 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. void check_model_ids_validity(const Model &model) { - std::set ids; - auto check = [&ids](ModelID id) { + std::set ids; + auto check = [&ids](ObjectID id) { assert(id.id > 0); assert(ids.find(id) == ids.end()); ids.insert(id); @@ -1912,14 +1910,13 @@ void check_model_ids_equal(const Model &model1, const Model &model2) } #if 0 -CEREAL_REGISTER_TYPE(Slic3r::ModelBase) CEREAL_REGISTER_TYPE(Slic3r::ModelObject) CEREAL_REGISTER_TYPE(Slic3r::ModelVolume) CEREAL_REGISTER_TYPE(Slic3r::ModelInstance) CEREAL_REGISTER_TYPE(Slic3r::Model) -CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ModelBase, Slic3r::ModelObject) -CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ModelBase, Slic3r::ModelVolume) -CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ModelBase, Slic3r::ModelInstance) -CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ModelBase, Slic3r::Model) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelObject) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelVolume) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelInstance) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::Model) #endif \ No newline at end of file diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 3330f140de..850ea42022 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -2,19 +2,20 @@ #define slic3r_Model_hpp_ #include "libslic3r.h" -#include "PrintConfig.hpp" +#include "Geometry.hpp" #include "Layer.hpp" +#include "ObjectID.hpp" #include "Point.hpp" -#include "TriangleMesh.hpp" +#include "PrintConfig.hpp" #include "Slicing.hpp" +#include "SLA/SLACommon.hpp" +#include "TriangleMesh.hpp" #include #include #include #include #include -#include "Geometry.hpp" -#include namespace Slic3r { @@ -35,82 +36,11 @@ typedef std::vector ModelObjectPtrs; typedef std::vector ModelVolumePtrs; typedef std::vector ModelInstancePtrs; -// Unique identifier of a Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial. -// Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject) -// Valid IDs are strictly positive (non zero). -// It is declared as an object, as some compilers (notably msvcc) consider a typedef size_t equivalent to size_t -// for parameter overload. -class ModelID -{ -public: - ModelID(size_t id) : id(id) {} - - bool operator==(const ModelID &rhs) const { return this->id == rhs.id; } - bool operator!=(const ModelID &rhs) const { return this->id != rhs.id; } - bool operator< (const ModelID &rhs) const { return this->id < rhs.id; } - bool operator> (const ModelID &rhs) const { return this->id > rhs.id; } - bool operator<=(const ModelID &rhs) const { return this->id <= rhs.id; } - bool operator>=(const ModelID &rhs) const { return this->id >= rhs.id; } - - bool valid() const { return id != 0; } - - size_t id; - -private: - ModelID() {} - - friend class cereal::access; - template void serialize(Archive &ar) { ar(id); } -}; - // Unique object / instance ID for the wipe tower. -extern ModelID wipe_tower_object_id(); -extern ModelID wipe_tower_instance_id(); +extern ObjectID wipe_tower_object_id(); +extern ObjectID wipe_tower_instance_id(); -// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID -// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject). -// Achtung! The s_last_id counter is not thread safe, so it is expected, that the ModelBase derived instances -// are only instantiated from the main thread. -class ModelBase -{ -public: - ModelID id() const { return m_id; } - -protected: - // Constructors to be only called by derived classes. - // Default constructor to assign a unique ID. - ModelBase() : m_id(generate_new_id()) {} - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - ModelBase(int) : m_id(ModelID(0)) {} - // The class tree will have virtual tables and type information. - virtual ~ModelBase() {} - - // Use with caution! - void set_new_unique_id() { m_id = generate_new_id(); } - void set_invalid_id() { m_id = 0; } - // Use with caution! - void copy_id(const ModelBase &rhs) { m_id = rhs.id(); } - - // Override this method if a ModelBase derived class owns other ModelBase derived instances. - void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } - -private: - ModelID m_id; - - static inline ModelID generate_new_id() { return ModelID(++ s_last_id); } - static size_t s_last_id; - - friend ModelID wipe_tower_object_id(); - friend ModelID wipe_tower_instance_id(); - - friend class cereal::access; - template void serialize(Archive &ar) { ar(m_id); } - ModelBase(const ModelID id) : m_id(id) {} - template static void load_and_construct(Archive & ar, cereal::construct &construct) { ModelID id; ar(id); construct(id); } -}; - -#define MODELBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ +#define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \ /* to make a private copy for background processing. */ \ static TYPE* new_copy(const TYPE &rhs) { return new TYPE(rhs); } \ @@ -138,14 +68,14 @@ private: return *this; \ } -#define MODELBASE_DERIVED_PRIVATE_COPY_MOVE(TYPE) \ +#define OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE(TYPE) \ private: \ /* Private constructor with an unused int parameter will create a TYPE instance with an invalid ID. */ \ - explicit TYPE(int) : ModelBase(-1) {}; \ + explicit TYPE(int) : ObjectBase(-1) {}; \ void assign_new_unique_ids_recursive(); // Material, which may be shared across multiple ModelObjects of a single Model. -class ModelMaterial : public ModelBase +class ModelMaterial : public ObjectBase { public: // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. @@ -175,14 +105,14 @@ private: friend class cereal::access; ModelMaterial() : m_model(nullptr) {} - template void serialize(Archive &ar) { ar(cereal::base_class(this)); ar(attributes, config); } + template void serialize(Archive &ar) { ar(cereal::base_class(this)); ar(attributes, config); } }; // A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), // and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. // Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, // different rotation and different uniform scaling. -class ModelObject : public ModelBase +class ModelObject : public ObjectBase { friend class Model; public: @@ -323,13 +253,13 @@ private: /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ - ModelObject(const ModelObject &rhs) : ModelBase(-1), m_model(rhs.m_model) { this->assign_copy(rhs); } - explicit ModelObject(ModelObject &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); } + ModelObject(const ModelObject &rhs) : ObjectBase(-1), m_model(rhs.m_model) { this->assign_copy(rhs); } + explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1) { this->assign_copy(std::move(rhs)); } ModelObject& operator=(const ModelObject &rhs) { this->assign_copy(rhs); m_model = rhs.m_model; return *this; } ModelObject& operator=(ModelObject &&rhs) { this->assign_copy(std::move(rhs)); m_model = rhs.m_model; return *this; } - MODELBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) - MODELBASE_DERIVED_PRIVATE_COPY_MOVE(ModelObject) + OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) + OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE(ModelObject) // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized. Model *m_model = nullptr; @@ -345,7 +275,7 @@ private: friend class cereal::access; ModelObject() : m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} template void serialize(Archive &ar) { - ar(cereal::base_class(this)); + ar(cereal::base_class(this)); ar(name, input_file, instances, volumes, config, layer_height_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation, m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); } @@ -362,7 +292,7 @@ enum class ModelVolumeType : int { // An object STL, or a modifier volume, over which a different set of parameters shall be applied. // ModelVolume instances are owned by a ModelObject. -class ModelVolume : public ModelBase +class ModelVolume : public ObjectBase { public: std::string name; @@ -456,7 +386,7 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - using ModelBase::set_new_unique_id; + using ObjectBase::set_new_unique_id; protected: friend class Print; @@ -496,7 +426,7 @@ private: // Copying an existing volume, therefore this volume will get a copy of the ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other) : - ModelBase(other), // copy the ID + ObjectBase(other), // copy the ID name(other.name), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { this->set_material_id(other.material_id()); @@ -515,14 +445,14 @@ private: friend class cereal::access; ModelVolume() : object(nullptr) {} template void serialize(Archive &ar) { - ar(cereal::base_class(this)); + ar(cereal::base_class(this)); ar(name, config, m_mesh, m_type, m_material_id, m_convex_hull, m_transformation, m_is_splittable); } }; // A single instance of a ModelObject. // Knows the affine transformation of an object. -class ModelInstance : public ModelBase +class ModelInstance : public ObjectBase { public: enum EPrintVolumeState : unsigned char @@ -610,7 +540,7 @@ private: friend class cereal::access; ModelInstance() : object(nullptr) {} template void serialize(Archive &ar) { - ar(cereal::base_class(this)); + ar(cereal::base_class(this)); ar(m_transformation, print_volume_state); } }; @@ -620,7 +550,7 @@ private: // and with multiple modifier meshes. // A model groups multiple objects, each object having possibly multiple instances, // all objects may share mutliple materials. -class Model : public ModelBase +class Model : public ObjectBase { static unsigned int s_auto_extruder_id; @@ -637,12 +567,12 @@ public: /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ - Model(const Model &rhs) : ModelBase(-1) { this->assign_copy(rhs); } - explicit Model(Model &&rhs) : ModelBase(-1) { this->assign_copy(std::move(rhs)); } + Model(const Model &rhs) : ObjectBase(-1) { this->assign_copy(rhs); } + explicit Model(Model &&rhs) : ObjectBase(-1) { this->assign_copy(std::move(rhs)); } Model& operator=(const Model &rhs) { this->assign_copy(rhs); return *this; } Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); return *this; } - MODELBASE_DERIVED_COPY_MOVE_CLONE(Model) + OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) static Model read_from_file(const std::string &input_file, DynamicPrintConfig *config = nullptr, bool add_default_instances = true); static Model read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances = true); @@ -653,7 +583,7 @@ public: ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); ModelObject* add_object(const ModelObject &other); void delete_object(size_t idx); - bool delete_object(ModelID id); + bool delete_object(ObjectID id); bool delete_object(ModelObject* object); void clear_objects(); @@ -700,16 +630,16 @@ public: std::string propose_export_file_name_and_path(const std::string &new_extension) const; private: - MODELBASE_DERIVED_PRIVATE_COPY_MOVE(Model) + OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE(Model) friend class cereal::access; template void serialize(Archive &ar) { - ar(cereal::base_class(this), materials, objects); + ar(cereal::base_class(this), materials, objects); } }; -#undef MODELBASE_DERIVED_COPY_MOVE_CLONE -#undef MODELBASE_DERIVED_PRIVATE_COPY_MOVE +#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE +#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. @@ -729,6 +659,6 @@ void check_model_ids_validity(const Model &model); void check_model_ids_equal(const Model &model1, const Model &model2); #endif /* NDEBUG */ -} +} // namespace Slic3r -#endif +#endif /* slic3r_Model_hpp_ */ diff --git a/src/libslic3r/ObjectID.cpp b/src/libslic3r/ObjectID.cpp new file mode 100644 index 0000000000..90681a5a6f --- /dev/null +++ b/src/libslic3r/ObjectID.cpp @@ -0,0 +1,9 @@ +#include "ObjectID.hpp" + +namespace Slic3r { + +size_t ObjectBase::s_last_id = 0; + +} // namespace Slic3r + +// CEREAL_REGISTER_TYPE(Slic3r::ObjectBase) diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp new file mode 100644 index 0000000000..6f9c3fcff6 --- /dev/null +++ b/src/libslic3r/ObjectID.hpp @@ -0,0 +1,87 @@ +#ifndef slic3r_ObjectID_hpp_ +#define slic3r_ObjectID_hpp_ + +#include +#include +#include +#include +#include + +namespace Slic3r { + +// Unique identifier of a mutable object accross the application. +// Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject) +// (for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial classes) +// and to serialize / deserialize an object onto the Undo / Redo stack. +// Valid IDs are strictly positive (non zero). +// It is declared as an object, as some compilers (notably msvcc) consider a typedef size_t equivalent to size_t +// for parameter overload. +class ObjectID +{ +public: + ObjectID(size_t id) : id(id) {} + + bool operator==(const ObjectID &rhs) const { return this->id == rhs.id; } + bool operator!=(const ObjectID &rhs) const { return this->id != rhs.id; } + bool operator< (const ObjectID &rhs) const { return this->id < rhs.id; } + bool operator> (const ObjectID &rhs) const { return this->id > rhs.id; } + bool operator<=(const ObjectID &rhs) const { return this->id <= rhs.id; } + bool operator>=(const ObjectID &rhs) const { return this->id >= rhs.id; } + + bool valid() const { return id != 0; } + + size_t id; + +private: + ObjectID() {} + + friend class cereal::access; + template void serialize(Archive &ar) { ar(id); } +}; + +// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID +// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject). +// Achtung! The s_last_id counter is not thread safe, so it is expected, that the ObjectBase derived instances +// are only instantiated from the main thread. +class ObjectBase +{ +public: + ObjectID id() const { return m_id; } + +protected: + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + ObjectBase() : m_id(generate_new_id()) {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + ObjectBase(int) : m_id(ObjectID(0)) {} + // The class tree will have virtual tables and type information. + virtual ~ObjectBase() {} + + // Use with caution! + void set_new_unique_id() { m_id = generate_new_id(); } + void set_invalid_id() { m_id = 0; } + // Use with caution! + void copy_id(const ObjectBase &rhs) { m_id = rhs.id(); } + + // Override this method if a ObjectBase derived class owns other ObjectBase derived instances. + void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } + +private: + ObjectID m_id; + + static inline ObjectID generate_new_id() { return ObjectID(++ s_last_id); } + static size_t s_last_id; + + friend ObjectID wipe_tower_object_id(); + friend ObjectID wipe_tower_instance_id(); + + friend class cereal::access; + template void serialize(Archive &ar) { ar(m_id); } + ObjectBase(const ObjectID id) : m_id(id) {} + template static void load_and_construct(Archive & ar, cereal::construct &construct) { ObjectID id; ar(id); construct(id); } +}; + +} // namespace Slic3r + +#endif /* slic3r_ObjectID_hpp_ */ diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index f9129f15a1..6d8c6e2bbb 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -732,8 +732,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co Moved, Deleted, }; - ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {} - ModelID id; + ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} + ObjectID id; Status status; // Search by id. bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } @@ -839,9 +839,9 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co print_object(print_object), trafo(print_object->trafo()), status(status) {} - PrintObjectStatus(ModelID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} // ID of the ModelObject & PrintObject - ModelID id; + ObjectID id; // Pointer to the old PrintObject PrintObject *print_object; // Trafo generated with model_object->world_matrix(true) diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index d4c39499c8..d84d492a00 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -246,7 +246,7 @@ public: struct TaskParams { TaskParams() : single_model_object(0), single_model_instance_only(false), to_object_step(-1), to_print_step(-1) {} // If non-empty, limit the processing to this ModelObject. - ModelID single_model_object; + ObjectID single_model_object; // If set, only process single_model_object. Otherwise process everything, but single_model_object first. bool single_model_instance_only; // If non-negative, stop processing at the successive object step. diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index c73ae5650d..aa7cf58b53 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -212,8 +212,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf Moved, Deleted, }; - ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {} - ModelID id; + ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} + ObjectID id; Status status; // Search by id. bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } @@ -316,9 +316,9 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf print_object(print_object), trafo(print_object->trafo()), status(status) {} - PrintObjectStatus(ModelID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} // ID of the ModelObject & PrintObject - ModelID id; + ObjectID id; // Pointer to the old PrintObject SLAPrintObject *print_object; // Trafo generated with model_object->world_matrix(true) diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index b0c5a8fdcc..9620e9b68d 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -54,10 +54,10 @@ public: bool is_left_handed() const { return m_left_handed; } struct Instance { - Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} + Instance(ObjectID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } // ID of the corresponding ModelInstance. - ModelID instance_id; + ObjectID instance_id; // Slic3r::Point objects in scaled G-code coordinates Point shift; // Rotation along the Z axis, in radians. diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index cf8a49ea22..4116ac34bb 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1812,14 +1812,14 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re struct ModelVolumeState { ModelVolumeState(const GLVolume *volume) : model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {} - ModelVolumeState(const ModelVolume *model_volume, const ModelID &instance_id, const GLVolume::CompositeID &composite_id) : + ModelVolumeState(const ModelVolume *model_volume, const ObjectID &instance_id, const GLVolume::CompositeID &composite_id) : model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {} - ModelVolumeState(const ModelID &volume_id, const ModelID &instance_id) : + ModelVolumeState(const ObjectID &volume_id, const ObjectID &instance_id) : model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {} bool new_geometry() const { return this->volume_idx == size_t(-1); } const ModelVolume *model_volume; - // ModelID of ModelVolume + ModelID of ModelInstance - // or timestamp of an SLAPrintObjectStep + ModelID of ModelInstance + // ObjectID of ModelVolume + ObjectID of ModelInstance + // or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance std::pair geometry_id; GLVolume::CompositeID composite_id; // Volume index in the new GLVolume vector. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index cfe07a61cf..9e703caaaa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -380,7 +380,7 @@ bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const bool GLGizmoSlaSupports::is_mesh_update_necessary() const { return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) - && ((m_model_object->id() != m_current_mesh_model_id) || m_its == nullptr); + && ((m_model_object->id() != m_current_mesh_object_id) || m_its == nullptr); } void GLGizmoSlaSupports::update_mesh() @@ -390,7 +390,7 @@ void GLGizmoSlaSupports::update_mesh() // This mesh does not account for the possible Z up SLA offset. m_mesh = &m_model_object->volumes.front()->mesh(); m_its = &m_mesh->its; - m_current_mesh_model_id = m_model_object->id(); + m_current_mesh_object_id = m_model_object->id(); m_editing_mode = false; m_AABB.deinit(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 30238cc9dd..8a5cdf3ef6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -26,7 +26,7 @@ class GLGizmoSlaSupports : public GLGizmoBase { private: ModelObject* m_model_object = nullptr; - ModelID m_current_mesh_model_id = 0; + ObjectID m_current_mesh_object_id = 0; int m_active_instance = -1; float m_active_instance_bb_radius; // to cache the bb mutable float m_z_shift = 0.f; diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 802f8d2847..c23f23e6b0 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -171,7 +171,7 @@ private: Vec3d dragging_center; // Map from indices of ModelObject instances in Model::objects // to a set of indices of ModelVolume instances in ModelObject::instances - // Here the index means a position inside the respective std::vector, not ModelID. + // Here the index means a position inside the respective std::vector, not ObjectID. ObjectIdxsToInstanceIdxsMap content; }; diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 397f7e4796..1f9fc939bc 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -9,19 +9,19 @@ use Test::More tests => 147; foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { $config->set('layer_height', 0.3); ok abs($config->get('layer_height') - 0.3) < 1e-4, 'set/get float'; - is $config->serialize('layer_height'), '0.3', 'serialize float'; + is $config->opt_serialize('layer_height'), '0.3', 'serialize float'; $config->set('perimeters', 2); is $config->get('perimeters'), 2, 'set/get int'; - is $config->serialize('perimeters'), '2', 'serialize int'; + is $config->opt_serialize('perimeters'), '2', 'serialize int'; $config->set('extrusion_axis', 'A'); is $config->get('extrusion_axis'), 'A', 'set/get string'; - is $config->serialize('extrusion_axis'), 'A', 'serialize string'; + is $config->opt_serialize('extrusion_axis'), 'A', 'serialize string'; $config->set('notes', "foo\nbar"); is $config->get('notes'), "foo\nbar", 'set/get string with newline'; - is $config->serialize('notes'), 'foo\nbar', 'serialize string with newline'; + is $config->opt_serialize('notes'), 'foo\nbar', 'serialize string with newline'; $config->set_deserialize('notes', 'bar\nbaz'); is $config->get('notes'), "bar\nbaz", 'deserialize string with newline'; @@ -59,7 +59,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo ) { $config->set('filament_notes', $test_data->{values}); - is $config->serialize('filament_notes'), $test_data->{serialized}, 'serialize multi-string value ' . $test_data->{name}; + is $config->opt_serialize('filament_notes'), $test_data->{serialized}, 'serialize multi-string value ' . $test_data->{name}; $config->set_deserialize('filament_notes', ''); is_deeply $config->get('filament_notes'), [], 'deserialize multi-string value - empty ' . $test_data->{name}; $config->set_deserialize('filament_notes', $test_data->{serialized}); @@ -68,12 +68,12 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set('first_layer_height', 0.3); ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent'; - is $config->serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; + is $config->opt_serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent'; $config->set('first_layer_height', '50%'); $config->get_abs_value('first_layer_height'); ok abs($config->get_abs_value('first_layer_height') - 0.15) < 1e-4, 'set/get relative floatOrPercent'; - is $config->serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; + is $config->opt_serialize('first_layer_height'), '50%', 'serialize relative floatOrPercent'; # Uh-oh, we have no point option to test at the moment #ok $config->set('print_center', [50,80]), 'valid point coordinates'; @@ -86,11 +86,10 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set('use_relative_e_distances', 1); is $config->get('use_relative_e_distances'), 1, 'set/get bool'; - is $config->serialize('use_relative_e_distances'), '1', 'serialize bool'; - + is $config->opt_serialize('use_relative_e_distances'), '1', 'serialize bool'; $config->set('gcode_flavor', 'teacup'); is $config->get('gcode_flavor'), 'teacup', 'set/get enum'; - is $config->serialize('gcode_flavor'), 'teacup', 'serialize enum'; + is $config->opt_serialize('gcode_flavor'), 'teacup', 'serialize enum'; $config->set_deserialize('gcode_flavor', 'mach3'); is $config->get('gcode_flavor'), 'mach3', 'deserialize enum (gcode_flavor)'; $config->set_deserialize('gcode_flavor', 'machinekit'); @@ -106,7 +105,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points'; $config->set('extruder_offset', [Slic3r::Pointf->new(10,20),Slic3r::Pointf->new(30,45)]); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[10,20],[30,45]], 'set/get points'; - is $config->serialize('extruder_offset'), '10x20,30x45', 'serialize points'; + is $config->opt_serialize('extruder_offset'), '10x20,30x45', 'serialize points'; $config->set_deserialize('extruder_offset', '20x10'); is_deeply [ map $_->pp, @{$config->get('extruder_offset')} ], [[20,10]], 'deserialize points'; $config->set_deserialize('extruder_offset', '0x0'); @@ -120,7 +119,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo # truncate ->get() to first decimal digit $config->set('nozzle_diameter', [0.2,3]); is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.2,3], 'set/get floats'; - is $config->serialize('nozzle_diameter'), '0.2,3', 'serialize floats'; + is $config->opt_serialize('nozzle_diameter'), '0.2,3', 'serialize floats'; $config->set_deserialize('nozzle_diameter', '0.1,0.4'); is_deeply [ map int($_*10)/10, @{$config->get('nozzle_diameter')} ], [0.1,0.4], 'deserialize floats'; $config->set_deserialize('nozzle_diameter', '3'); @@ -133,7 +132,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set('temperature', [180,210]); is_deeply $config->get('temperature'), [180,210], 'set/get ints'; - is $config->serialize('temperature'), '180,210', 'serialize ints'; + is $config->opt_serialize('temperature'), '180,210', 'serialize ints'; $config->set_deserialize('temperature', '195,220'); is_deeply $config->get('temperature'), [195,220], 'deserialize ints'; { @@ -147,7 +146,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo is $config->get_at('wipe', 0), 1, 'get_at bools'; is $config->get_at('wipe', 1), 0, 'get_at bools'; is $config->get_at('wipe', 9), 1, 'get_at bools'; - is $config->serialize('wipe'), '1,0', 'serialize bools'; + is $config->opt_serialize('wipe'), '1,0', 'serialize bools'; $config->set_deserialize('wipe', '0,1,1'); is_deeply $config->get('wipe'), [0,1,1], 'deserialize bools'; $config->set_deserialize('wipe', ''); @@ -162,7 +161,7 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo $config->set('post_process', ['foo','bar']); is_deeply $config->get('post_process'), ['foo','bar'], 'set/get strings'; - is $config->serialize('post_process'), 'foo;bar', 'serialize strings'; + is $config->opt_serialize('post_process'), 'foo;bar', 'serialize strings'; $config->set_deserialize('post_process', 'bar;baz'); is_deeply $config->get('post_process'), ['bar','baz'], 'deserialize strings'; { diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 4d48a2c6f1..c374880a13 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -33,7 +33,7 @@ %code{% RETVAL = ConfigBase__set_deserialize(THIS, opt_key, str); %}; void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false) %code{% ConfigBase__set_ifndef(THIS, opt_key, value, deserialize); %}; - std::string serialize(t_config_option_key opt_key); + std::string opt_serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); @@ -95,7 +95,7 @@ %code{% RETVAL = ConfigBase__set_deserialize(THIS, opt_key, str); %}; void set_ifndef(t_config_option_key opt_key, SV* value, bool deserialize = false) %code{% ConfigBase__set_ifndef(THIS, opt_key, value, deserialize); %}; - std::string serialize(t_config_option_key opt_key); + std::string opt_serialize(t_config_option_key opt_key); double get_abs_value(t_config_option_key opt_key); %name{get_abs_value_over} double get_abs_value(t_config_option_key opt_key, double ratio_over); From 90d1ac2c8f67329ae2cfeee9bf8ce24086db923a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 27 Jun 2019 11:25:04 +0200 Subject: [PATCH 221/627] Tech ENABLE_RENDER_PICKING_PASS extended so that user can switch between picking pass texture rendering and regular rendering by pressing [T] key --- src/libslic3r/Technologies.hpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 20 ++++++++++++++++++-- src/slic3r/GUI/GLCanvas3D.hpp | 4 ++++ src/slic3r/GUI/KBShortcutsDialog.cpp | 3 +++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 86a00480b2..f05bc0b577 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -15,7 +15,7 @@ #define ENABLE_RENDER_STATISTICS 0 // Shows an imgui dialog with camera related data #define ENABLE_CAMERA_STATISTICS 0 -// Render the picking pass instead of the main scene +// Render the picking pass instead of the main scene (use [T] key to toggle between regular rendering and picking pass only rendering) #define ENABLE_RENDER_PICKING_PASS 0 diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c3fd020e00..9bb6f45534 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1224,6 +1224,9 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , m_cursor_type(Standard) , m_color_by("volume") , m_reload_delayed(false) +#if ENABLE_RENDER_PICKING_PASS + , m_show_picking_texture(false) +#endif // ENABLE_RENDER_PICKING_PASS , m_render_sla_auxiliaries(true) { if (m_canvas != nullptr) { @@ -1627,7 +1630,10 @@ void GLCanvas3D::render() _picking_pass(); } -#if !ENABLE_RENDER_PICKING_PASS +#if ENABLE_RENDER_PICKING_PASS + if (!m_picking_enabled || !m_show_picking_texture) + { +#endif // ENABLE_RENDER_PICKING_PASS // draw scene glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); _render_background(); @@ -1657,7 +1663,9 @@ void GLCanvas3D::render() _render_current_gizmo(); _render_selection_sidebar_hints(); -#endif // !ENABLE_RENDER_PICKING_PASS +#if ENABLE_RENDER_PICKING_PASS + } +#endif // ENABLE_RENDER_PICKING_PASS #if ENABLE_SHOW_CAMERA_TARGET _render_camera_target(); @@ -2399,6 +2407,14 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) case 'k': { m_camera.select_next_type(); m_dirty = true; break; } case 'O': case 'o': { set_camera_zoom(-1.0); break; } +#if ENABLE_RENDER_PICKING_PASS + case 'T': + case 't': { + m_show_picking_texture = !m_show_picking_texture; + m_dirty = true; + break; + } +#endif // ENABLE_RENDER_PICKING_PASS case 'Z': case 'z': { m_selection.is_empty() ? zoom_to_volumes() : zoom_to_selection(); break; } default: { evt.Skip(); break; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 5a42879039..c891ed06ff 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -477,6 +477,10 @@ private: GCodePreviewVolumeIndex m_gcode_preview_volume_index; +#if ENABLE_RENDER_PICKING_PASS + bool m_show_picking_texture; +#endif // ENABLE_RENDER_PICKING_PASS + #if ENABLE_RENDER_STATISTICS RenderStats m_render_stats; #endif // ENABLE_RENDER_STATISTICS diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 1af658ed36..955c4b60b9 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -154,6 +154,9 @@ void KBShortcutsDialog::fill_shortcuts() plater_shortcuts.push_back(Shortcut("I", L("Zoom in"))); plater_shortcuts.push_back(Shortcut("O", L("Zoom out"))); plater_shortcuts.push_back(Shortcut("ESC", L("Unselect gizmo / Clear selection"))); +#if ENABLE_RENDER_PICKING_PASS + plater_shortcuts.push_back(Shortcut("T", L("Toggle picking pass texture rendering on/off"))); +#endif // ENABLE_RENDER_PICKING_PASS m_full_shortcuts.push_back(std::make_pair(_(L("Plater Shortcuts")), std::make_pair(plater_shortcuts, szRight))); From 1058721dba84f0c7e35a34d7b7b23382004307c1 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 27 Jun 2019 13:42:50 +0200 Subject: [PATCH 222/627] Added visual hints in 3D scene for layers editing --- src/slic3r/GUI/GLCanvas3D.cpp | 11 +- src/slic3r/GUI/GUI_ObjectLayers.cpp | 2 + src/slic3r/GUI/Selection.cpp | 154 +++++++++++++++++++++------- src/slic3r/GUI/Selection.hpp | 5 +- 4 files changed, 126 insertions(+), 46 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 60afcb653a..bdfd0507a4 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3307,7 +3307,8 @@ void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool foc void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type) { - printf("min_z = %.2f, max_z = %.2f, type=%d\n", range.first, range.second, type); + std::string field = "layer_" + std::to_string(type) + "_" + std::to_string(range.first) + "_" + std::to_string(range.second); + handle_sidebar_focus_event(field, true); } void GLCanvas3D::update_ui_from_settings() @@ -4275,13 +4276,7 @@ void GLCanvas3D::_render_sla_slices() const void GLCanvas3D::_render_selection_sidebar_hints() const { - if (m_use_VBOs) - m_shader.start_using(); - - m_selection.render_sidebar_hints(m_sidebar_field); - - if (m_use_VBOs) - m_shader.stop_using(); + m_selection.render_sidebar_hints(m_sidebar_field, m_shader); } diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index d854e54b3e..ff0f55aefc 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -257,6 +257,8 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, this->Bind(wxEVT_KILL_FOCUS, [this, edit_fn](wxFocusEvent& e) { + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); + if (!m_enter_pressed) { #ifndef __WXGTK__ /* Update data for next editor selection. diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index c6c85e21f6..82bc52d3ad 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1070,61 +1070,68 @@ void Selection::render_center(bool gizmo_is_dragging) const } #endif // ENABLE_RENDER_SELECTION_CENTER -void Selection::render_sidebar_hints(const std::string& sidebar_field) const +void Selection::render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const { if (sidebar_field.empty()) return; - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - glsafe(::glEnable(GL_DEPTH_TEST)); + if (!boost::starts_with(sidebar_field, "layer")) + { + shader.start_using(); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + glsafe(::glEnable(GL_LIGHTING)); + } - glsafe(::glEnable(GL_LIGHTING)); + glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glPushMatrix()); - const Vec3d& center = get_bounding_box().center(); - - if (is_single_full_instance() && ! wxGetApp().obj_manipul()->get_world_coordinates()) + if (!boost::starts_with(sidebar_field, "layer")) { - glsafe(::glTranslated(center(0), center(1), center(2))); - if (!boost::starts_with(sidebar_field, "position")) + const Vec3d& center = get_bounding_box().center(); + + if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { - Transform3d orient_matrix = Transform3d::Identity(); - if (boost::starts_with(sidebar_field, "scale")) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::starts_with(sidebar_field, "rotation")) + glsafe(::glTranslated(center(0), center(1), center(2))); + if (!boost::starts_with(sidebar_field, "position")) { - if (boost::ends_with(sidebar_field, "x")) + Transform3d orient_matrix = Transform3d::Identity(); + if (boost::starts_with(sidebar_field, "scale")) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::ends_with(sidebar_field, "y")) + else if (boost::starts_with(sidebar_field, "rotation")) { - const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); - if (rotation(0) == 0.0) + if (boost::ends_with(sidebar_field, "x")) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else - orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); + else if (boost::ends_with(sidebar_field, "y")) + { + const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); + if (rotation(0) == 0.0) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + else + orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); + } } + + glsafe(::glMultMatrixd(orient_matrix.data())); } + } + else if (is_single_volume() || is_single_modifier()) + { + glsafe(::glTranslated(center(0), center(1), center(2))); + Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + if (!boost::starts_with(sidebar_field, "position")) + orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } - } - else if (is_single_volume() || is_single_modifier()) - { - glsafe(::glTranslated(center(0), center(1), center(2))); - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - if (!boost::starts_with(sidebar_field, "position")) - orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); - - glsafe(::glMultMatrixd(orient_matrix.data())); - } - else - { - glsafe(::glTranslated(center(0), center(1), center(2))); - if (requires_local_axes()) + else { - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - glsafe(::glMultMatrixd(orient_matrix.data())); + glsafe(::glTranslated(center(0), center(1), center(2))); + if (requires_local_axes()) + { + Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + glsafe(::glMultMatrixd(orient_matrix.data())); + } } } @@ -1136,10 +1143,16 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const render_sidebar_scale_hints(sidebar_field); else if (boost::starts_with(sidebar_field, "size")) render_sidebar_size_hints(sidebar_field); + else if (boost::starts_with(sidebar_field, "layer")) + render_sidebar_layers_hints(sidebar_field); glsafe(::glPopMatrix()); - glsafe(::glDisable(GL_LIGHTING)); + if (!boost::starts_with(sidebar_field, "layer")) + { + glsafe(::glDisable(GL_LIGHTING)); + shader.stop_using(); + } } bool Selection::requires_local_axes() const @@ -1709,6 +1722,75 @@ void Selection::render_sidebar_size_hints(const std::string& sidebar_field) cons render_sidebar_scale_hints(sidebar_field); } +void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const +{ + static const double Margin = 10.0; + + std::string field = sidebar_field; + + // extract max_z + std::string::size_type pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + double max_z = std::stod(field.substr(pos + 1)); + + // extract min_z + field = field.substr(0, pos); + pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + double min_z = std::stod(field.substr(pos + 1)); + + // extract type + field = field.substr(0, pos); + pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + int type = std::stoi(field.substr(pos + 1)); + + const BoundingBoxf3& box = get_bounding_box(); + + const float min_x = box.min(0) - Margin; + const float max_x = box.max(0) + Margin; + const float min_y = box.min(1) - Margin; + const float max_y = box.max(1) + Margin; + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + // Draw the min_z plane + ::glBegin(GL_QUADS); + if (type == 1) + ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); + else + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, min_z); + ::glVertex3f(max_x, min_y, min_z); + ::glVertex3f(max_x, max_y, min_z); + ::glVertex3f(min_x, max_y, min_z); + glsafe(::glEnd()); + + // Draw the max_z plane + ::glBegin(GL_QUADS); + if (type == 2) + ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); + else + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, max_z); + ::glVertex3f(max_x, min_y, max_z); + ::glVertex3f(max_x, max_y, max_z); + ::glVertex3f(min_x, max_y, max_z); + glsafe(::glEnd()); + + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); +} + void Selection::render_sidebar_position_hint(Axis axis) const { m_arrow.set_color(AXES_COLOR[axis], 3); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 802f8d2847..31b68917ea 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -11,8 +11,8 @@ typedef class GLUquadric GLUquadricObj; #endif // ENABLE_RENDER_SELECTION_CENTER namespace Slic3r { +class Shader; namespace GUI { - class TransformationType { public: @@ -302,7 +302,7 @@ public: #if ENABLE_RENDER_SELECTION_CENTER void render_center(bool gizmo_is_dragging) const; #endif // ENABLE_RENDER_SELECTION_CENTER - void render_sidebar_hints(const std::string& sidebar_field) const; + void render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const; bool requires_local_axes() const; @@ -332,6 +332,7 @@ private: void render_sidebar_rotation_hints(const std::string& sidebar_field) const; void render_sidebar_scale_hints(const std::string& sidebar_field) const; void render_sidebar_size_hints(const std::string& sidebar_field) const; + void render_sidebar_layers_hints(const std::string& sidebar_field) const; void render_sidebar_position_hint(Axis axis) const; void render_sidebar_rotation_hint(Axis axis) const; void render_sidebar_scale_hint(Axis axis) const; From cecc1345508b684dd90f648df29a2d2b012031fb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 27 Jun 2019 13:54:51 +0200 Subject: [PATCH 223/627] Rewrote layers information export/import to/from 3mf using Boost Property Tree (xml_parser) --- src/libslic3r/Format/3mf.cpp | 144 +++++++++++++++-------------------- 1 file changed, 62 insertions(+), 82 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 45b8fe52fe..19802da84c 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -16,6 +16,12 @@ #include #include +#include +#include +#include +#include +namespace pt = boost::property_tree; + #include #include #include "miniz_extension.hpp" @@ -34,7 +40,7 @@ const std::string RELATIONSHIPS_FILE = "_rels/.rels"; const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; -const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Slic3r_PE_layer_config_ranges.txt"; +const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml"; const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const char* MODEL_TAG = "model"; @@ -796,43 +802,20 @@ namespace Slic3r { return; } - if (buffer.back() == '|') - buffer.pop_back(); + std::istringstream iss(buffer); // wrap returned xml to istringstream + pt::ptree objects_tree; + pt::read_xml(iss, objects_tree); - std::vector objects; - boost::split(objects, buffer, boost::is_any_of("|"), boost::token_compress_off); - - for (std::string& object : objects) + for (const auto& object : objects_tree.get_child("objects")) { - // delete all spaces - boost::replace_all(object, " ", ""); - - std::vector object_data; - boost::split(object_data, object, boost::is_any_of("*"), boost::token_compress_off); - /* there should be at least one layer config range in the object - * object_data[0] => object information - * object_data[i>=1] => range information - */ - if (object_data.size() < 2) { - add_error("Error while reading object data"); - continue; - } - - std::vector object_data_id; - boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); - if (object_data_id.size() != 2) { - add_error("Error while reading object id"); - continue; - } - - // get object information - int object_id = std::atoi(object_data_id[1].c_str()); - if (object_id == 0) { + pt::ptree object_tree = object.second; + int obj_idx = object_tree.get(".id", -1); + if (obj_idx <= 0) { add_error("Found invalid object id"); continue; } - IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(object_id); + IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(obj_idx); if (object_item != m_layer_config_ranges.end()) { add_error("Found duplicated layer config range"); continue; @@ -840,56 +823,26 @@ namespace Slic3r { t_layer_config_ranges config_ranges; - // get ranges information - for (size_t i = 1; i < object_data.size(); ++i) + for (const auto& range : object_tree.get_child("ranges")) { - if (object_data[i].back() == '\n') - object_data[i].pop_back(); - - std::vector range_data; - boost::split(range_data, object_data[i], boost::is_any_of("\n"), boost::token_compress_off); - /* There should be at least two options for layer config range - * range_data[0] => Z range information - * range_data[i>=1] => configuration for the range - */ - if (range_data.size() < 3) { - add_error("Found invalid layer config range"); - continue; - } - - std::vector z_range_str; - boost::split(z_range_str, range_data[0], boost::is_any_of("="), boost::token_compress_off); - if (z_range_str.size() != 2) { - add_error("Error while reading layer config range"); - continue; - } - - std::vector z_values; - boost::split(z_values, z_range_str[1], boost::is_any_of(";"), boost::token_compress_off); - if (z_values.size() != 2) { - add_error("Found invalid layer config range"); - continue; - } + pt::ptree range_tree = range.second; + double min_z = range_tree.get(".min_z"); + double max_z = range_tree.get(".max_z"); // get Z range information - t_layer_height_range z_range = { (coordf_t)std::atof(z_values[0].c_str()) , (coordf_t)std::atof(z_values[1].c_str()) }; - DynamicPrintConfig& config = config_ranges[z_range]; + DynamicPrintConfig& config = config_ranges[{ min_z, max_z }]; - // get configuration options for the range - for (size_t j = 1; j < range_data.size(); ++j) + for (const auto& option : range_tree.get_child("config")) { - std::vector key_val; - boost::split(key_val, range_data[j], boost::is_any_of("="), boost::token_compress_off); - if (key_val.size() != 2) { - add_error("Error while reading config value"); - continue; - } - config.set_deserialize(key_val[0], key_val[1]); + std::string opt_key = option.second.get(".opt_key"); + std::string value = option.second.data(); + + config.set_deserialize(opt_key, value); } } if (!config_ranges.empty()) - m_layer_config_ranges.insert(IdToLayerConfigRangesMap::value_type(object_id, config_ranges)); + m_layer_config_ranges.insert(IdToLayerConfigRangesMap::value_type(obj_idx, config_ranges)); } } } @@ -2152,7 +2105,7 @@ namespace Slic3r { bool _3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) { std::string out = ""; - char buffer[1024]; + pt::ptree tree; unsigned int object_cnt = 0; for (const ModelObject* object : model.objects) @@ -2161,26 +2114,53 @@ namespace Slic3r { const t_layer_config_ranges& ranges = object->layer_config_ranges; if (!ranges.empty()) { - sprintf(buffer, "object_id=%d\n", object_cnt); - out += buffer; + pt::ptree& obj_tree = tree.add("objects.object",""); + + obj_tree.put(".id", object_cnt); + obj_tree.add("ranges", ""); // Store the layer config ranges. for (const auto& range : ranges) { + pt::ptree& range_tree = obj_tree.add("ranges.range", ""); + // store minX and maxZ - sprintf(buffer, "*z_range = %f;%f\n", range.first.first, range.first.second); - out += buffer; + range_tree.put(".min_z", range.first.first); + range_tree.put(".max_z", range.first.second); + range_tree.add("config",""); // store range configuration const DynamicPrintConfig& config = range.second; - for (const std::string& key : config.keys()) - out += " " + key + " = " + config.serialize(key) + "\n"; + for (const std::string& opt_key : config.keys()) + { + pt::ptree& opt_tree = range_tree.add("config.option", config.serialize(opt_key)); + opt_tree.put(".opt_key", opt_key); + } } - - out += "|"; } } + if (!tree.empty()) + { + std::ostringstream oss; + boost::property_tree::write_xml(oss, tree); + out = oss.str(); + + // Post processing of the output string for a better preview + // JUST + // boost::replace_all(out, "><", ">\n<"); + // OR more "beautification" + boost::replace_all(out, ">\n \n \n \n \n \n \n \n \n Date: Thu, 27 Jun 2019 14:13:07 +0200 Subject: [PATCH 224/627] Fixed small typo --- src/libslic3r/Format/3mf.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 19802da84c..b526f68b55 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -17,7 +17,6 @@ #include #include -#include #include #include namespace pt = boost::property_tree; @@ -2156,7 +2155,7 @@ namespace Slic3r { boost::replace_all(out, ">\n \n \n \n ", ">\n "); boost::replace_all(out, ">\n \n Date: Thu, 27 Jun 2019 14:42:55 +0200 Subject: [PATCH 225/627] Removed memory leaks due to GUI_App::app_config, GUI_App::preset_bundle and GUI_App::preset_updater not being deleted --- src/slic3r/GUI/GUI_App.cpp | 12 ++++++++++++ src/slic3r/GUI/GUI_App.hpp | 1 + 2 files changed, 13 insertions(+) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 4f1c3adc8b..0e79a3d028 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -141,6 +141,18 @@ GUI_App::GUI_App() , m_imgui(new ImGuiWrapper()) {} +GUI_App::~GUI_App() +{ + if (app_config != nullptr) + delete app_config; + + if (preset_bundle != nullptr) + delete preset_bundle; + + if (preset_updater != nullptr) + delete preset_updater; +} + bool GUI_App::OnInit() { try { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 3f8b23e2d5..5a4da22d3f 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -95,6 +95,7 @@ public: bool initialized() const { return m_initialized; } GUI_App(); + ~GUI_App(); static unsigned get_colour_approx_luma(const wxColour &colour); static bool dark_mode(); From 97bb4a80cc9dc3a16d940e51974b31fa17e9cc54 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 27 Jun 2019 15:16:36 +0200 Subject: [PATCH 226/627] Removed memory leaks due to Sidebar::priv::object_manipulation, Sidebar::priv::object_settings and Sidebar::priv::frequently_changed_parameters not being deleted --- src/slic3r/GUI/Plater.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 472394d436..65cf326df7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -614,10 +614,10 @@ struct Sidebar::priv PresetComboBox *combo_printer; wxBoxSizer *sizer_params; - FreqChangedParams *frequently_changed_parameters; - ObjectList *object_list; - ObjectManipulation *object_manipulation; - ObjectSettings *object_settings; + FreqChangedParams *frequently_changed_parameters{ nullptr }; + ObjectList *object_list{ nullptr }; + ObjectManipulation *object_manipulation{ nullptr }; + ObjectSettings *object_settings{ nullptr }; ObjectInfo *object_info; SlicedInfo *sliced_info; @@ -626,10 +626,23 @@ struct Sidebar::priv wxButton *btn_send_gcode; priv(Plater *plater) : plater(plater) {} + ~priv(); void show_preset_comboboxes(); }; +Sidebar::priv::~priv() +{ + if (object_manipulation != nullptr) + delete object_manipulation; + + if (object_settings != nullptr) + delete object_settings; + + if (frequently_changed_parameters != nullptr) + delete frequently_changed_parameters; +} + void Sidebar::priv::show_preset_comboboxes() { const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA; From 6cfb9bec363f6d485bcf8b5b73747df8c793e320 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 27 Jun 2019 15:23:03 +0200 Subject: [PATCH 227/627] Removed memory leaks due to Plater::priv::config not being deleted --- src/slic3r/GUI/Plater.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 65cf326df7..a0deb52e32 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1526,6 +1526,7 @@ struct Plater::priv static const std::regex pattern_prusa; priv(Plater *q, MainFrame *main_frame); + ~priv(); void update(bool force_full_scene_refresh = false); void select_view(const std::string& direction); @@ -1795,6 +1796,12 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) camera.set_type(get_config("use_perspective_camera")); } +Plater::priv::~priv() +{ + if (config != nullptr) + delete config; +} + void Plater::priv::update(bool force_full_scene_refresh) { // the following line, when enabled, causes flickering on NVIDIA graphics cards From 0b940ec0895cac520eea06c5e1582544a582c019 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 27 Jun 2019 15:57:29 +0200 Subject: [PATCH 228/627] Some code improvements --- src/libslic3r/Format/3mf.cpp | 33 +++++++++++++++------------------ src/slic3r/GUI/Selection.cpp | 3 +++ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index b526f68b55..b866e640eb 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -822,8 +822,10 @@ namespace Slic3r { t_layer_config_ranges config_ranges; - for (const auto& range : object_tree.get_child("ranges")) + for (const auto& range : object_tree) { + if (range.first != "range") + continue; pt::ptree range_tree = range.second; double min_z = range_tree.get(".min_z"); double max_z = range_tree.get(".max_z"); @@ -831,8 +833,10 @@ namespace Slic3r { // get Z range information DynamicPrintConfig& config = config_ranges[{ min_z, max_z }]; - for (const auto& option : range_tree.get_child("config")) + for (const auto& option : range_tree) { + if (option.first != "option") + continue; std::string opt_key = option.second.get(".opt_key"); std::string value = option.second.data(); @@ -2116,23 +2120,21 @@ namespace Slic3r { pt::ptree& obj_tree = tree.add("objects.object",""); obj_tree.put(".id", object_cnt); - obj_tree.add("ranges", ""); // Store the layer config ranges. for (const auto& range : ranges) { - pt::ptree& range_tree = obj_tree.add("ranges.range", ""); + pt::ptree& range_tree = obj_tree.add("range", ""); // store minX and maxZ range_tree.put(".min_z", range.first.first); range_tree.put(".max_z", range.first.second); - range_tree.add("config",""); // store range configuration const DynamicPrintConfig& config = range.second; for (const std::string& opt_key : config.keys()) { - pt::ptree& opt_tree = range_tree.add("config.option", config.serialize(opt_key)); + pt::ptree& opt_tree = range_tree.add("option", config.serialize(opt_key)); opt_tree.put(".opt_key", opt_key); } } @@ -2145,19 +2147,14 @@ namespace Slic3r { boost::property_tree::write_xml(oss, tree); out = oss.str(); - // Post processing of the output string for a better preview - // JUST - // boost::replace_all(out, "><", ">\n<"); - // OR more "beautification" + // Post processing("beautification") of the output string for a better preview boost::replace_all(out, ">\n \n \n \n \n \n ", ">\n "); - boost::replace_all(out, ">\n \n \n \n ", ">\n "); + boost::replace_all(out, ">", ">\n "); + // OR just + boost::replace_all(out, "><", ">\n<"); } if (!out.empty()) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 82bc52d3ad..9939c291e0 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -331,6 +331,9 @@ void Selection::clear() // resets the cache in the sidebar wxGetApp().obj_manipul()->reset_cache(); + + // #et_FIXME fake KillFocus from sidebar + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); } // Update the selection based on the new instance IDs. From 19e6bf58dd2a020eca773f38df3ded1550a02890 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 27 Jun 2019 21:13:44 +0200 Subject: [PATCH 229/627] WIP on structuring arrange inputs --- src/libslic3r/MTUtils.hpp | 17 ++-- src/libslic3r/Model.cpp | 30 +++++++ src/libslic3r/Model.hpp | 13 ++- src/libslic3r/ModelArrange.cpp | 160 +++++++++++++++++++++++++-------- src/libslic3r/ModelArrange.hpp | 27 +++--- src/slic3r/GUI/GLCanvas3D.cpp | 37 ++++---- src/slic3r/GUI/GLCanvas3D.hpp | 33 ++++++- src/slic3r/GUI/Plater.cpp | 127 +++++++++++++------------- 8 files changed, 298 insertions(+), 146 deletions(-) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index ce26887f2c..e7f7ed3e67 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -276,8 +276,7 @@ using EigenVec = Eigen::Matrix; // Semantics are the following: // Upscaling (scaled()): only from floating point types (or Vec) to either // floating point or integer 'scaled coord' coordinates. -// Downscaling (unscaled()): from arithmetic types (or Vec) to either -// floating point only +// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only // Conversion definition from unscaled to floating point scaled template> inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_NOEXCEPT { - return static_cast(v / static_cast(SCALING_FACTOR)); + return Tout(v / Tin(SCALING_FACTOR)); } // Conversion definition from unscaled to integer 'scaled coord'. -// TODO: is the rounding necessary ? Here it is to show that it can be different -// but it does not have to be. Using std::round means loosing noexcept and -// constexpr modifiers +// TODO: is the rounding necessary? Here it is commented out to show that +// it can be different for integers but it does not have to be. Using +// std::round means loosing noexcept and constexpr modifiers template> inline SLIC3R_CONSTEXPR ScaledCoordOnly scaled(const Tin &v) SLIC3R_NOEXCEPT { //return static_cast(std::round(v / SCALING_FACTOR)); - return static_cast(v / static_cast(SCALING_FACTOR)); + return Tout(v / Tin(SCALING_FACTOR)); } // Conversion for Eigen vectors (N dimensional points) template> inline EigenVec, N> scaled(const EigenVec &v) { - return v.template cast() / SCALING_FACTOR; + return v.template cast() /*/ SCALING_FACTOR*/; } // Conversion from arithmetic scaled type to floating point unscaled @@ -314,7 +313,7 @@ template> inline SLIC3R_CONSTEXPR Tout unscaled(const Tin &v) SLIC3R_NOEXCEPT { - return static_cast(v * static_cast(SCALING_FACTOR)); + return Tout(v * Tout(SCALING_FACTOR)); } // Unscaling for Eigen vectors. Input base type can be arithmetic, output base diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 9d68f7141d..6b88987df0 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,5 +1,6 @@ #include "Model.hpp" #include "Geometry.hpp" +#include "MTUtils.hpp" #include "Format/AMF.hpp" #include "Format/OBJ.hpp" @@ -1800,6 +1801,35 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } +Polygon ModelInstance::get_arrange_polygon() const +{ + static const double SIMPLIFY_TOLERANCE_MM = 0.1; + + assert(m_inst); + + Vec3d rotation = get_rotation(); + rotation.z() = 0.; + Transform3d trafo_instance = Geometry:: + assemble_transform(Vec3d::Zero(), + rotation, + get_scaling_factor(), + get_mirror()); + + Polygon p = get_object()->convex_hull_2d(trafo_instance); + + assert(!p.points.empty()); + + // this may happen for malformed models, see: + // https://github.com/prusa3d/PrusaSlicer/issues/2209 + if (p.points.empty()) return {}; + + Polygons pp{p}; + pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); + if (!pp.empty()) p = pp.front(); + + return p; +} + // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. bool model_object_list_equal(const Model &model_old, const Model &model_new) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 0fd1140f0a..85a94b0fdf 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -7,6 +7,7 @@ #include "Point.hpp" #include "TriangleMesh.hpp" #include "Slicing.hpp" +#include "ModelArrange.hpp" #include #include @@ -490,7 +491,7 @@ private: // A single instance of a ModelObject. // Knows the affine transformation of an object. -class ModelInstance : public ModelBase +class ModelInstance : public ModelBase, public arr::Arrangeable { public: enum EPrintVolumeState : unsigned char @@ -552,6 +553,16 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } bool is_printable() const { return print_volume_state == PVS_Inside; } + + virtual void set_arrange_result(Vec2d offs, double rot_rads) final + { + // write the transformation data into the model instance + set_rotation(Z, get_rotation(Z) + rot_rads); + set_offset(X, get_offset(X) + offs(X)); + set_offset(Y, get_offset(Y) + offs(Y)); + } + + virtual Polygon get_arrange_polygon() const final; protected: friend class Print; diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index d50e03bf20..1fe7552b04 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -42,6 +42,8 @@ namespace arr { using namespace libnest2d; +using Shape = ClipperLib::Polygon; + // Only for debugging. Prints the model object vertices on stdout. //std::string toString(const Model& model, bool holes = true) { // std::stringstream ss; @@ -129,9 +131,7 @@ namespace bgi = boost::geometry::index; using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; -using ItemGroup = std::vector>; -template -using TPacker = typename placers::_NofitPolyPlacer; +using ItemGroup = std::vector>>; const double BIG_ITEM_TRESHOLD = 0.02; @@ -156,10 +156,10 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) { // at the same time, it has to provide reasonable results. std::tuple objfunc(const PointImpl& bincenter, - const TMultiShape& merged_pile, + const TMultiShape& merged_pile, const Box& pilebb, const ItemGroup& items, - const Item &item, + const _Item &item, double bin_area, double norm, // A norming factor for physical dimensions // a spatial index to quickly get neighbors of the candidate item @@ -225,7 +225,7 @@ objfunc(const PointImpl& bincenter, mp.emplace_back(item.transformedShape()); auto chull = sl::convexHull(mp); - placers::EdgeCache ec(chull); + placers::EdgeCache ec(chull); double circ = ec.circumference() / norm; double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; @@ -256,7 +256,7 @@ objfunc(const PointImpl& bincenter, for(auto& e : result) { // now get the score for the best alignment auto idx = e.second; - Item& p = items[idx]; + _Item& p = items[idx]; auto parea = p.area(); if(std::abs(1.0 - parea/item.area()) < 1e-6) { auto bb = boundingBox(p.boundingBox(), ibb); @@ -322,12 +322,12 @@ class _ArrBase { public: // Useful type shortcuts... - using Placer = TPacker; - using Selector = FirstFitSelection; + using Placer = typename placers::_NofitPolyPlacer; + using Selector = selections::_FirstFitSelection; using Packer = Nester; using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; - using Pile = TMultiShape; + using Pile = TMultiShape; protected: @@ -373,7 +373,7 @@ public: }; for(unsigned idx = 0; idx < items.size(); ++idx) { - Item& itm = items[idx]; + _Item& itm = items[idx]; if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx}); m_smallsrtree.insert({itm.boundingBox(), idx}); } @@ -382,13 +382,13 @@ public: m_pck.progressIndicator(progressind); m_pck.stopCondition(stopcond); } - - template inline IndexedPackGroup operator()(Args&&...args) { + + template inline _PackGroup operator()(Args&&...args) { m_rtree.clear(); - return m_pck.executeIndexed(std::forward(args)...); + return m_pck.execute(std::forward(args)...); } - - inline void preload(const PackGroup& pg) { + + inline void preload(const _PackGroup& pg) { m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; m_pconf.object_function = nullptr; // drop the special objectfunction m_pck.preload(pg); @@ -396,14 +396,14 @@ public: // Build the rtree for queries to work for(const ItemGroup& grp : pg) for(unsigned idx = 0; idx < grp.size(); ++idx) { - Item& itm = grp[idx]; + _Item& itm = grp[idx]; m_rtree.insert({itm.boundingBox(), idx}); } m_pck.configure(m_pconf); } - bool is_colliding(const Item& item) { + bool is_colliding(const _Item& item) { if(m_rtree.empty()) return false; std::vector result; m_rtree.query(bgi::intersects(item.boundingBox()), @@ -425,7 +425,7 @@ public: // Here we set up the actual object function that calls the common // object function for all bin shapes than does an additional inside // check for the arranged pile. - m_pconf.object_function = [this, bin] (const Item &item) { + m_pconf.object_function = [this, bin] (const _Item &item) { auto result = objfunc(bin.center(), m_merged_pile, @@ -468,7 +468,7 @@ public: _ArrBase(bin, dist, progressind, stopcond) { // As with the box, only the inside check is different. - m_pconf.object_function = [this, &bin] (const Item &item) { + m_pconf.object_function = [this, &bin] (const _Item &item) { auto result = objfunc(bin.center(), m_merged_pile, @@ -483,7 +483,7 @@ public: double score = std::get<0>(result); - auto isBig = [this](const Item& itm) { + auto isBig = [this](const _Item& itm) { return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; }; @@ -512,7 +512,7 @@ public: std::function stopcond = [](){return false;}): _ArrBase(bin, dist, progressind, stopcond) { - m_pconf.object_function = [this, &bin] (const Item &item) { + m_pconf.object_function = [this, &bin] (const _Item &item) { auto binbb = sl::boundingBox(bin); auto result = objfunc(binbb.center(), @@ -540,11 +540,11 @@ public: template<> class AutoArranger: public _ArrBase { public: - AutoArranger(Distance dist, std::function progressind, + AutoArranger(bool, Distance dist, std::function progressind, std::function stopcond): _ArrBase(Box(0, 0), dist, progressind, stopcond) { - this->m_pconf.object_function = [this] (const Item &item) { + this->m_pconf.object_function = [this] (const _Item &item) { auto result = objfunc({0, 0}, m_merged_pile, @@ -782,18 +782,18 @@ BedShapeHint bedShape(const Polyline &bed) { return ret; } -static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; +//static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; -//template -//IndexedPackGroup _arrange(std::vector> &shapes, -// const BinT & bin, -// coord_t minobjd, -// std::function prind, -// std::function stopfn) -//{ -// AutoArranger arranger{bin, minobjd, prind, stopfn}; -// return arranger(shapes.begin(), shapes.end()); -//} +template +_PackGroup _arrange(std::vector &shapes, + const BinT & bin, + coord_t minobjd, + std::function prind, + std::function stopfn) +{ + AutoArranger arranger{bin, minobjd, prind, stopfn}; + return arranger(shapes.begin(), shapes.end()); +} //template //IndexedPackGroup _arrange(std::vector> &shapes, @@ -845,11 +845,99 @@ static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; // return arranger(shapes.begin(), shapes.end()); //} -inline SLIC3R_CONSTEXPR libnest2d::Coord stride_padding(Coord w) +inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) { return w + w / 5; } +bool arrange(ArrangeableRefs & arrangables, + coord_t min_obj_distance, + BedShapeHint bedhint, + std::function progressind, + std::function stopcondition) +{ + bool ret = true; + + std::vector shapes; + shapes.reserve(arrangables.size()); + size_t id = 0; + for (Arrangeable &iref : arrangables) { + Polygon p = iref.get_arrange_polygon(); + + p.reverse(); + assert(!p.is_counter_clockwise()); + + Shape clpath(/*id++,*/ Slic3rMultiPoint_to_ClipperPath(p)); + + auto firstp = clpath.Contour.front(); clpath.Contour.emplace_back(firstp); + shapes.emplace_back(std::move(clpath)); + } + + _PackGroup result; + + auto& cfn = stopcondition; + + // Integer ceiling the min distance from the bed perimeters + coord_t md = min_obj_distance - SCALED_EPSILON; + md = (md % 2) ? md / 2 + 1 : md / 2; + coord_t binwidth = 0; + + switch (bedhint.type) { + case BedShapeType::BOX: { + // Create the arranger for the box shaped bed + BoundingBox bbb = bedhint.shape.box; + + auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, + ClipperLib::cInt{bbb.min(1)} - md}, + {ClipperLib::cInt{bbb.max(0)} + md, + ClipperLib::cInt{bbb.max(1)} + md}); + + result = _arrange(shapes, binbb, min_obj_distance, progressind, cfn); + binwidth = coord_t(binbb.width()); + break; + } + case BedShapeType::CIRCLE: { + auto c = bedhint.shape.circ; + auto cc = to_lnCircle(c); + result = _arrange(shapes, cc, min_obj_distance, progressind, cfn); + binwidth = scaled(c.radius()); + break; + } + case BedShapeType::IRREGULAR: { + auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon); + ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); + result = _arrange(shapes, irrbed, min_obj_distance, progressind, cfn); + BoundingBox polybb(bedhint.shape.polygon); + binwidth = (polybb.max(X) - polybb.min(X)); + break; + } + case BedShapeType::WHO_KNOWS: { + result = _arrange(shapes, false, min_obj_distance, progressind, cfn); + break; + } + }; + + if(result.empty() || stopcondition()) return false; + + ClipperLib::cInt stride = stride_padding(binwidth); + ClipperLib::cInt batch_offset = 0; + + for (const auto &group : result) { + for (_Item &itm : group) { + ClipperLib::IntPoint offs = itm.translation(); +// arrangables[itm.id()].get().set_arrange_result({offs.X, offs.Y}, +// itm.rotation()); + } + + // Only the first pack group can be placed onto the print bed. The + // other objects which could not fit will be placed next to the + // print bed + batch_offset += stride; + } + + return ret; +} + //// The final client function to arrange the Model. A progress indicator and //// a stop predicate can be also be passed to control the process. //bool arrange(Model &model, // The model with the geometries diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index cccf1bd575..77b357f4ea 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -32,8 +32,8 @@ enum class BedShapeType { }; struct BedShapeHint { - BedShapeType type; - /*union*/ struct { // I know but who cares... + BedShapeType type = BedShapeType::WHO_KNOWS; + /*union*/ struct { // I know but who cares... TODO: use variant from cpp17? Circle circ; BoundingBox box; Polyline polygon; @@ -42,24 +42,17 @@ struct BedShapeHint { BedShapeHint bedShape(const Polyline& bed); -class ArrangeItem { +class Arrangeable { public: - virtual ~ArrangeItem() = default; + virtual ~Arrangeable() = default; - virtual void transform(Vec2d offset, double rotation_rads) = 0; + virtual void set_arrange_result(Vec2d offset, double rotation_rads) = 0; - virtual Polygon silhouette() const = 0; + virtual Polygon get_arrange_polygon() const = 0; }; -using ArrangeItems = std::vector>; - -//struct WipeTowerInfo { -// bool is_wipe_tower = false; -// Vec2d pos; -// Vec2d bb_size; -// double rotation; -//}; +using ArrangeableRefs = std::vector>; /** * \brief Arranges the model objects on the screen. @@ -96,7 +89,7 @@ using ArrangeItems = std::vector>; // std::function progressind, // std::function stopcondition); -bool arrange(ArrangeItems &items, +bool arrange(ArrangeableRefs &items, coord_t min_obj_distance, BedShapeHint bedhint, std::function progressind, @@ -109,8 +102,8 @@ bool arrange(ArrangeItems &items, // coord_t min_obj_distance, // const Slic3r::Polyline& bed, // WipeTowerInfo& wti); -void find_new_position(ArrangeItems &items, - const ArrangeItems &instances_to_add, +void find_new_position(ArrangeableRefs &items, + const ArrangeableRefs &instances_to_add, coord_t min_obj_distance, BedShapeHint bedhint); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index bed3b754b3..bf892fe681 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3329,36 +3329,24 @@ void GLCanvas3D::update_ui_from_settings() -arr::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const +GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const { - arr::WipeTowerInfo wti; + WipeTowerInfo wti; + for (const GLVolume* vol : m_volumes.volumes) { if (vol->is_wipe_tower) { - wti.is_wipe_tower = true; - wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"), + wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"), m_config->opt_float("wipe_tower_y")); - wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); + wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); const BoundingBoxf3& bb = vol->bounding_box; - wti.bb_size = Vec2d(bb.size()(0), bb.size()(1)); + wti.m_bb_size = Vec2d(bb.size().x(), bb.size().y()); break; } } + return wti; } - -void GLCanvas3D::arrange_wipe_tower(const arr::WipeTowerInfo& wti) const -{ - if (wti.is_wipe_tower) { - DynamicPrintConfig cfg; - cfg.opt("wipe_tower_x", true)->value = wti.pos(0); - cfg.opt("wipe_tower_y", true)->value = wti.pos(1); - cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * wti.rotation; - wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); - } -} - - Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) { float z0 = 0.0f; @@ -5751,5 +5739,16 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } +void GLCanvas3D::WipeTowerInfo::set_arrange_result(Vec2d offset, double rotation_rads) +{ + m_pos += offset; + m_rotation += rotation_rads; + DynamicPrintConfig cfg; + cfg.opt("wipe_tower_x", true)->value = m_pos(X); + cfg.opt("wipe_tower_y", true)->value = m_pos(Y); + cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation; + wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 5a42879039..1872d2f376 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -611,9 +611,38 @@ public: int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } + + class WipeTowerInfo: public arr::Arrangeable { + Vec2d m_pos = {std::nan(""), std::nan("")}; + Vec2d m_bb_size; + double m_rotation; + friend class GLCanvas3D; + public: + + inline operator bool() const + { + return std::isnan(m_pos.x()) || std::isnan(m_pos.y()); + } - arr::WipeTowerInfo get_wipe_tower_info() const; - void arrange_wipe_tower(const arr::WipeTowerInfo& wti) const; + virtual void set_arrange_result(Vec2d offset, double rotation_rads) final; + + virtual Polygon get_arrange_polygon() const final + { + 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)}, + }); + + p.rotate(m_rotation); + p.translate(scaled(m_pos)); + return p; + } + }; + + WipeTowerInfo get_wipe_tower_info() const; // Returns the view ray line, in world coordinate, at the given mouse position. Linef3 mouse_ray(const Point& mouse_pos); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b4cfa33687..b4afdfe679 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1424,22 +1424,25 @@ struct Plater::priv priv * m_plater; class ArrangeJob : public Job - { - int count = 0; - + { + int m_count = 0; + GLCanvas3D::WipeTowerInfo m_wti; protected: + void prepare() override { - count = 0; + m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info(); + m_count = bool(m_wti); + for (auto obj : plater().model.objects) - count += int(obj->instances.size()); + m_count += int(obj->instances.size()); } public: //using Job::Job; ArrangeJob(priv * pltr): Job(pltr) {} - int status_range() const override { return count; } - void set_count(int c) { count = c; } + int status_range() const override { return m_count; } + void set_count(int c) { m_count = c; } void process() override; } arrange_job/*{m_plater}*/; @@ -1525,6 +1528,7 @@ struct Plater::priv std::string get_config(const std::string &key) const; BoundingBoxf bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const; + arr::BedShapeHint get_bed_shape_hint() const; std::vector load_files(const std::vector& input_files, bool load_model, bool load_config); std::vector load_model_objects(const ModelObjectPtrs &model_objects); wxString get_export_file(GUI::FileType file_type); @@ -2171,9 +2175,9 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode auto& bedpoints = bed_shape_opt->values; Polyline bed; bed.points.reserve(bedpoints.size()); for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - - arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); - + + std::pair wti = view3D->get_canvas3d()->get_wipe_tower_info(); + arr::find_new_position(model, new_instances, min_obj_distance, bed, wti); // it remains to move the wipe tower: @@ -2400,61 +2404,60 @@ void Plater::priv::sla_optimize_rotation() { m_ui_jobs.start(Jobs::Rotoptimize); } -void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { - static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; +arr::BedShapeHint Plater::priv::get_bed_shape_hint() const { + arr::BedShapeHint bedshape; - class ArrItemModelInstance: public arr::ArrangeItem { - ModelInstance *m_inst = nullptr; - public: - - ArrItemModelInstance() = default; - ArrItemModelInstance(ModelInstance *inst) : m_inst(inst) {} - - virtual void transform(Vec2d offs, double rot_rads) override { - assert(m_inst); - - // write the transformation data into the model instance - m_inst->set_rotation(Z, rot_rads); - m_inst->set_offset(offs); - } - - virtual Polygon silhouette() const override { - assert(m_inst); - - Vec3d rotation = m_inst->get_rotation(); - rotation.z() = 0.; - Transform3d trafo_instance = Geometry::assemble_transform( - Vec3d::Zero(), - rotation, - m_inst->get_scaling_factor(), - m_inst->get_mirror()); - - Polygon p = m_inst->get_object()->convex_hull_2d(trafo_instance); + const auto *bed_shape_opt = config->opt("bed_shape"); + assert(bed_shape_opt); + + if (bed_shape_opt) { + auto &bedpoints = bed_shape_opt->values; + Polyline bedpoly; bedpoly.points.reserve(bedpoints.size()); + for (auto &v : bedpoints) bedpoly.append(scaled(v)); + bedshape = arr::bedShape(bedpoly); + } + + return bedshape; +} - assert(!p.points.empty()); +void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { + static const auto arrangestr = _(L("Arranging")); + + arr::ArrangeableRefs arrangeinput; arrangeinput.reserve(m_count); + for(ModelObject *mo : plater().model.objects) + for(ModelInstance *minst : mo->instances) + arrangeinput.emplace_back(std::ref(*minst)); + + // 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_obj_distance = scaled(dist); + + arr::BedShapeHint bedshape = plater().get_bed_shape_hint(); + + try { + arr::arrange(arrangeinput, + min_obj_distance, + bedshape, + [this](unsigned st) { + if (st > 0) + update_status(m_count - int(st), arrangestr); + }, + [this]() { return was_canceled(); }); + } catch (std::exception & /*e*/) { + GUI::show_error(plater().q, + _(L("Could not arrange model objects! " + "Some geometries may be invalid."))); + } + + update_status(m_count, + was_canceled() ? _(L("Arranging canceled.")) + : _(L("Arranging done."))); - // this may happen for malformed models, see: - // https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (p.points.empty()) return {}; - - Polygons pp { p }; - pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); - if (!pp.empty()) p = pp.front(); - - return p; - } - }; - - // Count all the items on the bin (all the object's instances) - auto count = std::accumulate(plater().model.objects.begin(), - plater().model.objects.end(), - size_t(0), [](size_t s, ModelObject* o) - { - return s + o->instances.size(); - }); - -// std::vector items(size_t); - // TODO: we should decide whether to allow arrange when the search is // running we should probably disable explicit slicing and background // processing From 548f19462a9b55fd573a1e529a6be2fe0343b169 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 28 Jun 2019 15:42:59 +0200 Subject: [PATCH 230/627] Fix formatting --- src/libslic3r/MTUtils.hpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index f83d38a423..42992223fd 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -244,21 +244,21 @@ template bool all_of(const C &container) }); } -template -struct remove_cvref +template struct remove_cvref { using type = typename std::remove_cv::type>::type; }; -template -using remove_cvref_t = typename remove_cvref::type; +template using remove_cvref_t = typename remove_cvref::type; template class C, class T> -class Container: public C> { +class Container : public C> +{ public: - explicit Container(size_t count, T&& initval): - C>(count, initval) {} + explicit Container(size_t count, T &&initval) + : C>(count, initval) + {} }; template using DefaultContainer = std::vector; @@ -268,13 +268,13 @@ template class C = DefaultContainer> inline C> linspace(const T &start, const T &stop, const I &n) { Container vals(n, T()); - T stride = (stop - start) / n; - - size_t i = 0; + + T stride = (stop - start) / n; + size_t i = 0; std::generate(vals.begin(), vals.end(), [&i, start, stride] { - return start + i++ * stride; + return start + i++ * stride; }); - + return vals; } From 299e4f74c7af3762be54609b8818c14ca81d29a7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 28 Jun 2019 17:03:50 +0200 Subject: [PATCH 231/627] Arranging with new structure. --- src/libnest2d/include/libnest2d.h | 2 - src/libnest2d/include/libnest2d/libnest2d.hpp | 158 ++----- src/libnest2d/tests/test.cpp | 4 +- src/libslic3r/MTUtils.hpp | 2 +- src/libslic3r/Model.cpp | 6 +- src/libslic3r/Model.hpp | 18 +- src/libslic3r/ModelArrange.cpp | 425 +++++------------- src/libslic3r/ModelArrange.hpp | 21 +- src/slic3r/GUI/GLCanvas3D.cpp | 6 +- src/slic3r/GUI/GLCanvas3D.hpp | 12 +- src/slic3r/GUI/Plater.cpp | 14 +- 11 files changed, 189 insertions(+), 479 deletions(-) diff --git a/src/libnest2d/include/libnest2d.h b/src/libnest2d/include/libnest2d.h index a6eb36a4b0..f1d2506f48 100644 --- a/src/libnest2d/include/libnest2d.h +++ b/src/libnest2d/include/libnest2d.h @@ -30,9 +30,7 @@ using Circle = _Circle; using Item = _Item; using Rectangle = _Rectangle; - using PackGroup = _PackGroup; -using IndexedPackGroup = _IndexedPackGroup; using FillerSelection = selections::_FillerSelection; using FirstFitSelection = selections::_FirstFitSelection; diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 5d74aa3d9d..b8a542752a 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -65,6 +65,8 @@ class _Item { Box bb; bool valid; BBCache(): valid(false) {} } bb_cache_; + + std::function applyfn_; public: @@ -121,8 +123,26 @@ public: inline _Item(TContour&& contour, THolesContainer&& holes): - sh_(sl::create(std::move(contour), - std::move(holes))) {} + sh_(sl::create(std::move(contour), std::move(holes))) {} + + template + _Item(std::function applyfn, Args &&... args): + _Item(std::forward(args)...) + { + applyfn_ = std::move(applyfn); + } + + // Call the apply callback set in constructor. Within the callback, the + // original caller can apply the stored transformation to the original + // objects inteded for nesting. It might not be the shape handed over + // to _Item (e.g. arranging 3D shapes based on 2D silhouette or the + // client uses a simplified or processed polygon for nesting) + // This callback, if present, will be called for each item after the nesting + // is finished. + inline void callApplyFunction(unsigned binidx) const + { + if (applyfn_) applyfn_(*this, binidx); + } /** * @brief Convert the polygon to string representation. The format depends @@ -492,24 +512,6 @@ template using _ItemGroup = std::vector<_ItemRef>; template using _PackGroup = std::vector>>; -/** - * \brief A list of packed (index, item) pair vectors. Each vector represents a - * bin. - * - * The index is points to the position of the item in the original input - * sequence. This way the caller can use the items as a transformation data - * carrier and transform the original objects manually. - */ -template -using _IndexedPackGroup = std::vector< - std::vector< - std::pair< - unsigned, - _ItemRef - > - > - >; - template struct ConstItemRange { Iterator from; @@ -768,13 +770,9 @@ public: using BinType = typename TPlacer::BinType; using PlacementConfig = typename TPlacer::Config; using SelectionConfig = typename TSel::Config; - using Unit = TCoord>; - - using IndexedPackGroup = _IndexedPackGroup; using PackGroup = _PackGroup; using ResultType = PackGroup; - using ResultTypeIndexed = IndexedPackGroup; private: BinType bin_; @@ -786,6 +784,7 @@ private: using TSItem = remove_cvref_t; std::vector item_cache_; + StopCondition stopfn_; public: @@ -814,11 +813,13 @@ public: void configure(const PlacementConfig& pconf) { pconfig_ = pconf; } void configure(const SelectionConfig& sconf) { selector_.configure(sconf); } - void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) { + void configure(const PlacementConfig& pconf, const SelectionConfig& sconf) + { pconfig_ = pconf; selector_.configure(sconf); } - void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) { + void configure(const SelectionConfig& sconf, const PlacementConfig& pconf) + { pconfig_ = pconf; selector_.configure(sconf); } @@ -836,26 +837,6 @@ public: return _execute(from, to); } - /** - * A version of the arrange method returning an IndexedPackGroup with - * the item indexes into the original input sequence. - * - * Takes a little longer to collect the indices. Scales linearly with the - * input sequence size. - */ - template - inline IndexedPackGroup executeIndexed(TIterator from, TIterator to) - { - return _executeIndexed(from, to); - } - - /// Shorthand to normal arrange method. - template - inline PackGroup operator() (TIterator from, TIterator to) - { - return _execute(from, to); - } - /// Set a progress indicator function object for the selector. inline Nester& progressIndicator(ProgressFunction func) { @@ -865,7 +846,7 @@ public: /// Set a predicate to tell when to abort nesting. inline Nester& stopCondition(StopCondition fn) { - selector_.stopCondition(fn); return *this; + stopfn_ = fn; selector_.stopCondition(fn); return *this; } inline const PackGroup& lastResult() const @@ -878,16 +859,6 @@ public: selector_.preload(pgrp); } - inline void preload(const IndexedPackGroup& ipgrp) - { - PackGroup pgrp; pgrp.reserve(ipgrp.size()); - for(auto& ig : ipgrp) { - pgrp.emplace_back(); pgrp.back().reserve(ig.size()); - for(auto& r : ig) pgrp.back().emplace_back(r.second); - } - preload(pgrp); - } - private: template, - - // This function will be used only if the iterators are pointing to - // a type compatible with the libnest2d::_Item template. - // This way we can use references to input elements as they will - // have to exist for the lifetime of this call. - class T = enable_if_t< std::is_convertible::value, IT> - > - inline IndexedPackGroup _executeIndexed(TIterator from, - TIterator to, - bool = false) - { - __execute(from, to); - return createIndexedPackGroup(from, to, selector_); - } - - template, - class T = enable_if_t::value, IT> - > - inline IndexedPackGroup _executeIndexed(TIterator from, - TIterator to, - int = false) - { - item_cache_ = {from, to}; - __execute(item_cache_.begin(), item_cache_.end()); - return createIndexedPackGroup(from, to, selector_); - } - - template - static IndexedPackGroup createIndexedPackGroup(TIterator from, - TIterator to, - TSel& selector) - { - IndexedPackGroup pg; - pg.reserve(selector.getResult().size()); - - const PackGroup& pckgrp = selector.getResult(); - - for(size_t i = 0; i < pckgrp.size(); i++) { - auto items = pckgrp[i]; - pg.emplace_back(); - pg[i].reserve(items.size()); - - for(Item& itemA : items) { - auto it = from; - unsigned idx = 0; - while(it != to) { - Item& itemB = *it; - if(&itemB == &itemA) break; - it++; idx++; - } - pg[i].emplace_back(idx, itemA); - } - } - - return pg; - } - template inline void __execute(TIter from, TIter to) { if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { @@ -985,10 +896,19 @@ private: selector_.template packItems( from, to, bin_, pconfig_); - - if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { + + if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { item.removeOffset(); }); + + if(stopfn_ && !stopfn_()) { // Ignore results if nesting was stopped. + const PackGroup& bins = lastResult(); + unsigned binidx = 0; + for(auto& bin : bins) { + for(const Item& itm : bin) itm.callApplyFunction(binidx); + ++binidx; + } + } } }; diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 2f2b9beb59..72a600dbbf 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -366,7 +366,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) Nester arrange(Box(210, 250)); - auto groups = arrange(rects.begin(), rects.end()); + auto groups = arrange.execute(rects.begin(), rects.end()); ASSERT_EQ(groups.size(), 1u); ASSERT_EQ(groups[0].size(), rects.size()); @@ -420,7 +420,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) Nester arrange(Box(210, 250), min_obj_distance); - auto groups = arrange(rects.begin(), rects.end()); + auto groups = arrange.execute(rects.begin(), rects.end()); ASSERT_EQ(groups.size(), 1u); ASSERT_EQ(groups[0].size(), rects.size()); diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index e7f7ed3e67..01f0095bf4 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -303,7 +303,7 @@ inline SLIC3R_CONSTEXPR ScaledCoordOnly scaled(const Tin &v) SLIC3R_NOEXCE template> inline EigenVec, N> scaled(const EigenVec &v) { - return v.template cast() /*/ SCALING_FACTOR*/; + return (v / SCALING_FACTOR).template cast(); } // Conversion from arithmetic scaled type to floating point unscaled diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 6b88987df0..18f3f2f5ef 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1801,7 +1801,7 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } -Polygon ModelInstance::get_arrange_polygon() const +std::tuple ModelInstance::get_arrange_polygon() const { static const double SIMPLIFY_TOLERANCE_MM = 0.1; @@ -1827,7 +1827,9 @@ Polygon ModelInstance::get_arrange_polygon() const pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); if (!pp.empty()) p = pp.front(); - return p; + return std::make_tuple(p, Vec2crd{scaled(get_offset(X)), + scaled(get_offset(Y))}, + get_rotation(Z)); } // Test whether the two models contain the same number of ModelObjects with the same set of IDs diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 85a94b0fdf..c022808271 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -554,15 +554,21 @@ public: bool is_printable() const { return print_volume_state == PVS_Inside; } - virtual void set_arrange_result(Vec2d offs, double rot_rads) final + // ///////////////////////////////////////////////////////////////////////// + // Implement arr::Arrangeable interface + // ///////////////////////////////////////////////////////////////////////// + + // Getting the input polygon for arrange + virtual std::tuple get_arrange_polygon() const final; + + // Apply the arrange result on the ModelInstance + virtual void apply_arrange_result(Vec2d offs, double rot_rads) final { // write the transformation data into the model instance - set_rotation(Z, get_rotation(Z) + rot_rads); - set_offset(X, get_offset(X) + offs(X)); - set_offset(Y, get_offset(Y) + offs(Y)); + set_rotation(Z, rot_rads); + set_offset(X, offs(X)); + set_offset(Y, offs(Y)); } - - virtual Polygon get_arrange_polygon() const final; protected: friend class Print; diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 1fe7552b04..8859796489 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -4,7 +4,10 @@ #include "SVG.hpp" #include "MTUtils.hpp" -#include +#include +#include +#include +#include #include #include @@ -18,14 +21,20 @@ namespace libnest2d { using LargeInt = __int128; #else using LargeInt = boost::multiprecision::int128_t; -template<> struct _NumTag { using Type = ScalarTag; }; +template<> struct _NumTag +{ + using Type = ScalarTag; +}; #endif -template struct _NumTag> { using Type = RationalTag; }; + +template struct _NumTag> +{ + using Type = RationalTag; +}; namespace nfp { -template -struct NfpImpl +template struct NfpImpl { NfpResult operator()(const S &sh, const S &other) { @@ -33,16 +42,22 @@ struct NfpImpl } }; -} -} +} // namespace nfp +} // namespace libnest2d namespace Slic3r { namespace arr { using namespace libnest2d; +namespace clppr = ClipperLib; -using Shape = ClipperLib::Polygon; +using Item = _Item; +using Box = _Box; +using Circle = _Circle; +using Segment = _Segment; +using MultiPolygon = TMultiShape; +using PackGroup = _PackGroup; // Only for debugging. Prints the model object vertices on stdout. //std::string toString(const Model& model, bool holes = true) { @@ -131,7 +146,7 @@ namespace bgi = boost::geometry::index; using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; -using ItemGroup = std::vector>>; +using ItemGroup = std::vector>; const double BIG_ITEM_TRESHOLD = 0.02; @@ -156,10 +171,10 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) { // at the same time, it has to provide reasonable results. std::tuple objfunc(const PointImpl& bincenter, - const TMultiShape& merged_pile, + const MultiPolygon& merged_pile, const Box& pilebb, const ItemGroup& items, - const _Item &item, + const Item &item, double bin_area, double norm, // A norming factor for physical dimensions // a spatial index to quickly get neighbors of the candidate item @@ -224,8 +239,8 @@ objfunc(const PointImpl& bincenter, auto mp = merged_pile; mp.emplace_back(item.transformedShape()); auto chull = sl::convexHull(mp); - - placers::EdgeCache ec(chull); + + placers::EdgeCache ec(chull); double circ = ec.circumference() / norm; double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; @@ -256,7 +271,7 @@ objfunc(const PointImpl& bincenter, for(auto& e : result) { // now get the score for the best alignment auto idx = e.second; - _Item& p = items[idx]; + Item& p = items[idx]; auto parea = p.area(); if(std::abs(1.0 - parea/item.area()) < 1e-6) { auto bb = boundingBox(p.boundingBox(), ibb); @@ -322,12 +337,11 @@ class _ArrBase { public: // Useful type shortcuts... - using Placer = typename placers::_NofitPolyPlacer; - using Selector = selections::_FirstFitSelection; + using Placer = typename placers::_NofitPolyPlacer; + using Selector = selections::_FirstFitSelection; using Packer = Nester; using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; - using Pile = TMultiShape; protected: @@ -337,7 +351,7 @@ protected: SpatIndex m_rtree; // spatial index for the normal (bigger) objects SpatIndex m_smallsrtree; // spatial index for only the smaller items double m_norm; // A coefficient to scale distances - Pile m_merged_pile; // The already merged pile (vector of items) + MultiPolygon m_merged_pile; // The already merged pile (vector of items) Box m_pilebb; // The bounding box of the merged pile. ItemGroup m_remaining; // Remaining items (m_items at the beginning) ItemGroup m_items; // The items to be packed @@ -354,7 +368,7 @@ public: // Set up a callback that is called just before arranging starts // This functionality is provided by the Nester class (m_pack). m_pconf.before_packing = - [this](const Pile& merged_pile, // merged pile + [this](const MultiPolygon& merged_pile, // merged pile const ItemGroup& items, // packed items const ItemGroup& remaining) // future items to be packed { @@ -373,7 +387,7 @@ public: }; for(unsigned idx = 0; idx < items.size(); ++idx) { - _Item& itm = items[idx]; + Item& itm = items[idx]; if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx}); m_smallsrtree.insert({itm.boundingBox(), idx}); } @@ -383,12 +397,12 @@ public: m_pck.stopCondition(stopcond); } - template inline _PackGroup operator()(Args&&...args) { + template inline PackGroup operator()(Args&&...args) { m_rtree.clear(); return m_pck.execute(std::forward(args)...); } - inline void preload(const _PackGroup& pg) { + inline void preload(const PackGroup& pg) { m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; m_pconf.object_function = nullptr; // drop the special objectfunction m_pck.preload(pg); @@ -396,14 +410,14 @@ public: // Build the rtree for queries to work for(const ItemGroup& grp : pg) for(unsigned idx = 0; idx < grp.size(); ++idx) { - _Item& itm = grp[idx]; + Item& itm = grp[idx]; m_rtree.insert({itm.boundingBox(), idx}); } m_pck.configure(m_pconf); } - bool is_colliding(const _Item& item) { + bool is_colliding(const Item& item) { if(m_rtree.empty()) return false; std::vector result; m_rtree.query(bgi::intersects(item.boundingBox()), @@ -425,7 +439,7 @@ public: // Here we set up the actual object function that calls the common // object function for all bin shapes than does an additional inside // check for the arranged pile. - m_pconf.object_function = [this, bin] (const _Item &item) { + m_pconf.object_function = [this, bin] (const Item &item) { auto result = objfunc(bin.center(), m_merged_pile, @@ -452,23 +466,21 @@ public: } }; -using lnCircle = libnest2d::_Circle; - -inline lnCircle to_lnCircle(const Circle& circ) { - return lnCircle({circ.center()(0), circ.center()(1)}, circ.radius()); +inline Circle to_lnCircle(const CircleBed& circ) { + return Circle({circ.center()(0), circ.center()(1)}, circ.radius()); } // Arranger specialization for circle shaped bin. -template<> class AutoArranger: public _ArrBase { +template<> class AutoArranger: public _ArrBase { public: - AutoArranger(const lnCircle& bin, Distance dist, + AutoArranger(const Circle& bin, Distance dist, std::function progressind = [](unsigned){}, std::function stopcond = [](){return false;}): - _ArrBase(bin, dist, progressind, stopcond) { + _ArrBase(bin, dist, progressind, stopcond) { // As with the box, only the inside check is different. - m_pconf.object_function = [this, &bin] (const _Item &item) { + m_pconf.object_function = [this, &bin] (const Item &item) { auto result = objfunc(bin.center(), m_merged_pile, @@ -483,7 +495,7 @@ public: double score = std::get<0>(result); - auto isBig = [this](const _Item& itm) { + auto isBig = [this](const Item& itm) { return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; }; @@ -512,7 +524,7 @@ public: std::function stopcond = [](){return false;}): _ArrBase(bin, dist, progressind, stopcond) { - m_pconf.object_function = [this, &bin] (const _Item &item) { + m_pconf.object_function = [this, &bin] (const Item &item) { auto binbb = sl::boundingBox(bin); auto result = objfunc(binbb.center(), @@ -544,7 +556,7 @@ public: std::function stopcond): _ArrBase(Box(0, 0), dist, progressind, stopcond) { - this->m_pconf.object_function = [this] (const _Item &item) { + this->m_pconf.object_function = [this] (const Item &item) { auto result = objfunc({0, 0}, m_merged_pile, @@ -563,152 +575,12 @@ public: } }; -// A container which stores a pointer to the 3D object and its projected -// 2D shape from top view. -//using ShapeData2D = std::vector>; - -//ShapeData2D projectModelFromTop(const Slic3r::Model &model, -// const WipeTowerInfo &wti, -// double tolerance) -//{ -// ShapeData2D ret; - -// // Count all the items on the bin (all the object's instances) -// auto s = std::accumulate(model.objects.begin(), model.objects.end(), -// size_t(0), [](size_t s, ModelObject* o) -// { -// return s + o->instances.size(); -// }); - -// ret.reserve(s); - -// for(ModelObject* objptr : model.objects) { -// if (! objptr->instances.empty()) { - -// // TODO export the exact 2D projection. Cannot do it as libnest2d -// // does not support concave shapes (yet). -// ClipperLib::Path clpath; - -// // Object instances should carry the same scaling and -// // x, y rotation that is why we use the first instance. -// { -// ModelInstance *finst = objptr->instances.front(); -// Vec3d rotation = finst->get_rotation(); -// rotation.z() = 0.; -// Transform3d trafo_instance = Geometry::assemble_transform( -// Vec3d::Zero(), -// rotation, -// finst->get_scaling_factor(), -// finst->get_mirror()); -// Polygon p = objptr->convex_hull_2d(trafo_instance); - -// assert(!p.points.empty()); - -// // this may happen for malformed models, see: -// // https://github.com/prusa3d/PrusaSlicer/issues/2209 -// if (p.points.empty()) continue; - -// if(tolerance > EPSILON) { -// Polygons pp { p }; -// pp = p.simplify(scaled(tolerance)); -// if (!pp.empty()) p = pp.front(); -// } - -// p.reverse(); -// assert(!p.is_counter_clockwise()); -// clpath = Slic3rMultiPoint_to_ClipperPath(p); -// auto firstp = clpath.front(); clpath.emplace_back(firstp); -// } - -// Vec3d rotation0 = objptr->instances.front()->get_rotation(); -// rotation0(2) = 0.; -// for(ModelInstance* objinst : objptr->instances) { -// ClipperLib::Polygon pn; -// pn.Contour = clpath; - -// // Efficient conversion to item. -// Item item(std::move(pn)); - -// // Invalid geometries would throw exceptions when arranging -// if(item.vertexCount() > 3) { -// item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation())); -// item.translation({ -// scaled(objinst->get_offset(X)), -// scaled(objinst->get_offset(Y)) -// }); -// ret.emplace_back(objinst, item); -// } -// } -// } -// } - -// // The wipe tower is a separate case (in case there is one), let's duplicate the code -// if (wti.is_wipe_tower) { -// Points pts; -// pts.emplace_back(coord_t(scale_(0.)), coord_t(scale_(0.))); -// pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(0.))); -// pts.emplace_back(coord_t(scale_(wti.bb_size(0))), coord_t(scale_(wti.bb_size(1)))); -// pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(wti.bb_size(1)))); -// pts.emplace_back(coord_t(scale_(-0.)), coord_t(scale_(0.))); -// Polygon p(std::move(pts)); -// ClipperLib::Path clpath = Slic3rMultiPoint_to_ClipperPath(p); -// ClipperLib::Polygon pn; -// pn.Contour = clpath; -// // Efficient conversion to item. -// Item item(std::move(pn)); -// item.rotation(wti.rotation), -// item.translation({ -// scaled(wti.pos(0)), -// scaled(wti.pos(1)) -// }); -// ret.emplace_back(nullptr, item); -// } - -// return ret; -//} - -// Apply the calculated translations and rotations (currently disabled) to -// the Model object instances. -//void applyResult(IndexedPackGroup::value_type &group, -// ClipperLib::cInt batch_offset, -// ShapeData2D & shapemap, -// WipeTowerInfo & wti) -//{ -// for(auto& r : group) { -// auto idx = r.first; // get the original item index -// Item& item = r.second; // get the item itself - -// // Get the model instance from the shapemap using the index -// ModelInstance *inst_ptr = shapemap[idx].first; - -// // Get the transformation data from the item object and scale it -// // appropriately -// auto off = item.translation(); -// Radians rot = item.rotation(); - -// Vec3d foff(unscaled(off.X + batch_offset), -// unscaled(off.Y), -// inst_ptr ? inst_ptr->get_offset()(Z) : 0.); - -// if (inst_ptr) { -// // write the transformation data into the model instance -// inst_ptr->set_rotation(Z, rot); -// inst_ptr->set_offset(foff); -// } -// else { // this is the wipe tower - we will modify the struct with the info -// // and leave it up to the called to actually move the wipe tower -// wti.pos = Vec2d(foff(0), foff(1)); -// wti.rotation = rot; -// } -// } -//} - // Get the type of bed geometry from a simple vector of points. BedShapeHint bedShape(const Polyline &bed) { BedShapeHint ret; - auto x = [](const Point& p) { return p(0); }; - auto y = [](const Point& p) { return p(1); }; + auto x = [](const Point& p) { return p(X); }; + auto y = [](const Point& p) { return p(Y); }; auto width = [x](const BoundingBox& box) { return x(box.max) - x(box.min); @@ -721,7 +593,7 @@ BedShapeHint bedShape(const Polyline &bed) { auto area = [&width, &height](const BoundingBox& box) { double w = width(box); double h = height(box); - return w*h; + return w * h; }; auto poly_area = [](Polyline p) { @@ -752,11 +624,11 @@ BedShapeHint bedShape(const Polyline &bed) { avg_dist /= vertex_distances.size(); - Circle ret(center, avg_dist); + CircleBed ret(center, avg_dist); for(auto el : vertex_distances) { if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { - ret = Circle(); + ret = CircleBed(); break; } } @@ -785,14 +657,14 @@ BedShapeHint bedShape(const Polyline &bed) { //static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; template -_PackGroup _arrange(std::vector &shapes, - const BinT & bin, - coord_t minobjd, - std::function prind, - std::function stopfn) +PackGroup _arrange(std::vector & items, + const BinT & bin, + coord_t minobjd, + std::function prind, + std::function stopfn) { AutoArranger arranger{bin, minobjd, prind, stopfn}; - return arranger(shapes.begin(), shapes.end()); + return arranger(items.begin(), items.end()); } //template @@ -850,185 +722,94 @@ inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) return w + w / 5; } -bool arrange(ArrangeableRefs & arrangables, +//// The final client function to arrange the Model. A progress indicator and +//// a stop predicate can be also be passed to control the process. +bool arrange(Arrangeables & arrangables, coord_t min_obj_distance, BedShapeHint bedhint, std::function progressind, std::function stopcondition) { bool ret = true; + namespace clppr = ClipperLib; - std::vector shapes; - shapes.reserve(arrangables.size()); - size_t id = 0; - for (Arrangeable &iref : arrangables) { - Polygon p = iref.get_arrange_polygon(); + std::vector items; + items.reserve(arrangables.size()); + coord_t binwidth = 0; + + for (Arrangeable *arrangeable : arrangables) { + assert(arrangeable); - p.reverse(); - assert(!p.is_counter_clockwise()); + auto arrangeitem = arrangeable->get_arrange_polygon(); - Shape clpath(/*id++,*/ Slic3rMultiPoint_to_ClipperPath(p)); + Polygon& p = std::get<0>(arrangeitem); + const Vec2crd& offs = std::get<1>(arrangeitem); + double rotation = std::get<2>(arrangeitem); - auto firstp = clpath.Contour.front(); clpath.Contour.emplace_back(firstp); - shapes.emplace_back(std::move(clpath)); + if (p.is_counter_clockwise()) p.reverse(); + + clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); + + auto firstp = clpath.Contour.front(); + clpath.Contour.emplace_back(firstp); + + items.emplace_back( + // callback called by arrange to apply the result on the arrangeable + [arrangeable, &binwidth](const Item &itm, unsigned binidx) { + clppr::cInt stride = binidx * stride_padding(binwidth); + + clppr::IntPoint offs = itm.translation(); + arrangeable->apply_arrange_result({unscaled(offs.X + stride), + unscaled(offs.Y)}, + itm.rotation()); + }, + std::move(clpath)); + items.front().rotation(rotation); + items.front().translation({offs.x(), offs.y()}); } - _PackGroup result; - - auto& cfn = stopcondition; - // Integer ceiling the min distance from the bed perimeters coord_t md = min_obj_distance - SCALED_EPSILON; md = (md % 2) ? md / 2 + 1 : md / 2; - coord_t binwidth = 0; switch (bedhint.type) { case BedShapeType::BOX: { // Create the arranger for the box shaped bed BoundingBox bbb = bedhint.shape.box; - - auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, - ClipperLib::cInt{bbb.min(1)} - md}, - {ClipperLib::cInt{bbb.max(0)} + md, - ClipperLib::cInt{bbb.max(1)} + md}); - - result = _arrange(shapes, binbb, min_obj_distance, progressind, cfn); + bbb.min -= Point{md, md}, bbb.max += Point{md, md}; + + Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; binwidth = coord_t(binbb.width()); + + _arrange(items, binbb, min_obj_distance, progressind, stopcondition); break; } case BedShapeType::CIRCLE: { auto c = bedhint.shape.circ; auto cc = to_lnCircle(c); - result = _arrange(shapes, cc, min_obj_distance, progressind, cfn); binwidth = scaled(c.radius()); + _arrange(items, cc, min_obj_distance, progressind, stopcondition); break; } case BedShapeType::IRREGULAR: { auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon); - ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); - result = _arrange(shapes, irrbed, min_obj_distance, progressind, cfn); + auto irrbed = sl::create(std::move(ctour)); BoundingBox polybb(bedhint.shape.polygon); binwidth = (polybb.max(X) - polybb.min(X)); + _arrange(items, irrbed, min_obj_distance, progressind, stopcondition); break; } case BedShapeType::WHO_KNOWS: { - result = _arrange(shapes, false, min_obj_distance, progressind, cfn); + _arrange(items, false, min_obj_distance, progressind, stopcondition); break; } }; - if(result.empty() || stopcondition()) return false; - - ClipperLib::cInt stride = stride_padding(binwidth); - ClipperLib::cInt batch_offset = 0; - - for (const auto &group : result) { - for (_Item &itm : group) { - ClipperLib::IntPoint offs = itm.translation(); -// arrangables[itm.id()].get().set_arrange_result({offs.X, offs.Y}, -// itm.rotation()); - } - - // Only the first pack group can be placed onto the print bed. The - // other objects which could not fit will be placed next to the - // print bed - batch_offset += stride; - } + if(stopcondition()) return false; return ret; } -//// The final client function to arrange the Model. A progress indicator and -//// a stop predicate can be also be passed to control the process. -//bool arrange(Model &model, // The model with the geometries -// WipeTowerInfo& wti, // Wipe tower info -// coord_t min_obj_distance, // Has to be in scaled (clipper) measure -// const Polyline &bed, // The bed geometry. -// BedShapeHint bedhint, // Hint about the bed geometry type. -// bool first_bin_only, // What to do is not all items fit. - -// // Controlling callbacks. -// std::function progressind, -// std::function stopcondition) -//{ -// bool ret = true; - -// // Get the 2D projected shapes with their 3D model instance pointers -// auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); - -// // Copy the references for the shapes only as the arranger expects a -// // sequence of objects convertible to Item or ClipperPolygon -// std::vector> shapes; -// shapes.reserve(shapemap.size()); -// std::for_each(shapemap.begin(), shapemap.end(), -// [&shapes] (ShapeData2D::value_type& it) -// { -// shapes.push_back(std::ref(it.second)); -// }); - -// IndexedPackGroup result; - -// // If there is no hint about the shape, we will try to guess -// if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); - -// BoundingBox bbb(bed); - -// auto& cfn = stopcondition; - -// // Integer ceiling the min distance from the bed perimeters -// coord_t md = min_obj_distance - SCALED_EPSILON; -// md = (md % 2) ? md / 2 + 1 : md / 2; - -// auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, -// ClipperLib::cInt{bbb.min(1)} - md}, -// {ClipperLib::cInt{bbb.max(0)} + md, -// ClipperLib::cInt{bbb.max(1)} + md}); - -// switch(bedhint.type) { -// case BedShapeType::BOX: { -// // Create the arranger for the box shaped bed -// result = _arrange(shapes, binbb, min_obj_distance, progressind, cfn); -// break; -// } -// case BedShapeType::CIRCLE: { -// auto c = bedhint.shape.circ; -// auto cc = to_lnCircle(c); -// result = _arrange(shapes, cc, min_obj_distance, progressind, cfn); -// break; -// } -// case BedShapeType::IRREGULAR: -// case BedShapeType::WHO_KNOWS: { -// auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); -// ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); -// result = _arrange(shapes, irrbed, min_obj_distance, progressind, cfn); -// break; -// } -// }; - -// if(result.empty() || stopcondition()) return false; - -// if(first_bin_only) { -// applyResult(result.front(), 0, shapemap, wti); -// } else { - -// ClipperLib::cInt stride = stride_padding(binbb.width()); -// ClipperLib::cInt batch_offset = 0; - -// for(auto& group : result) { -// applyResult(group, batch_offset, shapemap, wti); - -// // Only the first pack group can be placed onto the print bed. The -// // other objects which could not fit will be placed next to the -// // print bed -// batch_offset += stride; -// } -// } - -// for(auto objptr : model.objects) objptr->invalidate_bounding_box(); - -// return ret && result.size() == 1; -//} - //void find_new_position(const Model &model, // ModelInstancePtrs toadd, // coord_t min_obj_distance, diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index 77b357f4ea..45dde13d6b 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -11,13 +11,13 @@ class Model; namespace arr { -class Circle { +class CircleBed { Point center_; double radius_; public: - inline Circle(): center_(0, 0), radius_(std::nan("")) {} - inline Circle(const Point& c, double r): center_(c), radius_(r) {} + inline CircleBed(): center_(0, 0), radius_(std::nan("")) {} + inline CircleBed(const Point& c, double r): center_(c), radius_(r) {} inline double radius() const { return radius_; } inline const Point& center() const { return center_; } @@ -34,7 +34,7 @@ enum class BedShapeType { struct BedShapeHint { BedShapeType type = BedShapeType::WHO_KNOWS; /*union*/ struct { // I know but who cares... TODO: use variant from cpp17? - Circle circ; + CircleBed circ; BoundingBox box; Polyline polygon; } shape; @@ -47,12 +47,13 @@ public: virtual ~Arrangeable() = default; - virtual void set_arrange_result(Vec2d offset, double rotation_rads) = 0; + virtual void apply_arrange_result(Vec2d offset, double rotation_rads) = 0; - virtual Polygon get_arrange_polygon() const = 0; + /// Get the 2D silhouette to arrange and an initial offset and rotation + virtual std::tuple get_arrange_polygon() const = 0; }; -using ArrangeableRefs = std::vector>; +using Arrangeables = std::vector; /** * \brief Arranges the model objects on the screen. @@ -89,7 +90,7 @@ using ArrangeableRefs = std::vector>; // std::function progressind, // std::function stopcondition); -bool arrange(ArrangeableRefs &items, +bool arrange(Arrangeables &items, coord_t min_obj_distance, BedShapeHint bedhint, std::function progressind, @@ -102,8 +103,8 @@ bool arrange(ArrangeableRefs &items, // coord_t min_obj_distance, // const Slic3r::Polyline& bed, // WipeTowerInfo& wti); -void find_new_position(ArrangeableRefs &items, - const ArrangeableRefs &instances_to_add, +void find_new_position(Arrangeables &items, + const Arrangeables &instances_to_add, coord_t min_obj_distance, BedShapeHint bedhint); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index bf892fe681..4e30934891 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5739,10 +5739,10 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } -void GLCanvas3D::WipeTowerInfo::set_arrange_result(Vec2d offset, double rotation_rads) +void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2d offset, double rotation_rads) { - m_pos += offset; - m_rotation += rotation_rads; + m_pos = offset; + m_rotation = rotation_rads; DynamicPrintConfig cfg; cfg.opt("wipe_tower_x", true)->value = m_pos(X); cfg.opt("wipe_tower_y", true)->value = m_pos(Y); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 1872d2f376..2d3c3b27f9 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -613,7 +613,7 @@ public: int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } class WipeTowerInfo: public arr::Arrangeable { - Vec2d m_pos = {std::nan(""), std::nan("")}; + Vec2d m_pos = {std::nan(""), std::nan("")}; Vec2d m_bb_size; double m_rotation; friend class GLCanvas3D; @@ -621,12 +621,12 @@ public: inline operator bool() const { - return std::isnan(m_pos.x()) || std::isnan(m_pos.y()); + return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); } - virtual void set_arrange_result(Vec2d offset, double rotation_rads) final; + virtual void apply_arrange_result(Vec2d offset, double rotation_rads) final; - virtual Polygon get_arrange_polygon() const final + virtual std::tuple get_arrange_polygon() const final { Polygon p({ {coord_t(0), coord_t(0)}, @@ -636,9 +636,7 @@ public: {coord_t(0), coord_t(0)}, }); - p.rotate(m_rotation); - p.translate(scaled(m_pos)); - return p; + return std::make_tuple(p, scaled(m_pos), m_rotation); } }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b4afdfe679..193390f854 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2357,7 +2357,7 @@ void Plater::priv::remove(size_t obj_idx) void Plater::priv::delete_object_from_model(size_t obj_idx) -{ +{ model.delete_object(obj_idx); update(); object_list_changed(); @@ -2422,11 +2422,15 @@ arr::BedShapeHint Plater::priv::get_bed_shape_hint() const { void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { static const auto arrangestr = _(L("Arranging")); - - arr::ArrangeableRefs arrangeinput; arrangeinput.reserve(m_count); + + // Collect the model instances and place them into the input vector + arr::Arrangeables arrangeinput; arrangeinput.reserve(m_count); for(ModelObject *mo : plater().model.objects) for(ModelInstance *minst : mo->instances) - arrangeinput.emplace_back(std::ref(*minst)); + arrangeinput.emplace_back(minst); + + // Place back the wipe tower if that's available. + if (m_wti) arrangeinput.emplace_back(&m_wti); // 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. @@ -3456,7 +3460,7 @@ void Plater::priv::set_bed_shape(const Pointfs& shape) bool Plater::priv::can_delete() const { - return !get_selection().is_empty() && !get_selection().is_wipe_tower(); + return !get_selection().is_empty() && !get_selection().is_wipe_tower() && !m_ui_jobs.is_any_running(); } bool Plater::priv::can_delete_all() const From cb3a586debed37fdc338e4afdd6673a2bb377dfd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 28 Jun 2019 18:27:15 +0200 Subject: [PATCH 232/627] Adapt find_new_position is WIP. Cleaning up comments. --- src/libnest2d/include/libnest2d/libnest2d.hpp | 4 + .../include/libnest2d/selections/firstfit.hpp | 12 +- src/libslic3r/ModelArrange.cpp | 222 +++++++----------- src/libslic3r/ModelArrange.hpp | 88 ++++--- src/slic3r/GUI/Plater.cpp | 156 ++++-------- 5 files changed, 181 insertions(+), 301 deletions(-) diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index b8a542752a..4831c1fb67 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -67,6 +67,7 @@ class _Item { } bb_cache_; std::function applyfn_; + bool fixed_{false}; public: @@ -143,6 +144,9 @@ public: { if (applyfn_) applyfn_(*this, binidx); } + + inline bool isFixed() const noexcept { return fixed_; } + inline void markAsFixed(bool fixed = true) { fixed_ = fixed; } /** * @brief Convert the polygon to string representation. The format depends diff --git a/src/libnest2d/include/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp index d521673b44..287204c08a 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -39,6 +39,15 @@ public: std::vector placers; placers.reserve(last-first); + + std::for_each(first, last, [this](Item& itm) { + if(itm.isFixed()) { + if(packed_bins_.empty()) packed_bins_.emplace_back(); + packed_bins_.front().emplace_back(itm); + } else { + store_.emplace_back(itm); + } + }); // If the packed_items array is not empty we have to create as many // placers as there are elements in packed bins and preload each item @@ -49,8 +58,6 @@ public: placers.back().preload(ig); } - std::copy(first, last, std::back_inserter(store_)); - auto sortfunc = [](Item& i1, Item& i2) { return i1.area() > i2.area(); }; @@ -76,7 +83,6 @@ public: } } - auto it = store_.begin(); while(it != store_.end() && !cancelled()) { diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 8859796489..6d9c6007f8 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -1,5 +1,4 @@ #include "ModelArrange.hpp" -//#include "Model.hpp" #include "Geometry.hpp" #include "SVG.hpp" #include "MTUtils.hpp" @@ -656,34 +655,35 @@ BedShapeHint bedShape(const Polyline &bed) { //static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; +//template +//PackGroup _arrange(std::vector & items, +// const BinT & bin, +// coord_t minobjd, +// std::function prind, +// std::function stopfn) +//{ +// AutoArranger arranger{bin, minobjd, prind, stopfn}; +// return arranger(items.begin(), items.end()); +//} + template -PackGroup _arrange(std::vector & items, +PackGroup _arrange(std::vector & shapes, + const PackGroup & preshapes, const BinT & bin, coord_t minobjd, std::function prind, std::function stopfn) { - AutoArranger arranger{bin, minobjd, prind, stopfn}; - return arranger(items.begin(), items.end()); -} - -//template -//IndexedPackGroup _arrange(std::vector> &shapes, -// const PackGroup & preshapes, -// std::vector &minstances, -// const BinT & bin, -// coord_t minobjd) -//{ // auto binbb = sl::boundingBox(bin); -// AutoArranger arranger{bin, minobjd}; + AutoArranger arranger{bin, minobjd, prind, stopfn}; -// if(!preshapes.front().empty()) { // If there is something on the plate -// arranger.preload(preshapes); + if(!preshapes.front().empty()) { // If there is something on the plate + arranger.preload(preshapes); -// // Try to put the first item to the center, as the arranger will not -// // do this for us. + // Try to put the first item to the center, as the arranger will not + // do this for us. // auto shptrit = minstances.begin(); // for(auto shit = shapes.begin(); shit != shapes.end(); ++shit, ++shptrit) // { @@ -712,10 +712,10 @@ PackGroup _arrange(std::vector & items, // break; // } // } -// } + } -// return arranger(shapes.begin(), shapes.end()); -//} + return arranger(shapes.begin(), shapes.end()); +} inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) { @@ -725,53 +725,73 @@ inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) //// The final client function to arrange the Model. A progress indicator and //// a stop predicate can be also be passed to control the process. bool arrange(Arrangeables & arrangables, + const Arrangeables & excludes, coord_t min_obj_distance, - BedShapeHint bedhint, + const BedShapeHint & bedhint, std::function progressind, std::function stopcondition) { bool ret = true; namespace clppr = ClipperLib; - std::vector items; + std::vector items, excluded_items; items.reserve(arrangables.size()); coord_t binwidth = 0; - for (Arrangeable *arrangeable : arrangables) { - assert(arrangeable); - - auto arrangeitem = arrangeable->get_arrange_polygon(); - - Polygon& p = std::get<0>(arrangeitem); - const Vec2crd& offs = std::get<1>(arrangeitem); - double rotation = std::get<2>(arrangeitem); - - if (p.is_counter_clockwise()) p.reverse(); - - clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); - - auto firstp = clpath.Contour.front(); - clpath.Contour.emplace_back(firstp); + PackGroup preshapes{ {} }; // pack group with one initial bin for preloading - items.emplace_back( + auto process_arrangeable = + [](const Arrangeable * arrangeable, + std::vector & outp, + std::function applyfn) + { + assert(arrangeable); + + auto arrangeitem = arrangeable->get_arrange_polygon(); + + Polygon & p = std::get<0>(arrangeitem); + const Vec2crd &offs = std::get<1>(arrangeitem); + double rotation = std::get<2>(arrangeitem); + + if (p.is_counter_clockwise()) p.reverse(); + + clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); + + auto firstp = clpath.Contour.front(); + clpath.Contour.emplace_back(firstp); + + outp.emplace_back(applyfn, std::move(clpath)); + outp.front().rotation(rotation); + outp.front().translation({offs.x(), offs.y()}); + }; + + for (Arrangeable *arrangeable : arrangables) { + process_arrangeable( + arrangeable, + items, // callback called by arrange to apply the result on the arrangeable [arrangeable, &binwidth](const Item &itm, unsigned binidx) { clppr::cInt stride = binidx * stride_padding(binwidth); clppr::IntPoint offs = itm.translation(); - arrangeable->apply_arrange_result({unscaled(offs.X + stride), + arrangeable->apply_arrange_result({unscaled(offs.X + + stride), unscaled(offs.Y)}, itm.rotation()); - }, - std::move(clpath)); - items.front().rotation(rotation); - items.front().translation({offs.x(), offs.y()}); + }); } + for (const Arrangeable * fixed: excludes) + process_arrangeable(fixed, excluded_items, nullptr); + + for(Item& excl : excluded_items) preshapes.front().emplace_back(excl); + // Integer ceiling the min distance from the bed perimeters coord_t md = min_obj_distance - SCALED_EPSILON; md = (md % 2) ? md / 2 + 1 : md / 2; - + + auto& cfn = stopcondition; + switch (bedhint.type) { case BedShapeType::BOX: { // Create the arranger for the box shaped bed @@ -781,14 +801,14 @@ bool arrange(Arrangeables & arrangables, Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; binwidth = coord_t(binbb.width()); - _arrange(items, binbb, min_obj_distance, progressind, stopcondition); + _arrange(items, preshapes, binbb, min_obj_distance, progressind, cfn); break; } case BedShapeType::CIRCLE: { auto c = bedhint.shape.circ; auto cc = to_lnCircle(c); binwidth = scaled(c.radius()); - _arrange(items, cc, min_obj_distance, progressind, stopcondition); + _arrange(items, preshapes, cc, min_obj_distance, progressind, cfn); break; } case BedShapeType::IRREGULAR: { @@ -796,11 +816,11 @@ bool arrange(Arrangeables & arrangables, auto irrbed = sl::create(std::move(ctour)); BoundingBox polybb(bedhint.shape.polygon); binwidth = (polybb.max(X) - polybb.min(X)); - _arrange(items, irrbed, min_obj_distance, progressind, stopcondition); + _arrange(items, preshapes, irrbed, min_obj_distance, progressind, cfn); break; } case BedShapeType::WHO_KNOWS: { - _arrange(items, false, min_obj_distance, progressind, stopcondition); + _arrange(items, preshapes, false, min_obj_distance, progressind, cfn); break; } }; @@ -810,99 +830,15 @@ bool arrange(Arrangeables & arrangables, return ret; } -//void find_new_position(const Model &model, -// ModelInstancePtrs toadd, -// coord_t min_obj_distance, -// const Polyline &bed, -// WipeTowerInfo& wti) -//{ -// // Get the 2D projected shapes with their 3D model instance pointers -// auto shapemap = arr::projectModelFromTop(model, wti, SIMPLIFY_TOLERANCE_MM); - -// // Copy the references for the shapes only, as the arranger expects a -// // sequence of objects convertible to Item or ClipperPolygon -// PackGroup preshapes; preshapes.emplace_back(); -// ItemGroup shapes; -// preshapes.front().reserve(shapemap.size()); - -// std::vector shapes_ptr; shapes_ptr.reserve(toadd.size()); -// IndexedPackGroup result; - -// // If there is no hint about the shape, we will try to guess -// BedShapeHint bedhint = bedShape(bed); - -// BoundingBox bbb(bed); - -// // Integer ceiling the min distance from the bed perimeters -// coord_t md = min_obj_distance - SCALED_EPSILON; -// md = (md % 2) ? md / 2 + 1 : md / 2; - -// auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, -// ClipperLib::cInt{bbb.min(1)} - md}, -// {ClipperLib::cInt{bbb.max(0)} + md, -// ClipperLib::cInt{bbb.max(1)} + md}); - -// for(auto it = shapemap.begin(); it != shapemap.end(); ++it) { -// // `toadd` vector contains the instance pointers which have to be -// // considered by arrange. If `it` points to an ModelInstance, which -// // is NOT in `toadd`, add it to preshapes. -// if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end()) { -// if(it->second.isInside(binbb)) // just ignore items which are outside -// preshapes.front().emplace_back(std::ref(it->second)); -// } -// else { -// shapes_ptr.emplace_back(it->first); -// shapes.emplace_back(std::ref(it->second)); -// } -// } - -// switch(bedhint.type) { -// case BedShapeType::BOX: { -// // Create the arranger for the box shaped bed -// result = _arrange(shapes, preshapes, shapes_ptr, binbb, min_obj_distance); -// break; -// } -// case BedShapeType::CIRCLE: { -// auto c = bedhint.shape.circ; -// auto cc = to_lnCircle(c); -// result = _arrange(shapes, preshapes, shapes_ptr, cc, min_obj_distance); -// break; -// } -// case BedShapeType::IRREGULAR: -// case BedShapeType::WHO_KNOWS: { -// auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); -// ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); -// result = _arrange(shapes, preshapes, shapes_ptr, irrbed, min_obj_distance); -// break; -// } -// }; - -// // Now we go through the result which will contain the fixed and the moving -// // polygons as well. We will have to search for our item. - -// ClipperLib::cInt stride = stride_padding(binbb.width()); -// ClipperLib::cInt batch_offset = 0; - -// for(auto& group : result) { -// for(auto& r : group) if(r.first < shapes.size()) { -// Item& resultitem = r.second; -// unsigned idx = r.first; -// auto offset = resultitem.translation(); -// Radians rot = resultitem.rotation(); -// ModelInstance *minst = shapes_ptr[idx]; -// Vec3d foffset(unscaled(offset.X + batch_offset), -// unscaled(offset.Y), -// minst->get_offset()(Z)); - -// // write the transformation data into the model instance -// minst->set_rotation(Z, rot); -// minst->set_offset(foffset); -// } -// batch_offset += stride; -// } -//} - +/// Arrange, without the fixed items (excludes) +bool arrange(Arrangeables & inp, + coord_t min_d, + const BedShapeHint & bedhint, + std::function prfn, + std::function stopfn) +{ + return arrange(inp, {}, min_d, bedhint, prfn, stopfn); } - -} +} // namespace arr +} // namespace Slic3r diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index 45dde13d6b..306081eb8a 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -1,16 +1,14 @@ #ifndef MODELARRANGE_HPP #define MODELARRANGE_HPP -//#include "Model.hpp" #include "Polygon.hpp" #include "BoundingBox.hpp" namespace Slic3r { -class Model; - namespace arr { +/// A geometry abstraction for a circular print bed. Similarly to BoundingBox. class CircleBed { Point center_; double radius_; @@ -24,6 +22,7 @@ public: inline operator bool() { return !std::isnan(radius_); } }; +/// Types of print bed shapes. enum class BedShapeType { BOX, CIRCLE, @@ -31,6 +30,7 @@ enum class BedShapeType { WHO_KNOWS }; +/// Info about the print bed for the arrange() function. struct BedShapeHint { BedShapeType type = BedShapeType::WHO_KNOWS; /*union*/ struct { // I know but who cares... TODO: use variant from cpp17? @@ -40,13 +40,19 @@ struct BedShapeHint { } shape; }; +/// Get a bed shape hint for arrange() from a naked Polyline. BedShapeHint bedShape(const Polyline& bed); +/** + * @brief Classes implementing the Arrangeable interface can be used as input + * to the arrange function. + */ class Arrangeable { public: virtual ~Arrangeable() = default; + /// Apply the result transformation calculated by the arrangement. virtual void apply_arrange_result(Vec2d offset, double rotation_rads) = 0; /// Get the 2D silhouette to arrange and an initial offset and rotation @@ -58,56 +64,48 @@ using Arrangeables = std::vector; /** * \brief Arranges the model objects on the screen. * - * The arrangement considers multiple bins (aka. print beds) for placing all - * the items provided in the model argument. If the items don't fit on one - * print bed, the remaining will be placed onto newly created print beds. - * The first_bin_only parameter, if set to true, disables this behavior and - * makes sure that only one print bed is filled and the remaining items will be - * untouched. When set to false, the items which could not fit onto the - * print bed will be placed next to the print bed so the user should see a - * pile of items on the print bed and some other piles outside the print - * area that can be dragged later onto the print bed as a group. + * The arrangement considers multiple bins (aka. print beds) for placing + * all the items provided in the model argument. If the items don't fit on + * one print bed, the remaining will be placed onto newly created print + * beds. The first_bin_only parameter, if set to true, disables this + * behavior and makes sure that only one print bed is filled and the + * remaining items will be untouched. When set to false, the items which + * could not fit onto the print bed will be placed next to the print bed so + * the user should see a pile of items on the print bed and some other + * piles outside the print area that can be dragged later onto the print + * bed as a group. + * + * \param items Input which are object pointers implementing the + * Arrangeable interface. + * + * \param min_obj_distance The minimum distance which is allowed for any + * pair of items on the print bed in any direction. + * + * \param bedhint Info about the shape and type of the + * bed. remaining items which do not fit onto the print area next to the + * print bed or leave them untouched (let the user arrange them by hand or + * remove them). + * + * \param progressind Progress indicator callback called when + * an object gets packed. The unsigned argument is the number of items + * remaining to pack. * - * \param model The model object with the 3D content. - * \param dist The minimum distance which is allowed for any pair of items - * on the print bed in any direction. - * \param bb The bounding box of the print bed. It corresponds to the 'bin' - * for bin packing. - * \param first_bin_only This parameter controls whether to place the - * remaining items which do not fit onto the print area next to the print - * bed or leave them untouched (let the user arrange them by hand or remove - * them). - * \param progressind Progress indicator callback called when an object gets - * packed. The unsigned argument is the number of items remaining to pack. * \param stopcondition A predicate returning true if abort is needed. */ -//bool arrange(Model &model, -// WipeTowerInfo& wipe_tower_info, -// coord_t min_obj_distance, -// const Slic3r::Polyline& bed, -// BedShapeHint bedhint, -// bool first_bin_only, -// std::function progressind, -// std::function stopcondition); - bool arrange(Arrangeables &items, coord_t min_obj_distance, - BedShapeHint bedhint, + const BedShapeHint& bedhint, std::function progressind, std::function stopcondition); -/// This will find a suitable position for a new object instance and leave the -/// old items untouched. -//void find_new_position(const Model& model, -// ModelInstancePtrs instances_to_add, -// coord_t min_obj_distance, -// const Slic3r::Polyline& bed, -// WipeTowerInfo& wti); -void find_new_position(Arrangeables &items, - const Arrangeables &instances_to_add, - coord_t min_obj_distance, - BedShapeHint bedhint); - +/// Same as the previous, only that it takes unmovable items as an +/// additional argument. +bool arrange(Arrangeables &items, + const Arrangeables &excludes, + coord_t min_obj_distance, + const BedShapeHint& bedhint, + std::function progressind, + std::function stopcondition); } // arr } // Slic3r diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 193390f854..aba8ae71d9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2461,132 +2461,68 @@ void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { update_status(m_count, was_canceled() ? _(L("Arranging canceled.")) : _(L("Arranging done."))); +} - // TODO: we should decide whether to allow arrange when the search is - // running we should probably disable explicit slicing and background - // processing - -// static const auto arrangestr = _(L("Arranging")); - -// auto &config = plater().config; -// auto &view3D = plater().view3D; -// auto &model = plater().model; - -// // 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(config); -// } - -// auto min_obj_distance = coord_t(dist / SCALING_FACTOR); - -// const auto *bed_shape_opt = config->opt( -// "bed_shape"); - -// assert(bed_shape_opt); -// auto & bedpoints = bed_shape_opt->values; -// Polyline bed; -// bed.points.reserve(bedpoints.size()); -// for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - -// update_status(0, arrangestr); - -// arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); - -// try { -// arr::BedShapeHint hint; - -// // TODO: from Sasha from GUI or -// hint.type = arr::BedShapeType::WHO_KNOWS; - -// arr::arrange(model, -// wti, -// min_obj_distance, -// bed, -// hint, -// false, // create many piles not just one pile -// [this](unsigned st) { -// if (st > 0) -// update_status(count - int(st), arrangestr); -// }, -// [this]() { return was_canceled(); }); -// } catch (std::exception & /*e*/) { -// GUI::show_error(plater().q, -// L("Could not arrange model objects! " -// "Some geometries may be invalid.")); -// } - -// update_status(count, -// was_canceled() ? _(L("Arranging canceled.")) -// : _(L("Arranging done."))); - -// // it remains to move the wipe tower: -// view3D->get_canvas3d()->arrange_wipe_tower(wti); +void find_new_position(const Model & model, + ModelInstancePtrs instances, + coord_t min_d, + const arr::BedShapeHint &bedhint) +{ + + // TODO } void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() { -// int obj_idx = plater().get_selected_object_idx(); -// if (obj_idx < 0) { return; } + int obj_idx = plater().get_selected_object_idx(); + if (obj_idx < 0) { return; } -// ModelObject *o = plater().model.objects[size_t(obj_idx)]; + 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(); }); + 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(); }); -// const auto *bed_shape_opt = -// plater().config->opt("bed_shape"); + + double mindist = 6.0; // FIXME -// assert(bed_shape_opt); - -// auto & bedpoints = bed_shape_opt->values; -// Polyline bed; -// bed.points.reserve(bedpoints.size()); -// for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - -// double mindist = 6.0; // FIXME + if (!was_canceled()) { + for(ModelInstance * oi : o->instances) { + oi->set_rotation({r[X], r[Y], r[Z]}); -// 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); + 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(); + MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); + double r = rotbb.angle_to_X(); -// // The box should be landscape -// if(rotbb.width() < rotbb.height()) r += PI / 2; + // The box should be landscape + if(rotbb.width() < rotbb.height()) r += PI / 2; -// Vec3d rt = oi->get_rotation(); rt(Z) += r; + Vec3d rt = oi->get_rotation(); rt(Z) += r; -// oi->set_rotation(rt); -// } + oi->set_rotation(rt); + } -// arr::WipeTowerInfo wti; // useless in SLA context -// arr::find_new_position(plater().model, -// o->instances, -// coord_t(mindist / SCALING_FACTOR), -// bed, -// wti); - -// // Correct the z offset of the object which was corrupted be -// // the rotation -// o->ensure_on_bed(); -// } + find_new_position(plater().model, + o->instances, + scaled(mindist), + plater().get_bed_shape_hint()); -// update_status(100, -// was_canceled() ? _(L("Orientation search canceled.")) -// : _(L("Orientation found."))); + // 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() From d7c418ef84eabacdb83171277098a6c769ea00d4 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 1 Jul 2019 08:33:40 +0200 Subject: [PATCH 233/627] Modified function thick_lines_to_indexed_vertex_array() to remove visual artifacts on paths in gcode preview --- src/slic3r/GUI/3DScene.cpp | 167 +++++++++++-------------------------- 1 file changed, 48 insertions(+), 119 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index f9a79f2d8c..5853ab7125 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1020,8 +1020,8 @@ static void thick_lines_to_indexed_vertex_array( // right, left, top, bottom int idx_prev[4] = { -1, -1, -1, -1 }; double bottom_z_prev = 0.; - Vec2d b1_prev(Vec2d::Zero()); - Vec2d v_prev(Vec2d::Zero()); + Vec2d b1_prev(Vec2d::Zero()); + Vec2d v_prev(Vec2d::Zero()); int idx_initial[4] = { -1, -1, -1, -1 }; double width_initial = 0.; double bottom_z_initial = 0.0; @@ -1031,8 +1031,6 @@ static void thick_lines_to_indexed_vertex_array( for (size_t ii = 0; ii < lines_end; ++ ii) { size_t i = (ii == lines.size()) ? 0 : ii; const Line &line = lines[i]; - double len = unscale(line.length()); - double inv_len = 1.0 / len; double bottom_z = top_z - heights[i]; double middle_z = 0.5 * (top_z + bottom_z); double width = widths[i]; @@ -1041,8 +1039,7 @@ static void thick_lines_to_indexed_vertex_array( bool is_last = (ii == lines_end - 1); bool is_closing = closed && is_last; - Vec2d v = unscale(line.vector()); - v *= inv_len; + Vec2d v = unscale(line.vector()).normalized(); Vec2d a = unscale(line.a); Vec2d b = unscale(line.b); @@ -1061,9 +1058,7 @@ static void thick_lines_to_indexed_vertex_array( } // calculate new XY normals - Vector n = line.normal(); - Vec3d xy_right_normal = unscale(n(0), n(1), 0); - xy_right_normal *= inv_len; + Vec2d xy_right_normal = unscale(line.normal()).normalized(); int idx_a[4]; int idx_b[4]; @@ -1091,9 +1086,9 @@ static void thick_lines_to_indexed_vertex_array( idx_a[BOTTOM] = idx_last ++; volume.push_geometry(a(0), a(1), bottom_z, 0., 0., -1.); idx_a[LEFT ] = idx_last ++; - volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), -xy_right_normal(2)); + volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), 0.0); idx_a[RIGHT] = idx_last ++; - volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), xy_right_normal(2)); + volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), 0.0); } else { idx_a[BOTTOM] = idx_prev[BOTTOM]; @@ -1108,18 +1103,29 @@ static void thick_lines_to_indexed_vertex_array( // Continuing a previous segment. // Share left / right vertices if possible. double v_dot = v_prev.dot(v); - bool sharp = v_dot < 0.707; // sin(45 degrees) + bool sharp = v_dot < 0.9999; // v_dot < 0.9999; // cos(1 degree) if (sharp) { if (!bottom_z_different) { // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. idx_a[RIGHT] = idx_last++; - volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), xy_right_normal(2)); + volume.push_geometry(a1(0), a1(1), middle_z, xy_right_normal(0), xy_right_normal(1), 0.0); idx_a[LEFT] = idx_last++; - volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), -xy_right_normal(2)); + volume.push_geometry(a2(0), a2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), 0.0); + if (cross2(v_prev, v) > 0.) { + // Right turn. Fill in the right turn wedge. + volume.push_triangle(idx_prev[RIGHT], idx_a[RIGHT], idx_prev[TOP]); + volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a[RIGHT]); + } + else { + // Left turn. Fill in the left turn wedge. + volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a[LEFT]); + volume.push_triangle(idx_prev[LEFT], idx_a[LEFT], idx_prev[BOTTOM]); + } } } - if (v_dot > 0.9) { + else + { if (!bottom_z_different) { // The two successive segments are nearly collinear. @@ -1127,45 +1133,6 @@ static void thick_lines_to_indexed_vertex_array( idx_a[RIGHT] = idx_prev[RIGHT]; } } - else if (!sharp) { - if (!bottom_z_different) - { - // Create a sharp corner with an overshot and average the left / right normals. - // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc. - Vec2d intersection(Vec2d::Zero()); - Geometry::ray_ray_intersection(b1_prev, v_prev, a1, v, intersection); - a1 = intersection; - a2 = 2. * a - intersection; - assert((a - a1).norm() < width); - assert((a - a2).norm() < width); - float *n_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6; - float *p_left_prev = n_left_prev + 3; - float *n_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6; - float *p_right_prev = n_right_prev + 3; - p_left_prev [0] = float(a2(0)); - p_left_prev [1] = float(a2(1)); - p_right_prev[0] = float(a1(0)); - p_right_prev[1] = float(a1(1)); - xy_right_normal(0) += n_right_prev[0]; - xy_right_normal(1) += n_right_prev[1]; - xy_right_normal *= 1. / xy_right_normal.norm(); - n_left_prev [0] = float(-xy_right_normal(0)); - n_left_prev [1] = float(-xy_right_normal(1)); - n_right_prev[0] = float( xy_right_normal(0)); - n_right_prev[1] = float( xy_right_normal(1)); - idx_a[LEFT ] = idx_prev[LEFT ]; - idx_a[RIGHT] = idx_prev[RIGHT]; - } - } - else if (cross2(v_prev, v) > 0.) { - // Right turn. Fill in the right turn wedge. - volume.push_triangle(idx_prev[RIGHT], idx_a [RIGHT], idx_prev[TOP] ); - volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a [RIGHT] ); - } else { - // Left turn. Fill in the left turn wedge. - volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a [LEFT] ); - volume.push_triangle(idx_prev[LEFT], idx_a [LEFT], idx_prev[BOTTOM]); - } if (is_closing) { if (!sharp) { if (!bottom_z_different) @@ -1204,9 +1171,9 @@ static void thick_lines_to_indexed_vertex_array( } // Generate new vertices for the end of this line segment. idx_b[LEFT ] = idx_last ++; - volume.push_geometry(b2(0), b2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), -xy_right_normal(2)); + volume.push_geometry(b2(0), b2(1), middle_z, -xy_right_normal(0), -xy_right_normal(1), 0.0); idx_b[RIGHT ] = idx_last ++; - volume.push_geometry(b1(0), b1(1), middle_z, xy_right_normal(0), xy_right_normal(1), xy_right_normal(2)); + volume.push_geometry(b1(0), b1(1), middle_z, xy_right_normal(0), xy_right_normal(1), 0.0); memcpy(idx_prev, idx_b, 4 * sizeof(int)); bottom_z_prev = bottom_z; @@ -1265,9 +1232,9 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, int idx_initial[4] = { -1, -1, -1, -1 }; int idx_prev[4] = { -1, -1, -1, -1 }; double z_prev = 0.0; - Vec3d n_right_prev = Vec3d::Zero(); - Vec3d n_top_prev = Vec3d::Zero(); - Vec3d unit_v_prev = Vec3d::Zero(); + Vec3d n_right_prev = Vec3d::Zero(); + Vec3d n_top_prev = Vec3d::Zero(); + Vec3d unit_v_prev = Vec3d::Zero(); double width_initial = 0.0; // new vertices around the line endpoints @@ -1289,18 +1256,19 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, Vec3d n_top = Vec3d::Zero(); Vec3d n_right = Vec3d::Zero(); - Vec3d unit_positive_z(0.0, 0.0, 1.0); - + if ((line.a(0) == line.b(0)) && (line.a(1) == line.b(1))) { // vertical segment - n_right = (line.a(2) < line.b(2)) ? Vec3d(-1.0, 0.0, 0.0) : Vec3d(1.0, 0.0, 0.0); - n_top = Vec3d(0.0, 1.0, 0.0); + n_top = Vec3d::UnitY(); + n_right = Vec3d::UnitX(); + if (line.a(2) < line.b(2)) + n_right = -n_right; } else { - // generic segment - n_right = unit_v.cross(unit_positive_z).normalized(); + // horizontal segment + n_right = unit_v.cross(Vec3d::UnitZ()).normalized(); n_top = n_right.cross(unit_v).normalized(); } @@ -1361,7 +1329,7 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, // Continuing a previous segment. // Share left / right vertices if possible. double v_dot = unit_v_prev.dot(unit_v); - bool is_sharp = v_dot < 0.707; // sin(45 degrees) + bool is_sharp = v_dot < 0.9999; // v_dot < 0.9999; // cos(1 degree) bool is_right_turn = n_top_prev.dot(unit_v_prev.cross(unit_v)) > 0.0; if (is_sharp) @@ -1371,65 +1339,26 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, volume.push_geometry(a[RIGHT], n_right); idx_a[LEFT] = idx_last++; volume.push_geometry(a[LEFT], n_left); - } - if (v_dot > 0.9) + if (is_right_turn) + { + // Right turn. Fill in the right turn wedge. + volume.push_triangle(idx_prev[RIGHT], idx_a[RIGHT], idx_prev[TOP]); + volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a[RIGHT]); + } + else + { + // Left turn. Fill in the left turn wedge. + volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a[LEFT]); + volume.push_triangle(idx_prev[LEFT], idx_a[LEFT], idx_prev[BOTTOM]); + } + } + else { // The two successive segments are nearly collinear. idx_a[LEFT] = idx_prev[LEFT]; idx_a[RIGHT] = idx_prev[RIGHT]; } - else if (!is_sharp) - { - // Create a sharp corner with an overshot and average the left / right normals. - // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc. - - // averages normals - Vec3d average_n_right = 0.5 * (n_right + n_right_prev).normalized(); - Vec3d average_n_left = -average_n_right; - Vec3d average_rl_displacement = 0.5 * width * average_n_right; - - // updates vertices around a - a[RIGHT] = l_a + average_rl_displacement; - a[LEFT] = l_a - average_rl_displacement; - - // updates previous line normals - float* normal_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT] * 6; - normal_left_prev[0] = float(average_n_left(0)); - normal_left_prev[1] = float(average_n_left(1)); - normal_left_prev[2] = float(average_n_left(2)); - - float* normal_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6; - normal_right_prev[0] = float(average_n_right(0)); - normal_right_prev[1] = float(average_n_right(1)); - normal_right_prev[2] = float(average_n_right(2)); - - // updates previous line's vertices around b - float* b_left_prev = normal_left_prev + 3; - b_left_prev[0] = float(a[LEFT](0)); - b_left_prev[1] = float(a[LEFT](1)); - b_left_prev[2] = float(a[LEFT](2)); - - float* b_right_prev = normal_right_prev + 3; - b_right_prev[0] = float(a[RIGHT](0)); - b_right_prev[1] = float(a[RIGHT](1)); - b_right_prev[2] = float(a[RIGHT](2)); - - idx_a[LEFT] = idx_prev[LEFT]; - idx_a[RIGHT] = idx_prev[RIGHT]; - } - else if (is_right_turn) - { - // Right turn. Fill in the right turn wedge. - volume.push_triangle(idx_prev[RIGHT], idx_a[RIGHT], idx_prev[TOP]); - volume.push_triangle(idx_prev[RIGHT], idx_prev[BOTTOM], idx_a[RIGHT]); - } - else - { - // Left turn. Fill in the left turn wedge. - volume.push_triangle(idx_prev[LEFT], idx_prev[TOP], idx_a[LEFT]); - volume.push_triangle(idx_prev[LEFT], idx_a[LEFT], idx_prev[BOTTOM]); - } if (ii == lines.size()) { From 1a529cb77815cf2b392104611462da5b3d3641a2 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 28 Jun 2019 11:35:54 +0200 Subject: [PATCH 234/627] PresetUpdater: Fix: Index installed too early --- src/slic3r/Utils/PresetUpdater.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 8c3ced31a2..bc600fcad9 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -402,15 +402,8 @@ Updates PresetUpdater::priv::get_config_updates() const } } - copy_file_fix(idx.path(), bundle_path_idx); - const auto ver_current = idx.find(vp.config_version); const bool ver_current_found = ver_current != idx.end(); - if (! ver_current_found) { - auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str(); - BOOST_LOG_TRIVIAL(error) << message; - GUI::show_error(nullptr, GUI::from_u8(message)); - } BOOST_LOG_TRIVIAL(debug) << boost::format("Vendor: %1%, version installed: %2%%3%, version cached: %4%") % vp.name @@ -418,6 +411,13 @@ Updates PresetUpdater::priv::get_config_updates() const % (ver_current_found ? "" : " (not found in index!)") % recommended->config_version.to_string(); + if (! ver_current_found) { + auto message = (boost::format("Preset bundle `%1%` version not found in index: %2%") % idx.vendor() % vp.config_version.to_string()).str(); + BOOST_LOG_TRIVIAL(error) << message; + GUI::show_error(nullptr, GUI::from_u8(message)); + continue; + } + if (ver_current_found && !ver_current->is_current_slic3r_supported()) { BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string(); updates.incompats.emplace_back(std::move(bundle_path), *ver_current, vp.name); @@ -459,10 +459,16 @@ Updates PresetUpdater::priv::get_config_updates() const found = true; } } - if (! found) + + if (found) { + // 'Install' the index in the vendor directory. This is used to memoize + // offered updates and to not offer the same update again if it was cancelled by the user. + copy_file_fix(idx.path(), bundle_path_idx); + } else { BOOST_LOG_TRIVIAL(warning) << boost::format("Index for vendor %1% indicates update (%2%) but the new bundle was found neither in cache nor resources") % idx.vendor() % recommended->config_version.to_string(); + } } } From 4269c8b23cb6878a20f468a916d0079ecaf647a0 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 1 Jul 2019 12:28:16 +0200 Subject: [PATCH 235/627] Removed GLVolume non-VBO rendering --- src/slic3r/GUI/3DBed.cpp | 25 +- src/slic3r/GUI/3DBed.hpp | 8 +- src/slic3r/GUI/3DScene.cpp | 315 +++++++--------------- src/slic3r/GUI/3DScene.hpp | 55 ++-- src/slic3r/GUI/GLCanvas3D.cpp | 185 ++++++------- src/slic3r/GUI/GLCanvas3D.hpp | 3 +- src/slic3r/GUI/GLCanvas3DManager.cpp | 5 +- src/slic3r/GUI/GLCanvas3DManager.hpp | 1 - src/slic3r/GUI/GLToolbar.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 4 +- src/slic3r/GUI/Selection.cpp | 6 +- src/slic3r/GUI/Selection.hpp | 2 +- 12 files changed, 221 insertions(+), 390 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index d73e423e0b..2b2631ab8e 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -274,8 +274,8 @@ void Bed3D::Axes::render_axis(double length) const Bed3D::Bed3D() : m_type(Custom) - , m_requires_canvas_update(false) #if ENABLE_TEXTURES_FROM_SVG + , m_requires_canvas_update(false) , m_vbo_id(0) #endif // ENABLE_TEXTURES_FROM_SVG , m_scale_factor(1.0f) @@ -330,12 +330,11 @@ Point Bed3D::point_projection(const Point& point) const } #if ENABLE_TEXTURES_FROM_SVG -void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const +void Bed3D::render(GLCanvas3D* canvas, float theta, float scale_factor) const { m_scale_factor = scale_factor; - EType type = useVBOs ? m_type : Custom; - switch (type) + switch (m_type) { case MK2: { @@ -361,7 +360,7 @@ void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_fa } } #else -void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const +void Bed3D::render(float theta, float scale_factor) const { m_scale_factor = scale_factor; @@ -372,17 +371,17 @@ void Bed3D::render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_fa { case MK2: { - render_prusa(canvas, "mk2", theta, useVBOs); + render_prusa("mk2", theta); break; } case MK3: { - render_prusa(canvas, "mk3", theta, useVBOs); + render_prusa("mk3", theta); break; } case SL1: { - render_prusa(canvas, "sl1", theta, useVBOs); + render_prusa("sl1", theta); break; } default: @@ -546,7 +545,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom if (!bottom) { filename = model_path + "_bed.stl"; - if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, true)) { + if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); if (key == "mk2") // hardcoded value to match the stl model @@ -651,7 +650,7 @@ void Bed3D::render_prusa_shader(bool transparent) const } } #else -void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) const +void Bed3D::render_prusa(const std::string& key, float theta) const { std::string tex_path = resources_dir() + "/icons/bed/" + key; @@ -677,7 +676,7 @@ void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) cons std::string filename = tex_path + "_top.png"; if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) { - if (!m_top_texture.load_from_file(filename, true)) + if (!m_top_texture.load_from_file(filename, true, true)) { render_custom(); return; @@ -694,7 +693,7 @@ void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) cons filename = tex_path + "_bottom.png"; if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) { - if (!m_bottom_texture.load_from_file(filename, true)) + if (!m_bottom_texture.load_from_file(filename, true, true)) { render_custom(); return; @@ -711,7 +710,7 @@ void Bed3D::render_prusa(const std::string &key, float theta, bool useVBOs) cons if (theta <= 90.0f) { filename = model_path + "_bed.stl"; - if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, useVBOs)) { + if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); if (key == "mk2") // hardcoded value to match the stl model diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 68a74f61cd..f5491221af 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -128,7 +128,11 @@ public: bool contains(const Point& point) const; Point point_projection(const Point& point) const; - void render(GLCanvas3D* canvas, float theta, bool useVBOs, float scale_factor) const; +#if ENABLE_TEXTURES_FROM_SVG + void render(GLCanvas3D* canvas, float theta, float scale_factor) const; +#else + void render(float theta, float scale_factor) const; +#endif // ENABLE_TEXTURES_FROM_SVG void render_axes() const; private: @@ -140,7 +144,7 @@ private: void render_prusa(GLCanvas3D* canvas, const std::string& key, bool bottom) const; void render_prusa_shader(bool transparent) const; #else - void render_prusa(const std::string &key, float theta, bool useVBOs) const; + void render_prusa(const std::string& key, float theta) const; #endif // ENABLE_TEXTURES_FROM_SVG void render_custom() const; #if ENABLE_TEXTURES_FROM_SVG diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 5853ab7125..417aaee4ff 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -55,21 +55,6 @@ void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char namespace Slic3r { -void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh) -{ - assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0); - assert(quad_indices.empty() && triangle_indices_size == 0); - assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size()); - - this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count()); - - for (int i = 0; i < (int)mesh.stl.stats.number_of_facets; ++i) { - const stl_facet &facet = mesh.stl.facet_start[i]; - for (int j = 0; j < 3; ++ j) - this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2)); - } -} - void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh) { assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0); @@ -89,7 +74,7 @@ void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh) } } -void GLIndexedVertexArray::finalize_geometry(bool use_VBOs) +void GLIndexedVertexArray::finalize_geometry() { assert(this->vertices_and_normals_interleaved_VBO_id == 0); assert(this->triangle_indices_VBO_id == 0); @@ -97,28 +82,28 @@ void GLIndexedVertexArray::finalize_geometry(bool use_VBOs) this->setup_sizes(); - if (use_VBOs) { - if (! empty()) { - glsafe(::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved.size() * 4, this->vertices_and_normals_interleaved.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - this->vertices_and_normals_interleaved.clear(); - } - if (! this->triangle_indices.empty()) { - glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices.size() * 4, this->triangle_indices.data(), GL_STATIC_DRAW)); - this->triangle_indices.clear(); - } - if (! this->quad_indices.empty()) { - glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices.size() * 4, this->quad_indices.data(), GL_STATIC_DRAW)); - this->quad_indices.clear(); - } - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + if (! empty()) { + glsafe(::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved.size() * 4, this->vertices_and_normals_interleaved.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + this->vertices_and_normals_interleaved.clear(); } + if (! this->triangle_indices.empty()) { + glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices.size() * 4, this->triangle_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + this->triangle_indices.clear(); + } + if (! this->quad_indices.empty()) { + glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices.size() * 4, this->quad_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + this->quad_indices.clear(); + } + this->shrink_to_fit(); } @@ -423,7 +408,7 @@ void GLVolume::render() const glFrontFace(GL_CCW); } -void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) const +void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const { if (!is_active) return; @@ -431,9 +416,6 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c if (!indexed_vertex_array.vertices_and_normals_interleaved_VBO_id) return; - if (this->is_left_handed()) - glFrontFace(GL_CW); - GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first)); GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first)); if (n_triangles + n_quads == 0) @@ -464,6 +446,9 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c return; } + if (this->is_left_handed()) + glFrontFace(GL_CW); + if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)render_color)); else @@ -500,162 +485,114 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c glFrontFace(GL_CCW); } -void GLVolume::render_legacy() const -{ - assert(!indexed_vertex_array.vertices_and_normals_interleaved_VBO_id); - if (!is_active) - return; - - if (this->is_left_handed()) - glFrontFace(GL_CW); - - GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first)); - GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first)); - if (n_triangles + n_quads == 0) - { - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glColor4fv(render_color)); - render(); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - return; - } - - glsafe(::glColor4fv(render_color)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), indexed_vertex_array.vertices_and_normals_interleaved.data() + 3)); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), indexed_vertex_array.vertices_and_normals_interleaved.data())); - - glsafe(::glPushMatrix()); - - glsafe(::glMultMatrixd(world_matrix().data())); - - if (n_triangles > 0) - glsafe(::glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, indexed_vertex_array.triangle_indices.data() + tverts_range.first)); - - if (n_quads > 0) - glsafe(::glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, indexed_vertex_array.quad_indices.data() + qverts_range.first)); - - glsafe(::glPopMatrix()); - - if (this->is_left_handed()) - glFrontFace(GL_CCW); -} - bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); } bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposBasePool); } std::vector GLVolumeCollection::load_object( - const ModelObject *model_object, + const ModelObject* model_object, int obj_idx, - const std::vector &instance_idxs, - const std::string &color_by, - bool use_VBOs) + const std::vector& instance_idxs, + const std::string& color_by) { std::vector volumes_idx; - for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++ volume_idx) + for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++volume_idx) for (int instance_idx : instance_idxs) - volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by, use_VBOs)); - return volumes_idx; + volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by)); + return volumes_idx; } int GLVolumeCollection::load_object_volume( - const ModelObject *model_object, + const ModelObject* model_object, int obj_idx, int volume_idx, int instance_idx, - const std::string &color_by, - bool use_VBOs) + const std::string& color_by) { - const ModelVolume *model_volume = model_object->volumes[volume_idx]; - const int extruder_id = model_volume->extruder_id(); - const ModelInstance *instance = model_object->instances[instance_idx]; + const ModelVolume* model_volume = model_object->volumes[volume_idx]; + const int extruder_id = model_volume->extruder_id(); + const ModelInstance* instance = model_object->instances[instance_idx]; const TriangleMesh& mesh = model_volume->mesh(); float color[4]; memcpy(color, GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); -/* if (model_volume->is_support_blocker()) { - color[0] = 1.0f; - color[1] = 0.2f; - color[2] = 0.2f; - } else if (model_volume->is_support_enforcer()) { - color[0] = 0.2f; - color[1] = 0.2f; - color[2] = 1.0f; - } - color[3] = model_volume->is_model_part() ? 1.f : 0.5f; */ + /* if (model_volume->is_support_blocker()) { + color[0] = 1.0f; + color[1] = 0.2f; + color[2] = 0.2f; + } else if (model_volume->is_support_enforcer()) { + color[0] = 0.2f; + color[1] = 0.2f; + color[2] = 1.0f; + } + color[3] = model_volume->is_model_part() ? 1.f : 0.5f; */ color[3] = model_volume->is_model_part() ? 1.f : 0.5f; this->volumes.emplace_back(new GLVolume(color)); - GLVolume &v = *this->volumes.back(); + GLVolume& v = *this->volumes.back(); v.set_color_from_model_volume(model_volume); - v.indexed_vertex_array.load_mesh(mesh, use_VBOs); + v.indexed_vertex_array.load_mesh(mesh); // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(use_VBOs); - v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); + v.indexed_vertex_array.finalize_geometry(); + v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); if (model_volume->is_model_part()) { - // GLVolume will reference a convex hull from model_volume! + // GLVolume will reference a convex hull from model_volume! v.set_convex_hull(model_volume->get_convex_hull_shared_ptr()); if (extruder_id != -1) v.extruder_id = extruder_id; } - v.is_modifier = ! model_volume->is_model_part(); + v.is_modifier = !model_volume->is_model_part(); v.shader_outside_printer_detection_enabled = model_volume->is_model_part(); v.set_instance_transformation(instance->get_transformation()); v.set_volume_transformation(model_volume->get_transformation()); - return int(this->volumes.size() - 1); + return int(this->volumes.size() - 1); } // Load SLA auxiliary GLVolumes (for support trees or pad). // This function produces volumes for multiple instances in a single shot, // as some object specific mesh conversions may be expensive. void GLVolumeCollection::load_object_auxiliary( - const SLAPrintObject *print_object, + const SLAPrintObject* print_object, int obj_idx, // pairs of - const std::vector> &instances, + const std::vector>& instances, SLAPrintObjectStep milestone, // Timestamp of the last change of the milestone - size_t timestamp, - bool use_VBOs) + size_t timestamp) { assert(print_object->is_step_done(milestone)); Transform3d mesh_trafo_inv = print_object->trafo().inverse(); // Get the support mesh. TriangleMesh mesh = print_object->get_mesh(milestone); mesh.transform(mesh_trafo_inv); - // Convex hull is required for out of print bed detection. - TriangleMesh convex_hull = mesh.convex_hull_3d(); - for (const std::pair &instance_idx : instances) { - const ModelInstance &model_instance = *print_object->model_object()->instances[instance_idx.first]; + // Convex hull is required for out of print bed detection. + TriangleMesh convex_hull = mesh.convex_hull_3d(); + for (const std::pair& instance_idx : instances) { + const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); - GLVolume &v = *this->volumes.back(); - v.indexed_vertex_array.load_mesh(mesh, use_VBOs); + GLVolume& v = *this->volumes.back(); + v.indexed_vertex_array.load_mesh(mesh); // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(use_VBOs); - v.composite_id = GLVolume::CompositeID(obj_idx, - int(milestone), (int)instance_idx.first); + v.indexed_vertex_array.finalize_geometry(); + v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); v.geometry_id = std::pair(timestamp, model_instance.id().id); - // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. + // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. if (&instance_idx == &instances.back()) v.set_convex_hull(std::move(convex_hull)); else v.set_convex_hull(convex_hull); - v.is_modifier = false; + v.is_modifier = false; v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree); v.set_instance_transformation(model_instance.get_transformation()); - // Leave the volume transformation at identity. + // Leave the volume transformation at identity. // v.set_volume_transformation(model_volume->get_transformation()); } } int GLVolumeCollection::load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width) + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width) { if (depth < 0.01f) return int(this->volumes.size() - 1); @@ -680,45 +617,45 @@ int GLVolumeCollection::load_wipe_tower_preview( { 38.453f, 0, 1 }, { 0, 0, 1 }, { 0, -depth, 1 }, { 100.0f, -depth, 1 }, { 100.0f, 0, 1 }, { 61.547f, 0, 1 }, { 55.7735f, -10.0f, 1 }, { 44.2265f, 10.0f, 1 } }; int out_facets_idx[][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 5, 0 }, { 3, 5, 6 }, { 6, 2, 7 }, { 6, 0, 2 }, { 8, 9, 10 }, { 11, 12, 13 }, { 10, 11, 14 }, { 14, 11, 13 }, { 15, 8, 14 }, {8, 10, 14}, {3, 12, 4}, {3, 13, 12}, {6, 13, 3}, {6, 14, 13}, {7, 14, 6}, {7, 15, 14}, {2, 15, 7}, {2, 8, 15}, {1, 8, 2}, {1, 9, 8}, - {0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11}}; - for (int i=0;i<16;++i) - points.push_back(Vec3d(out_points_idx[i][0] / (100.f/min_width), out_points_idx[i][1] + depth, out_points_idx[i][2])); - for (int i=0;i<28;++i) + {0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11} }; + for (int i = 0; i < 16; ++i) + points.push_back(Vec3d(out_points_idx[i][0] / (100.f / min_width), out_points_idx[i][1] + depth, out_points_idx[i][2])); + for (int i = 0; i < 28; ++i) facets.push_back(Vec3crd(out_facets_idx[i][0], out_facets_idx[i][1], out_facets_idx[i][2])); TriangleMesh tooth_mesh(points, facets); // We have the mesh ready. It has one tooth and width of min_width. We will now append several of these together until we are close to // the required width of the block. Than we can scale it precisely. - size_t n = std::max(1, int(width/min_width)); // How many shall be merged? - for (size_t i=0;ivolumes.emplace_back(new GLVolume(color)); - GLVolume &v = *this->volumes.back(); - v.indexed_vertex_array.load_mesh(mesh, use_VBOs); + GLVolume& v = *this->volumes.back(); + v.indexed_vertex_array.load_mesh(mesh); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); - v.set_volume_rotation(Vec3d(0., 0., (M_PI/180.) * rotation_angle)); + v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(use_VBOs); - v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); + v.indexed_vertex_array.finalize_geometry(); + v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); v.geometry_id.first = 0; v.geometry_id.second = wipe_tower_instance_id().id; v.is_wipe_tower = true; - v.shader_outside_printer_detection_enabled = ! size_unknown; + v.shader_outside_printer_detection_enabled = !size_unknown; return int(this->volumes.size() - 1); } @@ -759,7 +696,7 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo return list; } -void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func) const +void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func) const { glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -797,7 +734,7 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); - volume.first->render_VBOs(color_id, print_box_detection_id, print_box_worldmatrix_id); + volume.first->render(color_id, print_box_detection_id, print_box_worldmatrix_id); } glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); @@ -812,34 +749,6 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool glsafe(::glDisable(GL_BLEND)); } -void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func) const -{ - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - glsafe(::glCullFace(GL_BACK)); - if (disable_cullface) - glsafe(::glDisable(GL_CULL_FACE)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); - for (GLVolumeWithIdAndZ& volume : to_render) - { - volume.first->set_render_color(); - volume.first->render_legacy(); - } - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - if (disable_cullface) - glsafe(::glEnable(GL_CULL_FACE)); - - glsafe(::glDisable(GL_BLEND)); -} - bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstance::EPrintVolumeState* out_state) { if (config == nullptr) @@ -1623,8 +1532,7 @@ void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height GUI::GLCanvas3DManager _3DScene::s_canvas_mgr; GLModel::GLModel() - : m_useVBOs(false) - , m_filename("") + : m_filename("") { m_volume.shader_outside_printer_detection_enabled = false; } @@ -1677,14 +1585,6 @@ void GLModel::reset() } void GLModel::render() const -{ - if (m_useVBOs) - render_VBOs(); - else - render_legacy(); -} - -void GLModel::render_VBOs() const { glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -1697,7 +1597,8 @@ void GLModel::render_VBOs() const glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id)); GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1; glcheck(); - m_volume.render_VBOs(color_id, -1, -1); + + m_volume.render(color_id, -1, -1); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); @@ -1708,26 +1609,7 @@ void GLModel::render_VBOs() const glsafe(::glDisable(GL_BLEND)); } -void GLModel::render_legacy() const -{ - glsafe(::glEnable(GL_LIGHTING)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - glsafe(::glCullFace(GL_BACK)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - m_volume.render_legacy(); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glDisable(GL_LIGHTING)); -} - -bool GLArrow::on_init(bool useVBOs) +bool GLArrow::on_init() { Pointf3s vertices; std::vector triangles; @@ -1780,9 +1662,8 @@ bool GLArrow::on_init(bool useVBOs) triangles.emplace_back(6, 0, 7); triangles.emplace_back(7, 13, 6); - m_useVBOs = useVBOs; - m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles), useVBOs); - m_volume.finalize_geometry(m_useVBOs); + m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles)); + m_volume.finalize_geometry(); return true; } @@ -1794,7 +1675,7 @@ GLCurvedArrow::GLCurvedArrow(unsigned int resolution) m_resolution = 1; } -bool GLCurvedArrow::on_init(bool useVBOs) +bool GLCurvedArrow::on_init() { Pointf3s vertices; std::vector triangles; @@ -1895,14 +1776,13 @@ bool GLCurvedArrow::on_init(bool useVBOs) triangles.emplace_back(vertices_per_level, vertices_per_level + 1, 0); triangles.emplace_back(vertices_per_level, 2 * vertices_per_level + 1, vertices_per_level + 1); - m_useVBOs = useVBOs; - m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles), useVBOs); + m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles)); m_volume.bounding_box = m_volume.indexed_vertex_array.bounding_box(); - m_volume.finalize_geometry(m_useVBOs); + m_volume.finalize_geometry(); return true; } -bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs) +bool GLBed::on_init_from_file(const std::string& filename) { reset(); @@ -1923,7 +1803,6 @@ bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs) } m_filename = filename; - m_useVBOs = useVBOs; ModelObject* model_object = model.objects.front(); model_object->center_around_origin(); @@ -1931,13 +1810,13 @@ bool GLBed::on_init_from_file(const std::string& filename, bool useVBOs) TriangleMesh mesh = model.mesh(); mesh.repair(); - m_volume.indexed_vertex_array.load_mesh(mesh, useVBOs); + m_volume.indexed_vertex_array.load_mesh(mesh); float color[4] = { 0.235f, 0.235f, 0.235f, 1.0f }; set_color(color, 4); m_volume.bounding_box = m_volume.indexed_vertex_array.bounding_box(); - m_volume.finalize_geometry(m_useVBOs); + m_volume.finalize_geometry(); return true; } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index d197372abb..bfdce2daf5 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -115,9 +115,8 @@ public: unsigned int triangle_indices_VBO_id; unsigned int quad_indices_VBO_id; - void load_mesh_flat_shading(const TriangleMesh &mesh); void load_mesh_full_shading(const TriangleMesh &mesh); - void load_mesh(const TriangleMesh &mesh, bool use_VBOs) { use_VBOs ? this->load_mesh_full_shading(mesh) : this->load_mesh_flat_shading(mesh); } + void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; } @@ -166,7 +165,7 @@ public: // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(bool use_VBOs); + void finalize_geometry(); // Release the geometry data, release OpenGL VBOs. void release_geometry(); // Render either using an immediate mode, or the VBOs. @@ -415,10 +414,9 @@ public: void set_range(coordf_t low, coordf_t high); void render() const; - void render_VBOs(int color_id, int detection_id, int worldmatrix_id) const; - void render_legacy() const; + void render(int color_id, int detection_id, int worldmatrix_id) const; - void finalize_geometry(bool use_VBOs) { this->indexed_vertex_array.finalize_geometry(use_VBOs); } + void finalize_geometry() { this->indexed_vertex_array.finalize_geometry(); } void release_geometry() { this->indexed_vertex_array.release_geometry(); } void set_bounding_boxes_as_dirty() { m_transformed_bounding_box_dirty = true; m_transformed_convex_hull_bounding_box_dirty = true; } @@ -459,42 +457,38 @@ public: ~GLVolumeCollection() { clear(); }; std::vector load_object( - const ModelObject *model_object, + const ModelObject* model_object, int obj_idx, - const std::vector &instance_idxs, - const std::string &color_by, - bool use_VBOs); + const std::vector& instance_idxs, + const std::string& color_by); int load_object_volume( - const ModelObject *model_object, + const ModelObject* model_object, int obj_idx, int volume_idx, int instance_idx, - const std::string &color_by, - bool use_VBOs); + const std::string& color_by); // Load SLA auxiliary GLVolumes (for support trees or pad). void load_object_auxiliary( - const SLAPrintObject *print_object, + const SLAPrintObject* print_object, int obj_idx, // pairs of - const std::vector> &instances, + const std::vector>& instances, SLAPrintObjectStep milestone, // Timestamp of the last change of the milestone - size_t timestamp, - bool use_VBOs); + size_t timestamp); int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width); + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); // Render the volumes by OpenGL. - void render_VBOs(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; - void render_legacy(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; + void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(bool use_VBOs) { for (auto *v : volumes) v->finalize_geometry(use_VBOs); } + void finalize_geometry() { for (auto* v : volumes) v->finalize_geometry(); } // Release the geometry data assigned to the volumes. // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them. void release_geometry() { for (auto *v : volumes) v->release_geometry(); } @@ -533,15 +527,14 @@ class GLModel { protected: GLVolume m_volume; - bool m_useVBOs; std::string m_filename; public: GLModel(); virtual ~GLModel(); - bool init(bool useVBOs) { return on_init(useVBOs); } - bool init_from_file(const std::string& filename, bool useVBOs) { return on_init_from_file(filename, useVBOs); } + bool init() { return on_init(); } + bool init_from_file(const std::string& filename) { return on_init_from_file(filename); } void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box.center()); } void set_color(const float* color, unsigned int size); @@ -562,18 +555,14 @@ public: void render() const; protected: - virtual bool on_init(bool useVBOs) { return false; } - virtual bool on_init_from_file(const std::string& filename, bool useVBOs) { return false; } - -private: - void render_VBOs() const; - void render_legacy() const; + virtual bool on_init() { return false; } + virtual bool on_init_from_file(const std::string& filename) { return false; } }; class GLArrow : public GLModel { protected: - virtual bool on_init(bool useVBOs); + virtual bool on_init(); }; class GLCurvedArrow : public GLModel @@ -584,13 +573,13 @@ public: explicit GLCurvedArrow(unsigned int resolution); protected: - virtual bool on_init(bool useVBOs); + virtual bool on_init(); }; class GLBed : public GLModel { protected: - virtual bool on_init_from_file(const std::string& filename, bool useVBOs); + virtual bool on_init_from_file(const std::string& filename); }; class _3DScene diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9bb6f45534..ab08aa0a77 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1211,7 +1211,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , m_model(nullptr) , m_dirty(true) , m_initialized(false) - , m_use_VBOs(false) , m_apply_zoom_to_volumes_filter(false) , m_legend_texture_enabled(false) , m_picking_enabled(false) @@ -1252,7 +1251,7 @@ void GLCanvas3D::post_event(wxEvent &&event) wxPostEvent(m_canvas, event); } -bool GLCanvas3D::init(bool useVBOs) +bool GLCanvas3D::init() { if (m_initialized) return true; @@ -1303,30 +1302,30 @@ bool GLCanvas3D::init(bool useVBOs) if (m_multisample_allowed) glsafe(::glEnable(GL_MULTISAMPLE)); - if (useVBOs && !m_shader.init("gouraud.vs", "gouraud.fs")) + if (!m_shader.init("gouraud.vs", "gouraud.fs")) + { + std::cout << "Unable to initialize gouraud shader: please, check that the files gouraud.vs and gouraud.fs are available" << std::endl; return false; + } - if (m_toolbar.is_enabled() && useVBOs && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs")) + if (m_toolbar.is_enabled() && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs")) + { + std::cout << "Unable to initialize variable_layer_height shader: please, check that the files variable_layer_height.vs and variable_layer_height.fs are available" << std::endl; return false; - - m_use_VBOs = useVBOs; + } // on linux the gl context is not valid until the canvas is not shown on screen // we defer the geometry finalization of volumes until the first call to render() if (!m_volumes.empty()) - m_volumes.finalize_geometry(m_use_VBOs); + m_volumes.finalize_geometry(); - if (m_gizmos.is_enabled()) { - if (! m_gizmos.init(*this)) { - std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; - return false; - } - } + if (m_gizmos.is_enabled() && !m_gizmos.init(*this)) + std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; if (!_init_toolbar()) return false; - if (m_selection.is_enabled() && !m_selection.init(m_use_VBOs)) + if (m_selection.is_enabled() && !m_selection.init()) return false; post_event(SimpleEvent(EVT_GLCANVAS_INIT)); @@ -1778,7 +1777,7 @@ std::vector GLCanvas3D::load_object(const ModelObject& model_object, int ob instance_idxs.push_back(i); } } - return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_use_VBOs && m_initialized); + return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by); } std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) @@ -1966,8 +1965,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); if (it->new_geometry()) { // New volume. - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_use_VBOs && m_initialized); - m_volumes.volumes.back()->geometry_id = key.geometry_id; + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by); + m_volumes.volumes.back()->geometry_id = key.geometry_id; update_object_list = true; } else { // Recycling an old GLVolume. @@ -2031,7 +2030,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re for (size_t istep = 0; istep < sla_steps.size(); ++istep) if (!instances[istep].empty()) - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_use_VBOs && m_initialized); + m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); } // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed @@ -2068,9 +2067,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); if (!print->is_step_done(psWipeTower)) - depth = (900.f/w) * (float)(extruders_count - 1) ; + depth = (900.f/w) * (float)(extruders_count - 1); int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - 1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), + 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), brim_spacing * 4.5f); if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; @@ -3821,7 +3820,11 @@ void GLCanvas3D::_render_bed(float theta) const #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); #endif // ENABLE_RETINA_GL - m_bed.render(const_cast(this), theta, m_use_VBOs, scale_factor); +#if ENABLE_TEXTURES_FROM_SVG + m_bed.render(const_cast(this), theta, scale_factor); +#else + m_bed.render(theta, scale_factor); +#endif // ENABLE_TEXTURES_FROM_SVG } void GLCanvas3D::_render_axes() const @@ -3829,8 +3832,6 @@ void GLCanvas3D::_render_axes() const m_bed.render_axes(); } - - void GLCanvas3D::_render_objects() const { if (m_volumes.empty()) @@ -3841,75 +3842,44 @@ void GLCanvas3D::_render_objects() const m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane(); - if (m_use_VBOs) + if (m_picking_enabled) { - if (m_picking_enabled) + // Update the layer editing selection to the first object selected, update the current object maximum Z. + const_cast(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); + + if (m_config != nullptr) { - // Update the layer editing selection to the first object selected, update the current object maximum Z. - const_cast(m_layers_editing).select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); - - if (m_config != nullptr) - { - const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(false); - m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); - m_volumes.check_outside_state(m_config, nullptr); - } + const BoundingBoxf3& bed_bb = m_bed.get_bounding_box(false); + m_volumes.set_print_box((float)bed_bb.min(0), (float)bed_bb.min(1), 0.0f, (float)bed_bb.max(0), (float)bed_bb.max(1), (float)m_config->opt_float("max_print_height")); + m_volumes.check_outside_state(m_config, nullptr); } - - if (m_use_clipping_planes) - m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); - else - m_volumes.set_z_range(-FLT_MAX, FLT_MAX); - - m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); - - m_shader.start_using(); - if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { - int object_id = m_layers_editing.last_object_id; - m_volumes.render_VBOs(GLVolumeCollection::Opaque, false, m_camera.get_view_matrix(), [object_id](const GLVolume &volume) { - // Which volume to paint without the layer height profile shader? - return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); - }); - // Let LayersEditing handle rendering of the active object using the layer height profile shader. - m_layers_editing.render_volumes(*this, this->m_volumes); - } else { - // do not cull backfaces to show broken geometry, if any - m_volumes.render_VBOs(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { - return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); - }); - } - m_volumes.render_VBOs(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); - m_shader.stop_using(); } + + if (m_use_clipping_planes) + m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); else - { - ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); - ::glEnable(GL_CLIP_PLANE0); + m_volumes.set_z_range(-FLT_MAX, FLT_MAX); - if (m_use_clipping_planes) - { - glsafe(::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[0].get_data())); - glsafe(::glEnable(GL_CLIP_PLANE1)); - glsafe(::glClipPlane(GL_CLIP_PLANE2, (GLdouble*)m_clipping_planes[1].get_data())); - glsafe(::glEnable(GL_CLIP_PLANE2)); - } - + m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); + m_shader.start_using(); + if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { + int object_id = m_layers_editing.last_object_id; + m_volumes.render(GLVolumeCollection::Opaque, false, m_camera.get_view_matrix(), [object_id](const GLVolume& volume) { + // Which volume to paint without the layer height profile shader? + return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); + }); + // Let LayersEditing handle rendering of the active object using the layer height profile shader. + m_layers_editing.render_volumes(*this, this->m_volumes); + } else { // do not cull backfaces to show broken geometry, if any - m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { + m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) { return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0); }); - m_volumes.render_legacy(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); - - ::glDisable(GL_CLIP_PLANE0); - - if (m_use_clipping_planes) - { - glsafe(::glDisable(GL_CLIP_PLANE1)); - glsafe(::glDisable(GL_CLIP_PLANE2)); - } } - + m_volumes.render(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix()); + m_shader.stop_using(); + m_camera_clipping_plane = ClippingPlane::ClipsNothing(); glsafe(::glDisable(GL_LIGHTING)); } @@ -4025,6 +3995,7 @@ void GLCanvas3D::_render_current_gizmo() const void GLCanvas3D::_render_gizmos_overlay() const { +#if ENABLE_SVG_ICONS #if ENABLE_RETINA_GL // m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor()); const float scale = m_retina_helper->get_scale_factor()*wxGetApp().toolbar_icon_scale(); @@ -4035,6 +4006,7 @@ void GLCanvas3D::_render_gizmos_overlay() const const float size = int(GLGizmosManager::Default_Icons_Size*wxGetApp().toolbar_icon_scale()); m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment #endif /* __WXMSW__ */ +#endif // ENABLE_SVG_ICONS m_gizmos.render_overlay(*this, m_selection); } @@ -4288,13 +4260,9 @@ void GLCanvas3D::_render_sla_slices() const void GLCanvas3D::_render_selection_sidebar_hints() const { - if (m_use_VBOs) - m_shader.start_using(); - + m_shader.start_using(); m_selection.render_sidebar_hints(m_sidebar_field); - - if (m_use_VBOs) - m_shader.stop_using(); + m_shader.stop_using(); } @@ -4507,7 +4475,7 @@ void GLCanvas3D::_load_print_toolpaths() _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), volume); } volume.bounding_box = volume.indexed_vertex_array.bounding_box(); - volume.indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + volume.indexed_vertex_array.finalize_geometry(); } void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors, const std::vector& color_print_values) @@ -4694,7 +4662,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(); BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; } @@ -4865,7 +4833,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(); BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; } @@ -5050,7 +5018,7 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat { GLVolume* volume = m_volumes.volumes[i]; volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + volume->indexed_vertex_array.finalize_geometry(); } } } @@ -5105,7 +5073,7 @@ void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, { GLVolume* volume = m_volumes.volumes[i]; volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + volume->indexed_vertex_array.finalize_geometry(); } } } @@ -5339,7 +5307,7 @@ void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data) // finalize volumes and sends geometry to gpu volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + volume->indexed_vertex_array.finalize_geometry(); } } @@ -5370,7 +5338,7 @@ void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data) // finalize volumes and sends geometry to gpu volume->bounding_box = volume->indexed_vertex_array.bounding_box(); - volume->indexed_vertex_array.finalize_geometry(m_use_VBOs && m_initialized); + volume->indexed_vertex_array.finalize_geometry(); } } @@ -5396,7 +5364,7 @@ void GLCanvas3D::_load_fff_shells() instance_ids[i] = i; } - m_volumes.load_object(model_obj, object_id, instance_ids, "object", m_use_VBOs && m_initialized); + m_volumes.load_object(model_obj, object_id, instance_ids, "object"); ++object_id; } @@ -5416,9 +5384,9 @@ void GLCanvas3D::_load_fff_shells() float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); if (!print->is_step_done(psWipeTower)) - depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; + depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1); m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), brim_spacing * 4.5f); + !print->is_step_done(psWipeTower), brim_spacing * 4.5f); } } } @@ -5436,8 +5404,8 @@ void GLCanvas3D::_load_sla_shells() const TriangleMesh &mesh, const float color[4], bool outside_printer_detection_enabled) { m_volumes.volumes.emplace_back(new GLVolume(color)); GLVolume& v = *m_volumes.volumes.back(); - v.indexed_vertex_array.load_mesh(mesh, m_use_VBOs); - v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; + v.indexed_vertex_array.load_mesh(mesh); + v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; v.composite_id.volume_id = volume_id; v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0)); v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation)); @@ -5464,7 +5432,7 @@ void GLCanvas3D::_load_sla_shells() GLVolume& v = *m_volumes.volumes[i]; // finalize volumes and sends geometry to gpu v.bounding_box = v.indexed_vertex_array.bounding_box(); - v.indexed_vertex_array.finalize_geometry(m_use_VBOs); + v.indexed_vertex_array.finalize_geometry(); // apply shift z v.set_sla_shift_z(shift_z); } @@ -5640,7 +5608,7 @@ bool GLCanvas3D::_is_any_volume_outside() const void GLCanvas3D::_resize_toolbars() const { Size cnv_size = get_canvas_size(); - float zoom = get_camera_zoom(); + float zoom = (float)m_camera.get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; #if ENABLE_RETINA_GL @@ -5690,19 +5658,16 @@ void GLCanvas3D::_resize_toolbars() const } } - if (m_view_toolbar != nullptr) - { #if ENABLE_RETINA_GL - m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); + m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); #else - m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); + m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); #endif /* __WXMSW__ */ - // places the toolbar on the bottom-left corner of the 3d scene - float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; - float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; - m_view_toolbar.set_position(top, left); - } + // places the toolbar on the bottom-left corner of the 3d scene + float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; + float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; + m_view_toolbar.set_position(top, left); } #endif // !ENABLE_SVG_ICONS diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index c891ed06ff..722eafeeff 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -452,7 +452,6 @@ private: // Screen is only refreshed from the OnIdle handler if it is dirty. bool m_dirty; bool m_initialized; - bool m_use_VBOs; bool m_apply_zoom_to_volumes_filter; mutable std::vector m_hover_volume_idxs; bool m_warning_texture_enabled; @@ -494,7 +493,7 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas; } const wxGLCanvas* get_wxglcanvas() const { return m_canvas; } - bool init(bool useVBOs); + bool init(); void post_event(wxEvent &&event); void set_as_dirty(); diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index a1430ef22b..b2a3161e89 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -192,7 +192,6 @@ GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info; GLCanvas3DManager::GLCanvas3DManager() : m_context(nullptr) , m_gl_initialized(false) - , m_use_VBOs(false) { } @@ -266,8 +265,6 @@ void GLCanvas3DManager::init_gl() if (!m_gl_initialized) { glewInit(); - const AppConfig* config = GUI::get_app_config(); - m_use_VBOs = s_gl_info.is_version_greater_or_equal_to(2, 0); m_gl_initialized = true; if (GLEW_EXT_texture_compression_s3tc) s_compressed_textures_supported = true; @@ -323,7 +320,7 @@ bool GLCanvas3DManager::init(GLCanvas3D& canvas) if (!m_gl_initialized) init_gl(); - return canvas.init(m_use_VBOs); + return canvas.init(); } void GLCanvas3DManager::detect_multisample(int* attribList) diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 7a600dcbdb..c0e0df6224 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -75,7 +75,6 @@ private: wxGLContext* m_context; static GLInfo s_gl_info; bool m_gl_initialized; - bool m_use_VBOs; static EMultisampleState s_multisample; static bool s_compressed_textures_supported; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index f8082ad7e0..a927eba9b1 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -188,7 +188,7 @@ bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const Bac return true; std::string path = resources_dir() + "/icons/"; - bool res = !icons_texture.filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture.filename, false); + bool res = !icons_texture.filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture.filename, false, true); if (res) m_icons_texture.metadata = icons_texture; #endif // ENABLE_SVG_ICONS diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index c7435636d3..c9005807de 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -49,7 +49,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) if (!m_icons_texture.metadata.filename.empty()) { - if (!m_icons_texture.texture.load_from_file(resources_dir() + "/icons/" + m_icons_texture.metadata.filename, false)) + if (!m_icons_texture.texture.load_from_file(resources_dir() + "/icons/" + m_icons_texture.metadata.filename, false, true)) { reset(); return false; @@ -1072,7 +1072,7 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio #if ENABLE_SVG_ICONS it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); #else - it->second->render_input_window(2.0f * m_overlay_border + icon_size * zoom, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); + it->second->render_input_window(2.0f * m_overlay_border + scaled_icons_size * zoom, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); #endif // ENABLE_SVG_ICONS } #if ENABLE_SVG_ICONS diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 97168ee045..ccc4176fd4 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -99,14 +99,14 @@ void Selection::set_volumes(GLVolumePtrs* volumes) update_valid(); } -bool Selection::init(bool useVBOs) +bool Selection::init() { - if (!m_arrow.init(useVBOs)) + if (!m_arrow.init()) return false; m_arrow.set_scale(5.0 * Vec3d::Ones()); - if (!m_curved_arrow.init(useVBOs)) + if (!m_curved_arrow.init()) return false; m_curved_arrow.set_scale(5.0 * Vec3d::Ones()); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 802f8d2847..482a8309f7 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -212,7 +212,7 @@ public: #endif // ENABLE_RENDER_SELECTION_CENTER void set_volumes(GLVolumePtrs* volumes); - bool init(bool useVBOs); + bool init(); bool is_enabled() const { return m_enabled; } void set_enabled(bool enable) { m_enabled = enable; } From 2356fe5a1329170dbb97e727388517458f65e400 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 1 Jul 2019 13:26:06 +0200 Subject: [PATCH 236/627] Added member BoundingBoxf3 m_bounding_box to GLIndexedVertexArray and removed member BoundingBoxf3 bounding_box from GLVolume --- src/slic3r/GUI/3DScene.cpp | 18 ++++------ src/slic3r/GUI/3DScene.hpp | 40 +++++++++-------------- src/slic3r/GUI/GLCanvas3D.cpp | 20 +++--------- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 6 ++-- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 4 +-- 5 files changed, 30 insertions(+), 58 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 417aaee4ff..c483f0ec33 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -328,11 +328,12 @@ bool GLVolume::is_left_handed() const const BoundingBoxf3& GLVolume::transformed_bounding_box() const { - assert(bounding_box.defined || bounding_box.min(0) >= bounding_box.max(0) || bounding_box.min(1) >= bounding_box.max(1) || bounding_box.min(2) >= bounding_box.max(2)); + const BoundingBoxf3& box = bounding_box(); + assert(box.defined || box.min(0) >= box.max(0) || box.min(1) >= box.max(1) || box.min(2) >= box.max(2)); if (m_transformed_bounding_box_dirty) { - m_transformed_bounding_box = bounding_box.transformed(world_matrix()); + m_transformed_bounding_box = box.transformed(world_matrix()); m_transformed_bounding_box_dirty = false; } @@ -350,9 +351,10 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d & { return (m_convex_hull && m_convex_hull->stl.stats.number_of_facets > 0) ? m_convex_hull->transformed_bounding_box(trafo) : - bounding_box.transformed(trafo); + bounding_box().transformed(trafo); } + void GLVolume::set_range(double min_z, double max_z) { this->qverts_range.first = 0; @@ -530,8 +532,6 @@ int GLVolumeCollection::load_object_volume( v.set_color_from_model_volume(model_volume); v.indexed_vertex_array.load_mesh(mesh); - // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). - v.bounding_box = v.indexed_vertex_array.bounding_box(); v.indexed_vertex_array.finalize_geometry(); v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); if (model_volume->is_model_part()) @@ -573,8 +573,6 @@ void GLVolumeCollection::load_object_auxiliary( this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); GLVolume& v = *this->volumes.back(); v.indexed_vertex_array.load_mesh(mesh); - // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). - v.bounding_box = v.indexed_vertex_array.bounding_box(); v.indexed_vertex_array.finalize_geometry(); v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); v.geometry_id = std::pair(timestamp, model_instance.id().id); @@ -648,8 +646,6 @@ int GLVolumeCollection::load_wipe_tower_preview( v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); - // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). - v.bounding_box = v.indexed_vertex_array.bounding_box(); v.indexed_vertex_array.finalize_geometry(); v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); v.geometry_id.first = 0; @@ -679,7 +675,7 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo { for (GLVolumeWithIdAndZ& volume : list) { - volume.second.second = volume.first->bounding_box.transformed(view_matrix * volume.first->world_matrix()).max(2); + volume.second.second = volume.first->bounding_box().transformed(view_matrix * volume.first->world_matrix()).max(2); } std::sort(list.begin(), list.end(), @@ -1777,7 +1773,6 @@ bool GLCurvedArrow::on_init() triangles.emplace_back(vertices_per_level, 2 * vertices_per_level + 1, vertices_per_level + 1); m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles)); - m_volume.bounding_box = m_volume.indexed_vertex_array.bounding_box(); m_volume.finalize_geometry(); return true; } @@ -1815,7 +1810,6 @@ bool GLBed::on_init_from_file(const std::string& filename) float color[4] = { 0.235f, 0.235f, 0.235f, 1.0f }; set_color(color, 4); - m_volume.bounding_box = m_volume.indexed_vertex_array.bounding_box(); m_volume.finalize_geometry(); return true; diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index bfdce2daf5..f3db004621 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -82,6 +82,7 @@ public: this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; this->triangle_indices = rhs.triangle_indices; this->quad_indices = rhs.quad_indices; + this->m_bounding_box = rhs.m_bounding_box; this->setup_sizes(); return *this; } @@ -94,6 +95,7 @@ public: this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); this->triangle_indices = std::move(rhs.triangle_indices); this->quad_indices = std::move(rhs.quad_indices); + this->m_bounding_box = std::move(rhs.m_bounding_box); this->setup_sizes(); return *this; } @@ -110,7 +112,7 @@ public: size_t quad_indices_size; // IDs of the Vertex Array Objects, into which the geometry has been loaded. - // Zero if the VBOs are not used. + // Zero if the VBOs are not sent to GPU yet. unsigned int vertices_and_normals_interleaved_VBO_id; unsigned int triangle_indices_VBO_id; unsigned int quad_indices_VBO_id; @@ -135,6 +137,8 @@ public: this->vertices_and_normals_interleaved.push_back(x); this->vertices_and_normals_interleaved.push_back(y); this->vertices_and_normals_interleaved.push_back(z); + + m_bounding_box.merge(Vec3f(x, y, z).cast()); }; inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { @@ -168,7 +172,7 @@ public: void finalize_geometry(); // Release the geometry data, release OpenGL VBOs. void release_geometry(); - // Render either using an immediate mode, or the VBOs. + void render() const; void render(const std::pair &tverts_range, const std::pair &qverts_range) const; @@ -182,6 +186,7 @@ public: this->vertices_and_normals_interleaved.clear(); this->triangle_indices.clear(); this->quad_indices.clear(); + this->m_bounding_box.reset(); this->setup_sizes(); } @@ -194,27 +199,11 @@ public: this->quad_indices.shrink_to_fit(); } - BoundingBoxf3 bounding_box() const { - BoundingBoxf3 bbox; - if (! this->vertices_and_normals_interleaved.empty()) { - bbox.defined = true; - bbox.min(0) = bbox.max(0) = this->vertices_and_normals_interleaved[3]; - bbox.min(1) = bbox.max(1) = this->vertices_and_normals_interleaved[4]; - bbox.min(2) = bbox.max(2) = this->vertices_and_normals_interleaved[5]; - for (size_t i = 9; i < this->vertices_and_normals_interleaved.size(); i += 6) { - const float *verts = this->vertices_and_normals_interleaved.data() + i; - bbox.min(0) = std::min(bbox.min(0), verts[0]); - bbox.min(1) = std::min(bbox.min(1), verts[1]); - bbox.min(2) = std::min(bbox.min(2), verts[2]); - bbox.max(0) = std::max(bbox.max(0), verts[0]); - bbox.max(1) = std::max(bbox.max(1), verts[1]); - bbox.max(2) = std::max(bbox.max(2), verts[2]); - } - } - return bbox; - } + const BoundingBoxf3& bounding_box() const { return m_bounding_box; } private: + BoundingBoxf3 m_bounding_box; + inline void setup_sizes() { vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); triangle_indices_size = this->triangle_indices.size(); @@ -262,8 +251,6 @@ private: mutable bool m_transformed_convex_hull_bounding_box_dirty; public: - // Bounding box of this volume, in unscaled coordinates. - BoundingBoxf3 bounding_box; // Color of the triangles / quads held by this volume. float color[4]; // Color used to render this volume. @@ -328,6 +315,9 @@ public: // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer. std::vector offsets; + // Bounding box of this volume, in unscaled coordinates. + const BoundingBoxf3& bounding_box() const { return this->indexed_vertex_array.bounding_box(); } + void set_render_color(float r, float g, float b, float a); void set_render_color(const float* rgba, unsigned int size); // Sets render color in dependence of current state @@ -536,7 +526,7 @@ public: bool init() { return on_init(); } bool init_from_file(const std::string& filename) { return on_init_from_file(filename); } - void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box.center()); } + void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box().center()); } void set_color(const float* color, unsigned int size); const Vec3d& get_offset() const; @@ -547,7 +537,7 @@ public: void set_scale(const Vec3d& scale); const std::string& get_filename() const { return m_filename; } - const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box; } + const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box(); } const BoundingBoxf3& get_transformed_bounding_box() const { return m_volume.transformed_bounding_box(); } void reset(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ab08aa0a77..5655390ecd 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3355,7 +3355,7 @@ arr::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"), m_config->opt_float("wipe_tower_y")); wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); - const BoundingBoxf3& bb = vol->bounding_box; + const BoundingBoxf3& bb = vol->bounding_box(); wti.bb_size = Vec2d(bb.size()(0), bb.size()(1)); break; } @@ -4474,7 +4474,6 @@ void GLCanvas3D::_load_print_toolpaths() _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), volume); } - volume.bounding_box = volume.indexed_vertex_array.bounding_box(); volume.indexed_vertex_array.finalize_geometry(); } @@ -4640,9 +4639,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); // Copy the content back to the old GLVolume. vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); - // Clear the buffers, but keep them pre-allocated. + // Clear the buffers, but keep them pre-allocated. vol_new.indexed_vertex_array.clear(); // Just make sure that clear did not clear the reserved memory. vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); @@ -4650,8 +4647,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c } } for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); - vol->indexed_vertex_array.shrink_to_fit(); + vol->indexed_vertex_array.shrink_to_fit(); } }); @@ -4812,8 +4808,6 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); // Copy the content back to the old GLVolume. vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); // Clear the buffers, but keep them pre-allocated. vol_new.indexed_vertex_array.clear(); // Just make sure that clear did not clear the reserved memory. @@ -4821,7 +4815,6 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ } } for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); vol->indexed_vertex_array.shrink_to_fit(); } }); @@ -5017,7 +5010,6 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) { GLVolume* volume = m_volumes.volumes[i]; - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); volume->indexed_vertex_array.finalize_geometry(); } } @@ -5072,7 +5064,6 @@ void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) { GLVolume* volume = m_volumes.volumes[i]; - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); volume->indexed_vertex_array.finalize_geometry(); } } @@ -5306,7 +5297,6 @@ void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data) } // finalize volumes and sends geometry to gpu - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); volume->indexed_vertex_array.finalize_geometry(); } } @@ -5337,7 +5327,6 @@ void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data) } // finalize volumes and sends geometry to gpu - volume->bounding_box = volume->indexed_vertex_array.bounding_box(); volume->indexed_vertex_array.finalize_geometry(); } } @@ -5431,7 +5420,6 @@ void GLCanvas3D::_load_sla_shells() for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { GLVolume& v = *m_volumes.volumes[i]; // finalize volumes and sends geometry to gpu - v.bounding_box = v.indexed_vertex_array.bounding_box(); v.indexed_vertex_array.finalize_geometry(); // apply shift z v.set_sla_shift_z(shift_z); @@ -5523,7 +5511,7 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state() for (GLVolume* volume : m_volumes.volumes) { - volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box) : false; + volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box()) : false; } } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 372cd79efb..6affe6d3ab 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -441,7 +441,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); m_new_scale = volume->get_volume_scaling_factor() * 100.; - m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box.size()); + m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box().size()); m_new_enabled = true; } else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) @@ -721,8 +721,8 @@ void ObjectManipulation::change_size_value(int axis, double value) Vec3d ref_size = m_cache.size; if (selection.is_single_volume() || selection.is_single_modifier()) - ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box.size(); - else if (selection.is_single_full_instance()) + ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box().size(); + else if (selection.is_single_full_instance()) ref_size = m_world_coordinates ? selection.get_unscaled_instance_bounding_box().size() : (*wxGetApp().model_objects())[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index e8027c871d..ca5383b0d5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -136,7 +136,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const for (unsigned int idx : idxs) { const GLVolume* vol = selection.get_volume(idx); - m_box.merge(vol->bounding_box.transformed(vol->get_volume_transformation().get_matrix())); + m_box.merge(vol->bounding_box().transformed(vol->get_volume_transformation().get_matrix())); } // gets transform from first selected volume @@ -151,7 +151,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const else if (single_volume) { const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); - m_box = v->bounding_box; + m_box = v->bounding_box(); m_transform = v->world_matrix(); angles = Geometry::extract_euler_angles(m_transform); // consider rotation+mirror only components of the transform for offsets From a934c2e79c64273db45e7bc22835eb147488d81b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 1 Jul 2019 14:56:28 +0200 Subject: [PATCH 237/627] Changed a behavior logic of a value reverting for presets, derived from default. LOCKs and ARROWs work now in a same way like for presets, derived from system presets. --- src/slic3r/GUI/Preset.cpp | 20 ++++++++++++--- src/slic3r/GUI/Tab.cpp | 52 +++++++++++++++------------------------ src/slic3r/GUI/Tab.hpp | 6 +++++ 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 7192d485ca..b8add9fc70 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -824,11 +824,25 @@ const Preset* PresetCollection::get_selected_preset_parent() const if (this->get_selected_idx() == -1) // This preset collection has no preset activated yet. Only the get_edited_preset() is valid. return nullptr; - const std::string &inherits = this->get_edited_preset().inherits(); +// const std::string &inherits = this->get_edited_preset().inherits(); +// if (inherits.empty()) +// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; + + std::string inherits = this->get_edited_preset().inherits(); if (inherits.empty()) - return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; + { + if (this->get_selected_preset().is_system || this->get_selected_preset().is_default) + return &this->get_selected_preset(); + if (this->get_selected_preset().is_external) + return nullptr; + + inherits = m_type != Preset::Type::TYPE_PRINTER ? "- default -" : + this->get_edited_preset().printer_technology() == ptFFF ? + "- default FFF -" : "- default SLA -" ; + } + const Preset* preset = this->find_preset(inherits, false); - return (preset == nullptr || preset->is_default || preset->is_external) ? nullptr : preset; + return (preset == nullptr/* || preset->is_default*/ || preset->is_external) ? nullptr : preset; } const Preset* PresetCollection::get_preset_parent(const Preset& child) const diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6e8b8a471e..cd43d95c43 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -423,7 +423,7 @@ void Tab::update_changed_ui() const ScalableBitmap *sys_icon = &m_bmp_value_lock; const ScalableBitmap *icon = &m_bmp_value_revert; - const wxColour *color = &m_sys_label_clr; + const wxColour *color = m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr; const wxString *sys_tt = &m_tt_value_lock; const wxString *tt = &m_tt_value_revert; @@ -590,7 +590,7 @@ void Tab::update_changed_tree_ui() } } - const wxColor *clr = sys_page ? &m_sys_label_clr : + const wxColor *clr = sys_page ? (m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr) : modified_page ? &m_modified_label_clr : &m_default_text_clr; @@ -2584,11 +2584,14 @@ void Tab::load_current_preset() // Reload preset pages with the new configuration values. reload_config(); - m_bmp_non_system = m_presets->get_selected_preset_parent() ? &m_bmp_value_unlock : &m_bmp_white_bullet; - m_ttg_non_system = m_presets->get_selected_preset_parent() ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; - m_tt_non_system = m_presets->get_selected_preset_parent() ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + const Preset* selected_preset_parent = m_presets->get_selected_preset_parent(); + m_is_default_preset = selected_preset_parent != nullptr && selected_preset_parent->is_default; - m_undo_to_sys_btn->Enable(!preset.is_default); + m_bmp_non_system = selected_preset_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; + m_ttg_non_system = selected_preset_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = selected_preset_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + +// m_undo_to_sys_btn->Enable(!preset.is_default); #if 0 // use CallAfter because some field triggers schedule on_change calls using CallAfter, @@ -3174,18 +3177,18 @@ void Tab::fill_icon_descriptions() { m_icon_descriptions.emplace_back(&m_bmp_value_lock, L("LOCKED LOCK"), // TRN Description for "LOCKED LOCK" - L("indicates that the settings are the same as the system values for the current option group")); + L("indicates that the settings are the same as the system (or default) values for the current option group")); m_icon_descriptions.emplace_back(&m_bmp_value_unlock, L("UNLOCKED LOCK"), // TRN Description for "UNLOCKED LOCK" - L("indicates that some settings were changed and are not equal to the system values for " + L("indicates that some settings were changed and are not equal to the system (or default) values for " "the current option group.\n" "Click the UNLOCKED LOCK icon to reset all settings for current option group to " - "the system values.")); + "the system (or default) values.")); m_icon_descriptions.emplace_back(&m_bmp_white_bullet, L("WHITE BULLET"), // TRN Description for "WHITE BULLET" - L("for the left button: \tindicates a non-system preset,\n" + L("for the left button: \tindicates a non-system (or non-default) preset,\n" "for the right button: \tindicates that the settings hasn't been modified.")); m_icon_descriptions.emplace_back(&m_bmp_value_revert, L("BACK ARROW"), @@ -3198,29 +3201,14 @@ void Tab::fill_icon_descriptions() void Tab::set_tooltips_text() { -// m_undo_to_sys_btn->SetToolTip(_(L( "LOCKED LOCK icon indicates that the settings are the same as the system values " -// "for the current option group.\n" -// "UNLOCKED LOCK icon indicates that some settings were changed and are not equal " -// "to the system values for the current option group.\n" -// "WHITE BULLET icon indicates a non system preset.\n\n" -// "Click the UNLOCKED LOCK icon to reset all settings for current option group to " -// "the system values."))); -// -// m_undo_btn->SetToolTip(_(L( "WHITE BULLET icon indicates that the settings are the same as in the last saved" -// "preset for the current option group.\n" -// "BACK ARROW icon indicates that the settings were changed and are not equal to " -// "the last saved preset for the current option group.\n\n" -// "Click the BACK ARROW icon to reset all settings for the current option group to " -// "the last saved preset."))); - // --- Tooltip text for reset buttons (for whole options group) // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - m_ttg_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system values " + m_ttg_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system (or default) values " "for the current option group")); m_ttg_value_unlock = _(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " - "to the system values for the current option group.\n" - "Click to reset all settings for current option group to the system values.")); - m_ttg_white_bullet_ns = _(L("WHITE BULLET icon indicates a non system preset.")); + "to the system (or default) values for the current option group.\n" + "Click to reset all settings for current option group to the system (or default) values.")); + m_ttg_white_bullet_ns = _(L("WHITE BULLET icon indicates a non system (or non default) preset.")); m_ttg_non_system = &m_ttg_white_bullet_ns; // Text to be shown on the "Undo user changes" button next to each input field. m_ttg_white_bullet = _(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " @@ -3231,10 +3219,10 @@ void Tab::set_tooltips_text() // --- Tooltip text for reset buttons (for each option in group) // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the value is the same as the system value.")); + m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the value is the same as the system (or default) value.")); m_tt_value_unlock = _(L("UNLOCKED LOCK icon indicates that the value was changed and is not equal " - "to the system value.\n" - "Click to reset current value to the system value.")); + "to the system (or default) value.\n" + "Click to reset current value to the system (or default) value.")); // m_tt_white_bullet_ns= _(L("WHITE BULLET icon indicates a non system preset.")); m_tt_non_system = &m_ttg_white_bullet_ns; // Text to be shown on the "Undo user changes" button next to each input field. diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 6bbe15f7f4..73b6bb08d8 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -142,6 +142,12 @@ protected: PresetDependencies m_compatible_printers; PresetDependencies m_compatible_prints; + /* Indicates, that default preset or preset inherited from default is selected + * This value is used for a options color updating + * (use green color only for options, which values are equal to system values) + */ + bool m_is_default_preset {false}; + ScalableButton* m_undo_btn; ScalableButton* m_undo_to_sys_btn; ScalableButton* m_question_btn; From e5e7496cea88cd18afa9a83a95e1ed596c5a69c5 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 1 Jul 2019 16:56:38 +0200 Subject: [PATCH 238/627] Some changes for options tooltips --- src/slic3r/GUI/Field.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 7f42db4d79..e84e9637ff 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -113,11 +113,19 @@ wxString Field::get_tooltip_text(const wxString& default_string) wxString tooltip_text(""); wxString tooltip = _(m_opt.tooltip); edit_tooltip(tooltip); + + std::string opt_id = m_opt_id; + auto hash_pos = opt_id.find("#"); + if (hash_pos != std::string::npos) { + opt_id.replace(hash_pos, 1,"["); + opt_id += "]"; + } + if (tooltip.length() > 0) tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " + - (boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string + - (boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") + - _(L("parameter name")) + "\t: " + m_opt_id; + (boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string + + (boost::iends_with(opt_id, "_gcode") ? "" : "\n") + + _(L("parameter name")) + "\t: " + opt_id; return tooltip_text; } From 253ec07cb28ccefd2bd304ca515d9001b28d95c3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 1 Jul 2019 18:22:07 +0200 Subject: [PATCH 239/627] Still WIP --- .clang-format | 2 +- src/libnest2d/include/libnest2d/libnest2d.hpp | 2 +- .../{ModelArrange.cpp => Arrange.cpp} | 171 +++++++----------- .../{ModelArrange.hpp => Arrange.hpp} | 31 ++-- src/libslic3r/CMakeLists.txt | 4 +- src/libslic3r/Model.cpp | 89 +++++---- src/libslic3r/Model.hpp | 4 +- src/slic3r/GUI/GLCanvas3D.hpp | 4 +- src/slic3r/GUI/Plater.cpp | 145 ++++++++++----- 9 files changed, 242 insertions(+), 210 deletions(-) rename src/libslic3r/{ModelArrange.cpp => Arrange.cpp} (85%) rename src/libslic3r/{ModelArrange.hpp => Arrange.hpp} (82%) diff --git a/.clang-format b/.clang-format index 9cbcf26f26..e0a41be5fc 100644 --- a/.clang-format +++ b/.clang-format @@ -19,7 +19,7 @@ AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false -BinPackArguments: false +BinPackArguments: true BinPackParameters: false BraceWrapping: AfterClass: true diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 4831c1fb67..8d3a2272dd 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -901,7 +901,7 @@ private: selector_.template packItems( from, to, bin_, pconfig_); - if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { + if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { item.removeOffset(); }); diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/Arrange.cpp similarity index 85% rename from src/libslic3r/ModelArrange.cpp rename to src/libslic3r/Arrange.cpp index 6d9c6007f8..3abe495319 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -1,4 +1,4 @@ -#include "ModelArrange.hpp" +#include "Arrange.hpp" #include "Geometry.hpp" #include "SVG.hpp" #include "MTUtils.hpp" @@ -46,7 +46,14 @@ template struct NfpImpl namespace Slic3r { -namespace arr { +template> +inline SLIC3R_CONSTEXPR EigenVec unscaled( + const ClipperLib::IntPoint &v) SLIC3R_NOEXCEPT +{ + return EigenVec{unscaled(v.X), unscaled(v.Y)}; +} + +namespace arrangement { using namespace libnest2d; namespace clppr = ClipperLib; @@ -328,7 +335,6 @@ void fillConfig(PConf& pcfg) { template class AutoArranger {}; - // A class encapsulating the libnest2d Nester class and extending it with other // management and spatial index structures for acceleration. template @@ -360,7 +366,7 @@ public: std::function progressind, std::function stopcond): m_pck(bin, dist), m_bin_area(sl::area(bin)), - m_norm(std::sqrt(sl::area(bin))) + m_norm(std::sqrt(m_bin_area)) { fillConfig(m_pconf); @@ -391,9 +397,9 @@ public: m_smallsrtree.insert({itm.boundingBox(), idx}); } }; - - m_pck.progressIndicator(progressind); - m_pck.stopCondition(stopcond); + + if (progressind) m_pck.progressIndicator(progressind); + if (stopcond) m_pck.stopCondition(stopcond); } template inline PackGroup operator()(Args&&...args) { @@ -545,35 +551,6 @@ public: } }; -// Specialization with no bin. In this case the arranger should just arrange -// all objects into a minimum sized pile but it is not limited by a bin. A -// consequence is that only one pile should be created. -template<> class AutoArranger: public _ArrBase { -public: - - AutoArranger(bool, Distance dist, std::function progressind, - std::function stopcond): - _ArrBase(Box(0, 0), dist, progressind, stopcond) - { - this->m_pconf.object_function = [this] (const Item &item) { - - auto result = objfunc({0, 0}, - m_merged_pile, - m_pilebb, - m_items, - item, - 0, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - return std::get<0>(result); - }; - - this->m_pck.configure(m_pconf); - } -}; - // Get the type of bed geometry from a simple vector of points. BedShapeHint bedShape(const Polyline &bed) { BedShapeHint ret; @@ -653,19 +630,6 @@ BedShapeHint bedShape(const Polyline &bed) { return ret; } -//static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; - -//template -//PackGroup _arrange(std::vector & items, -// const BinT & bin, -// coord_t minobjd, -// std::function prind, -// std::function stopfn) -//{ -// AutoArranger arranger{bin, minobjd, prind, stopfn}; -// return arranger(items.begin(), items.end()); -//} - template PackGroup _arrange(std::vector & shapes, const PackGroup & preshapes, @@ -673,45 +637,35 @@ PackGroup _arrange(std::vector & shapes, coord_t minobjd, std::function prind, std::function stopfn) -{ - -// auto binbb = sl::boundingBox(bin); - +{ AutoArranger arranger{bin, minobjd, prind, stopfn}; - if(!preshapes.front().empty()) { // If there is something on the plate + // If there is something on the plate + if(!preshapes.empty() && !preshapes.front().empty()) { arranger.preload(preshapes); + auto binbb = sl::boundingBox(bin); // Try to put the first item to the center, as the arranger will not // do this for us. -// auto shptrit = minstances.begin(); -// for(auto shit = shapes.begin(); shit != shapes.end(); ++shit, ++shptrit) -// { -// // Try to place items to the center -// Item& itm = *shit; -// auto ibb = itm.boundingBox(); -// auto d = binbb.center() - ibb.center(); -// itm.translate(d); -// if(!arranger.is_colliding(itm)) { -// arranger.preload({{itm}}); + for (auto it = shapes.begin(); it != shapes.end(); ++it) { + Item &itm = *it; + auto ibb = itm.boundingBox(); + auto d = binbb.center() - ibb.center(); + itm.translate(d); + + if (!arranger.is_colliding(itm)) { + arranger.preload({{itm}}); -// auto offset = itm.translation(); -// Radians rot = itm.rotation(); -// ModelInstance *minst = *shptrit; - -// Vec3d foffset(unscaled(offset.X), -// unscaled(offset.Y), -// minst->get_offset()(Z)); - -// // write the transformation data into the model instance -// minst->set_rotation(Z, rot); -// minst->set_offset(foffset); + // Write the transformation data into the item. The callback + // was set on the instantiation of Item and calls the + // Arrangeable interface. + it->callApplyFunction(0); -// shit = shapes.erase(shit); -// shptrit = minstances.erase(shptrit); -// break; -// } -// } + // Remove this item, as it is arranged now + it = shapes.erase(it); + break; + } + } } return arranger(shapes.begin(), shapes.end()); @@ -724,8 +678,8 @@ inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) //// The final client function to arrange the Model. A progress indicator and //// a stop predicate can be also be passed to control the process. -bool arrange(Arrangeables & arrangables, - const Arrangeables & excludes, +bool arrange(ArrangeablePtrs & arrangables, + const ArrangeablePtrs & excludes, coord_t min_obj_distance, const BedShapeHint & bedhint, std::function progressind, @@ -741,29 +695,29 @@ bool arrange(Arrangeables & arrangables, PackGroup preshapes{ {} }; // pack group with one initial bin for preloading auto process_arrangeable = - [](const Arrangeable * arrangeable, - std::vector & outp, - std::function applyfn) + [](const Arrangeable * arrangeable, + std::vector & outp, + std::function applyfn) { - assert(arrangeable); + assert(arrangeable); - auto arrangeitem = arrangeable->get_arrange_polygon(); + auto arrangeitem = arrangeable->get_arrange_polygon(); - Polygon & p = std::get<0>(arrangeitem); - const Vec2crd &offs = std::get<1>(arrangeitem); - double rotation = std::get<2>(arrangeitem); + Polygon & p = std::get<0>(arrangeitem); + const Vec2crd &offs = std::get<1>(arrangeitem); + double rotation = std::get<2>(arrangeitem); - if (p.is_counter_clockwise()) p.reverse(); + if (p.is_counter_clockwise()) p.reverse(); - clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); + clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); - auto firstp = clpath.Contour.front(); - clpath.Contour.emplace_back(firstp); + auto firstp = clpath.Contour.front(); + clpath.Contour.emplace_back(firstp); - outp.emplace_back(applyfn, std::move(clpath)); - outp.front().rotation(rotation); - outp.front().translation({offs.x(), offs.y()}); - }; + outp.emplace_back(applyfn, std::move(clpath)); + outp.front().rotation(rotation); + outp.front().translation({offs.x(), offs.y()}); + }; for (Arrangeable *arrangeable : arrangables) { process_arrangeable( @@ -774,8 +728,7 @@ bool arrange(Arrangeables & arrangables, clppr::cInt stride = binidx * stride_padding(binwidth); clppr::IntPoint offs = itm.translation(); - arrangeable->apply_arrange_result({unscaled(offs.X + - stride), + arrangeable->apply_arrange_result({unscaled(offs.X + stride), unscaled(offs.Y)}, itm.rotation()); }); @@ -797,7 +750,6 @@ bool arrange(Arrangeables & arrangables, // Create the arranger for the box shaped bed BoundingBox bbb = bedhint.shape.box; bbb.min -= Point{md, md}, bbb.max += Point{md, md}; - Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; binwidth = coord_t(binbb.width()); @@ -808,6 +760,7 @@ bool arrange(Arrangeables & arrangables, auto c = bedhint.shape.circ; auto cc = to_lnCircle(c); binwidth = scaled(c.radius()); + _arrange(items, preshapes, cc, min_obj_distance, progressind, cfn); break; } @@ -816,11 +769,21 @@ bool arrange(Arrangeables & arrangables, auto irrbed = sl::create(std::move(ctour)); BoundingBox polybb(bedhint.shape.polygon); binwidth = (polybb.max(X) - polybb.min(X)); + _arrange(items, preshapes, irrbed, min_obj_distance, progressind, cfn); break; } - case BedShapeType::WHO_KNOWS: { - _arrange(items, preshapes, false, min_obj_distance, progressind, cfn); + case BedShapeType::INFINITE: { + // const InfiniteBed& nobin = bedhint.shape.infinite; + //Box infbb{{nobin.center.x(), nobin.center.y()}}; + Box infbb; + + _arrange(items, preshapes, infbb, min_obj_distance, progressind, cfn); + break; + } + case BedShapeType::UNKNOWN: { + // We know nothing about the bed, let it be infinite and zero centered + _arrange(items, preshapes, Box{}, min_obj_distance, progressind, cfn); break; } }; @@ -831,7 +794,7 @@ bool arrange(Arrangeables & arrangables, } /// Arrange, without the fixed items (excludes) -bool arrange(Arrangeables & inp, +bool arrange(ArrangeablePtrs & inp, coord_t min_d, const BedShapeHint & bedhint, std::function prfn, diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/Arrange.hpp similarity index 82% rename from src/libslic3r/ModelArrange.hpp rename to src/libslic3r/Arrange.hpp index 306081eb8a..87514a6003 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -6,7 +6,7 @@ namespace Slic3r { -namespace arr { +namespace arrangement { /// A geometry abstraction for a circular print bed. Similarly to BoundingBox. class CircleBed { @@ -22,21 +22,26 @@ public: inline operator bool() { return !std::isnan(radius_); } }; +/// Representing an unbounded bin +struct InfiniteBed { Point center; }; + /// Types of print bed shapes. enum class BedShapeType { BOX, CIRCLE, IRREGULAR, - WHO_KNOWS + INFINITE, + UNKNOWN }; /// Info about the print bed for the arrange() function. struct BedShapeHint { - BedShapeType type = BedShapeType::WHO_KNOWS; + BedShapeType type = BedShapeType::INFINITE; /*union*/ struct { // I know but who cares... TODO: use variant from cpp17? - CircleBed circ; + CircleBed circ; BoundingBox box; - Polyline polygon; + Polyline polygon; + InfiniteBed infinite; } shape; }; @@ -59,7 +64,7 @@ public: virtual std::tuple get_arrange_polygon() const = 0; }; -using Arrangeables = std::vector; +using ArrangeablePtrs = std::vector; /** * \brief Arranges the model objects on the screen. @@ -92,20 +97,20 @@ using Arrangeables = std::vector; * * \param stopcondition A predicate returning true if abort is needed. */ -bool arrange(Arrangeables &items, +bool arrange(ArrangeablePtrs &items, coord_t min_obj_distance, const BedShapeHint& bedhint, - std::function progressind, - std::function stopcondition); + std::function progressind = nullptr, + std::function stopcondition = nullptr); /// Same as the previous, only that it takes unmovable items as an /// additional argument. -bool arrange(Arrangeables &items, - const Arrangeables &excludes, +bool arrange(ArrangeablePtrs &items, + const ArrangeablePtrs &excludes, coord_t min_obj_distance, const BedShapeHint& bedhint, - std::function progressind, - std::function stopcondition); + std::function progressind = nullptr, + std::function stopcondition = nullptr); } // arr } // Slic3r diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index dc52257aa2..736290489a 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -107,8 +107,8 @@ add_library(libslic3r STATIC Line.hpp Model.cpp Model.hpp - ModelArrange.hpp - ModelArrange.cpp + Arrange.hpp + Arrange.cpp MotionPlanner.cpp MotionPlanner.hpp MultiPoint.cpp diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 18f3f2f5ef..fd48dcec48 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -375,31 +375,46 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) { // get the (transformed) size of each instance so that we take // into account their different transformations when packing - Pointfs instance_sizes; - Pointfs instance_centers; - for (const ModelObject *o : this->objects) - for (size_t i = 0; i < o->instances.size(); ++ i) { - // an accurate snug bounding box around the transformed mesh. - BoundingBoxf3 bbox(o->instance_bounding_box(i, true)); - instance_sizes.emplace_back(to_2d(bbox.size())); - instance_centers.emplace_back(to_2d(bbox.center())); - } +// Pointfs instance_sizes; +// Pointfs instance_centers; +// for (const ModelObject *o : this->objects) +// for (size_t i = 0; i < o->instances.size(); ++ i) { +// // an accurate snug bounding box around the transformed mesh. +// BoundingBoxf3 bbox(o->instance_bounding_box(i, true)); +// instance_sizes.emplace_back(to_2d(bbox.size())); +// instance_centers.emplace_back(to_2d(bbox.center())); +// } - Pointfs positions; - if (! _arrange(instance_sizes, dist, bb, positions)) - return false; +// Pointfs positions; +// if (! _arrange(instance_sizes, dist, bb, positions)) +// return false; - size_t idx = 0; - for (ModelObject *o : this->objects) { - for (ModelInstance *i : o->instances) { - Vec2d offset_xy = positions[idx] - instance_centers[idx]; - i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z))); - ++idx; - } - o->invalidate_bounding_box(); +// size_t idx = 0; +// for (ModelObject *o : this->objects) { +// for (ModelInstance *i : o->instances) { +// Vec2d offset_xy = positions[idx] - instance_centers[idx]; +// i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z))); +// ++idx; +// } +// o->invalidate_bounding_box(); +// } + size_t count = 0; + for (auto obj : objects) count += obj->instances.size(); + + arrangement::ArrangeablePtrs input; + input.reserve(count); + for (ModelObject *mo : objects) + for (ModelInstance *minst : mo->instances) + input.emplace_back(minst); + + arrangement::BedShapeHint bedhint; + + if (bb) { + bedhint.type = arrangement::BedShapeType::BOX; + bedhint.shape.box = BoundingBox(scaled(bb->min), scaled(bb->max)); } - - return true; + + return arrangement::arrange(input, scaled(dist), bedhint); } // Duplicate the entire model preserving instance relative positions. @@ -1804,31 +1819,27 @@ void ModelInstance::transform_polygon(Polygon* polygon) const std::tuple ModelInstance::get_arrange_polygon() const { static const double SIMPLIFY_TOLERANCE_MM = 0.1; - - assert(m_inst); - - Vec3d rotation = get_rotation(); - rotation.z() = 0.; - Transform3d trafo_instance = Geometry:: - assemble_transform(Vec3d::Zero(), - rotation, - get_scaling_factor(), - get_mirror()); - + + Vec3d rotation = get_rotation(); + rotation.z() = 0.; + Transform3d trafo_instance = + Geometry::assemble_transform(Vec3d::Zero(), rotation, + get_scaling_factor(), get_mirror()); + Polygon p = get_object()->convex_hull_2d(trafo_instance); - + assert(!p.points.empty()); - + // this may happen for malformed models, see: // https://github.com/prusa3d/PrusaSlicer/issues/2209 if (p.points.empty()) return {}; - + Polygons pp{p}; pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); if (!pp.empty()) p = pp.front(); - - return std::make_tuple(p, Vec2crd{scaled(get_offset(X)), - scaled(get_offset(Y))}, + + return std::make_tuple(p, + Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}, get_rotation(Z)); } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index c022808271..51759640ce 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -7,7 +7,7 @@ #include "Point.hpp" #include "TriangleMesh.hpp" #include "Slicing.hpp" -#include "ModelArrange.hpp" +#include "Arrange.hpp" #include #include @@ -491,7 +491,7 @@ private: // A single instance of a ModelObject. // Knows the affine transformation of an object. -class ModelInstance : public ModelBase, public arr::Arrangeable +class ModelInstance : public ModelBase, public arrangement::Arrangeable { public: enum EPrintVolumeState : unsigned char diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 2d3c3b27f9..524e0c8837 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -4,7 +4,7 @@ #include #include -#include "libslic3r/ModelArrange.hpp" +#include "libslic3r/Arrange.hpp" #include "3DScene.hpp" #include "GLToolbar.hpp" #include "Event.hpp" @@ -612,7 +612,7 @@ public: int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } - class WipeTowerInfo: public arr::Arrangeable { + class WipeTowerInfo: public arrangement::Arrangeable { Vec2d m_pos = {std::nan(""), std::nan("")}; Vec2d m_bb_size; double m_rotation; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index aba8ae71d9..08c70dbe06 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -32,7 +32,6 @@ #include "libslic3r/Format/3mf.hpp" #include "libslic3r/GCode/PreviewData.hpp" #include "libslic3r/Model.hpp" -#include "libslic3r/ModelArrange.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/PrintConfig.hpp" @@ -1218,6 +1217,28 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi return true; } +namespace { +arrangement::ArrangeablePtrs get_arrange_input(Model &model, const Selection &sel) { + auto selmap = sel.get_content(); + + size_t count = 0; + for (auto obj : model.objects) count += obj->instances.size(); + + arrangement::ArrangeablePtrs ret; ret.reserve(count); + + if (selmap.empty()) + for (ModelObject *mo : model.objects) + for (ModelInstance *minst : mo->instances) + ret.emplace_back(minst); + else + for (auto &s : selmap) + for (auto &instid : s.second) + ret.emplace_back(model.objects[s.first]->instances[instid]); + + return ret; +} +} + // Plater / private struct Plater::priv { @@ -1425,50 +1446,91 @@ struct Plater::priv class ArrangeJob : public Job { - int m_count = 0; GLCanvas3D::WipeTowerInfo m_wti; + arrangement::ArrangeablePtrs m_selected, m_unselected; + + static std::array collect( + Model &model, const Selection &sel) + { + auto selmap = sel.get_content(); + + size_t count = 0; + for (auto obj : model.objects) count += obj->instances.size(); + + arrangement::ArrangeablePtrs selected, unselected; + selected.reserve(count + 1 /* for optional wti */); + unselected.reserve(count + 1 /* for optional wti */); + + for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { + auto oit = selmap.find(int(oidx)); + + if (oit != selmap.end()) { + auto &iids = oit->second; + + for (size_t iidx = 0; + iidx < model.objects[oidx]->instances.size(); + ++iidx) + { + auto instit = iids.find(iidx); + ModelInstance *inst = model.objects[oidx] + ->instances[iidx]; + instit == iids.end() ? + unselected.emplace_back(inst) : + selected.emplace_back(inst); + } + } else // object not selected, all instances are unselected + for (auto inst : model.objects[oidx]->instances) + unselected.emplace_back(inst); + } + + if (selected.empty()) selected.swap(unselected); + + return {selected, unselected}; + } + protected: void prepare() override { m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info(); - m_count = bool(m_wti); - for (auto obj : plater().model.objects) - m_count += int(obj->instances.size()); + const Selection& sel = plater().get_selection(); + auto arrinput = collect(plater().model, sel); + m_selected.swap(arrinput[0]); + m_unselected.swap(arrinput[1]); + + if (m_wti) + sel.is_wipe_tower() ? + m_selected.emplace_back(&m_wti) : + m_unselected.emplace_back(&m_wti); } public: - //using Job::Job; - ArrangeJob(priv * pltr): Job(pltr) {} - int status_range() const override { return m_count; } - void set_count(int c) { m_count = c; } + using Job::Job; + int status_range() const override + { + return int(m_selected.size()); + } void process() override; - } arrange_job/*{m_plater}*/; + } arrange_job{m_plater}; class RotoptimizeJob : public Job { public: - //using Job::Job; - RotoptimizeJob(priv * pltr): Job(pltr) {} + using Job::Job; void process() override; - } rotoptimize_job/*{m_plater}*/; + } 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> m_jobs/*{arrange_job, - rotoptimize_job}*/; + std::vector> m_jobs{arrange_job, + rotoptimize_job}; public: - ExclusiveJobGroup(priv *_plater) - : m_plater(_plater) - , arrange_job(m_plater) - , rotoptimize_job(m_plater) - , m_jobs({arrange_job, rotoptimize_job}) - {} + ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {} void start(Jobs jid) { m_plater->background_process.stop(); @@ -1528,7 +1590,7 @@ struct Plater::priv std::string get_config(const std::string &key) const; BoundingBoxf bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const; - arr::BedShapeHint get_bed_shape_hint() const; + arrangement::BedShapeHint get_bed_shape_hint() const; std::vector load_files(const std::vector& input_files, bool load_model, bool load_config); std::vector load_model_objects(const ModelObjectPtrs &model_objects); wxString get_export_file(GUI::FileType file_type); @@ -2404,8 +2466,8 @@ void Plater::priv::sla_optimize_rotation() { m_ui_jobs.start(Jobs::Rotoptimize); } -arr::BedShapeHint Plater::priv::get_bed_shape_hint() const { - arr::BedShapeHint bedshape; +arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { + arrangement::BedShapeHint bedshape; const auto *bed_shape_opt = config->opt("bed_shape"); assert(bed_shape_opt); @@ -2414,7 +2476,7 @@ arr::BedShapeHint Plater::priv::get_bed_shape_hint() const { auto &bedpoints = bed_shape_opt->values; Polyline bedpoly; bedpoly.points.reserve(bedpoints.size()); for (auto &v : bedpoints) bedpoly.append(scaled(v)); - bedshape = arr::bedShape(bedpoly); + bedshape = arrangement::bedShape(bedpoly); } return bedshape; @@ -2423,15 +2485,6 @@ arr::BedShapeHint Plater::priv::get_bed_shape_hint() const { void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { static const auto arrangestr = _(L("Arranging")); - // Collect the model instances and place them into the input vector - arr::Arrangeables arrangeinput; arrangeinput.reserve(m_count); - for(ModelObject *mo : plater().model.objects) - for(ModelInstance *minst : mo->instances) - arrangeinput.emplace_back(minst); - - // Place back the wipe tower if that's available. - if (m_wti) arrangeinput.emplace_back(&m_wti); - // 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); @@ -2440,25 +2493,25 @@ void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { } coord_t min_obj_distance = scaled(dist); - - arr::BedShapeHint bedshape = plater().get_bed_shape_hint(); + auto count = unsigned(m_selected.size()); + arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); try { - arr::arrange(arrangeinput, - min_obj_distance, - bedshape, - [this](unsigned st) { - if (st > 0) - update_status(m_count - int(st), arrangestr); - }, - [this]() { return was_canceled(); }); + arrangement::arrange(m_selected, m_unselected, min_obj_distance, + bedshape, + [this, count](unsigned st) { + if (st > 0) // will not finalize after last one + update_status(count - st, arrangestr); + }, + [this]() { return was_canceled(); }); } catch (std::exception & /*e*/) { GUI::show_error(plater().q, _(L("Could not arrange model objects! " "Some geometries may be invalid."))); } - update_status(m_count, + // finalize just here. + update_status(int(count), was_canceled() ? _(L("Arranging canceled.")) : _(L("Arranging done."))); } @@ -2466,7 +2519,7 @@ void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { void find_new_position(const Model & model, ModelInstancePtrs instances, coord_t min_d, - const arr::BedShapeHint &bedhint) + const arrangement::BedShapeHint &bedhint) { // TODO From ba82cbe007caf8b17b0507b5681c8b3c0f8e7a77 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Jul 2019 10:32:01 +0200 Subject: [PATCH 240/627] Fix broken partial arrange --- src/libslic3r/Arrange.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 3abe495319..87289d968a 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -340,28 +340,26 @@ class AutoArranger {}; template class _ArrBase { public: - // Useful type shortcuts... using Placer = typename placers::_NofitPolyPlacer; using Selector = selections::_FirstFitSelection; - using Packer = Nester; - using PConfig = typename Packer::PlacementConfig; + using Packer = Nester; + using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; - + protected: - - Packer m_pck; - PConfig m_pconf; // Placement configuration - double m_bin_area; - SpatIndex m_rtree; // spatial index for the normal (bigger) objects + Packer m_pck; + PConfig m_pconf; // Placement configuration + double m_bin_area; + SpatIndex m_rtree; // spatial index for the normal (bigger) objects SpatIndex m_smallsrtree; // spatial index for only the smaller items - double m_norm; // A coefficient to scale distances + double m_norm; // A coefficient to scale distances MultiPolygon m_merged_pile; // The already merged pile (vector of items) - Box m_pilebb; // The bounding box of the merged pile. - ItemGroup m_remaining; // Remaining items (m_items at the beginning) - ItemGroup m_items; // The items to be packed + Box m_pilebb; // The bounding box of the merged pile. + ItemGroup m_remaining; // Remaining items (m_items at the beginning) + ItemGroup m_items; // The items to be packed + public: - _ArrBase(const TBin& bin, Distance dist, std::function progressind, std::function stopcond): @@ -715,8 +713,8 @@ bool arrange(ArrangeablePtrs & arrangables, clpath.Contour.emplace_back(firstp); outp.emplace_back(applyfn, std::move(clpath)); - outp.front().rotation(rotation); - outp.front().translation({offs.x(), offs.y()}); + outp.back().rotation(rotation); + outp.back().translation({offs.x(), offs.y()}); }; for (Arrangeable *arrangeable : arrangables) { From b835075fd64e82c36ea821cd326b3f3ecd005356 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 2 Jul 2019 10:34:30 +0200 Subject: [PATCH 241/627] Visual hints for layers editing enabled whenever a layer is selected into the objects list --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 20 +++++++++++++------- src/slic3r/GUI/GUI_ObjectLayers.hpp | 5 ++++- src/slic3r/GUI/GUI_ObjectList.cpp | 1 + 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index ff0f55aefc..f9d3a89567 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -197,17 +197,25 @@ void ObjectLayers::update_layers_list() // Add new control according to the selected item if (type & itLayerRoot) + { + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); + m_selectable_range = { 0.0, 0.0 }; create_layers_list(); + } else - create_layer(objects_ctrl->GetModel()->GetLayerRangeByItem(item)); - + { + t_layer_height_range range = objects_ctrl->GetModel()->GetLayerRangeByItem(item); + create_layer(range); + update_scene_from_editor_selection(range, etLayerHeight); + } + m_parent->Layout(); } -void ObjectLayers::update_scene_from_editor_selection() const +void ObjectLayers::update_scene_from_editor_selection(const t_layer_height_range& range, EditorType type) const { // needed to show the visual hints in 3D scene - wxGetApp().plater()->canvas3D()->handle_layers_data_focus_event(m_selectable_range, m_selection_type); + wxGetApp().plater()->canvas3D()->handle_layers_data_focus_event(range, type); } void ObjectLayers::UpdateAndShow(const bool show) @@ -257,8 +265,6 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, this->Bind(wxEVT_KILL_FOCUS, [this, edit_fn](wxFocusEvent& e) { - wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); - if (!m_enter_pressed) { #ifndef __WXGTK__ /* Update data for next editor selection. @@ -291,7 +297,7 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e) { set_focus_data(); - parent->update_scene_from_editor_selection(); + parent->update_scene_from_editor_selection(parent->get_selectable_range(), parent->get_selection_type()); e.Skip(); }, this->GetId()); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index f19217fbeb..253cbf0a4f 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -73,11 +73,14 @@ public: void create_layers_list(); void update_layers_list(); - void update_scene_from_editor_selection() const; + void update_scene_from_editor_selection(const t_layer_height_range& range, EditorType type) const; void UpdateAndShow(const bool show) override; void msw_rescale(); + const t_layer_height_range& get_selectable_range() const { return m_selectable_range; } + EditorType get_selection_type() const { return m_selection_type; } + friend class LayerRangeEditor; }; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 03f9cd45f1..af1a9908d1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2067,6 +2067,7 @@ void ObjectList::part_selection_changed() Sidebar& panel = wxGetApp().sidebar(); panel.Freeze(); + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers); From f09fb92b619ea94e7ea6f02edd393a4dfe5d7976 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 2 Jul 2019 11:43:07 +0200 Subject: [PATCH 242/627] Fix build against system-provided qhull --- src/libslic3r/TriangleMesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 56accfefa9..fbfff90fb6 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -547,9 +547,9 @@ TriangleMesh TriangleMesh::convex_hull_3d() const #if REALfloat qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt"); #else - src_vertices.reserve(this->its.vertices() * 3); + src_vertices.reserve(this->its.vertices.size() * 3); // We will now fill the vector with input points for computation: - for (const stl_vertex &v : ths->its.vertices.size()) + for (const stl_vertex &v : this->its.vertices) for (int i = 0; i < 3; ++ i) src_vertices.emplace_back(v(i)); qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); From 914bf632288f036b2b7cb509eb5ba843cdc3b7ed Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Jul 2019 12:15:53 +0200 Subject: [PATCH 243/627] Unify AutoArranger subclasses --- src/libnest2d/include/libnest2d/libnest2d.hpp | 8 +- src/libslic3r/Arrange.cpp | 561 +++++++++--------- 2 files changed, 301 insertions(+), 268 deletions(-) diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 8d3a2272dd..c7d515d49b 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -774,14 +774,14 @@ public: using BinType = typename TPlacer::BinType; using PlacementConfig = typename TPlacer::Config; using SelectionConfig = typename TSel::Config; - using Unit = TCoord>; + using Coord = TCoord>; using PackGroup = _PackGroup; using ResultType = PackGroup; private: BinType bin_; PlacementConfig pconfig_; - Unit min_obj_distance_; + Coord min_obj_distance_; using SItem = typename SelectionStrategy::Item; using TPItem = remove_cvref_t; @@ -802,7 +802,7 @@ public: class PConf = PlacementConfig, class SConf = SelectionConfig> Nester( TBinType&& bin, - Unit min_obj_distance = 0, + Coord min_obj_distance = 0, const PConf& pconfig = PConf(), const SConf& sconfig = SConf()): bin_(std::forward(bin)), @@ -895,7 +895,7 @@ private: template inline void __execute(TIter from, TIter to) { if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { - item.addOffset(static_cast(std::ceil(min_obj_distance_/2.0))); + item.addOffset(static_cast(std::ceil(min_obj_distance_/2.0))); }); selector_.template packItems( diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 87289d968a..debd29024c 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -171,142 +171,6 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) { return Box(minc, maxc); } -// This is "the" object function which is evaluated many times for each vertex -// (decimated with the accuracy parameter) of each object. Therefore it is -// upmost crucial for this function to be as efficient as it possibly can be but -// at the same time, it has to provide reasonable results. -std::tuple -objfunc(const PointImpl& bincenter, - const MultiPolygon& merged_pile, - const Box& pilebb, - const ItemGroup& items, - const Item &item, - double bin_area, - double norm, // A norming factor for physical dimensions - // a spatial index to quickly get neighbors of the candidate item - const SpatIndex& spatindex, - const SpatIndex& smalls_spatindex, - const ItemGroup& remaining - ) -{ - // We will treat big items (compared to the print bed) differently - auto isBig = [bin_area](double a) { - return a/bin_area > BIG_ITEM_TRESHOLD ; - }; - - // Candidate item bounding box - auto ibb = sl::boundingBox(item.transformedShape()); - - // Calculate the full bounding box of the pile with the candidate item - auto fullbb = boundingBox(pilebb, ibb); - - // The bounding box of the big items (they will accumulate in the center - // of the pile - Box bigbb; - if(spatindex.empty()) bigbb = fullbb; - else { - auto boostbb = spatindex.bounds(); - boost::geometry::convert(boostbb, bigbb); - } - - // Will hold the resulting score - double score = 0; - - if(isBig(item.area()) || spatindex.empty()) { - // This branch is for the bigger items.. - - auto minc = ibb.minCorner(); // bottom left corner - auto maxc = ibb.maxCorner(); // top right corner - - // top left and bottom right corners - auto top_left = PointImpl{getX(minc), getY(maxc)}; - auto bottom_right = PointImpl{getX(maxc), getY(minc)}; - - // Now the distance of the gravity center will be calculated to the - // five anchor points and the smallest will be chosen. - std::array dists; - auto cc = fullbb.center(); // The gravity center - dists[0] = pl::distance(minc, cc); - dists[1] = pl::distance(maxc, cc); - dists[2] = pl::distance(ibb.center(), cc); - dists[3] = pl::distance(top_left, cc); - dists[4] = pl::distance(bottom_right, cc); - - // The smalles distance from the arranged pile center: - auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; - auto bindist = pl::distance(ibb.center(), bincenter) / norm; - dist = 0.8*dist + 0.2*bindist; - - // Density is the pack density: how big is the arranged pile - double density = 0; - - if(remaining.empty()) { - - auto mp = merged_pile; - mp.emplace_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - - placers::EdgeCache ec(chull); - - double circ = ec.circumference() / norm; - double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; - score = 0.5*circ + 0.5*bcirc; - - } else { - // Prepare a variable for the alignment score. - // This will indicate: how well is the candidate item aligned with - // its neighbors. We will check the alignment with all neighbors and - // return the score for the best alignment. So it is enough for the - // candidate to be aligned with only one item. - auto alignment_score = 1.0; - - density = std::sqrt((fullbb.width() / norm )* - (fullbb.height() / norm)); - auto querybb = item.boundingBox(); - - // Query the spatial index for the neighbors - std::vector result; - result.reserve(spatindex.size()); - if(isBig(item.area())) { - spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); - } else { - smalls_spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); - } - - for(auto& e : result) { // now get the score for the best alignment - auto idx = e.second; - Item& p = items[idx]; - auto parea = p.area(); - if(std::abs(1.0 - parea/item.area()) < 1e-6) { - auto bb = boundingBox(p.boundingBox(), ibb); - auto bbarea = bb.area(); - auto ascore = 1.0 - (item.area() + parea)/bbarea; - - if(ascore < alignment_score) alignment_score = ascore; - } - } - - // The final mix of the score is the balance between the distance - // from the full pile center, the pack density and the - // alignment with the neighbors - if(result.empty()) - score = 0.5 * dist + 0.5 * density; - else - score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; - } - } else { - // Here there are the small items that should be placed around the - // already processed bigger items. - // No need to play around with the anchor points, the center will be - // just fine for small items - score = pl::distance(ibb.center(), bigbb.center()) / norm; - } - - return std::make_tuple(score, fullbb); -} - // Fill in the placer algorithm configuration with values carefully chosen for // Slic3r. template @@ -332,13 +196,16 @@ void fillConfig(PConf& pcfg) { // Type trait for an arranger class for different bin types (box, circle, // polygon, etc...) -template -class AutoArranger {}; +//template +//class AutoArranger {}; + +template clppr::IntPoint center(const Bin& bin) { return bin.center(); } +template<> clppr::IntPoint center(const clppr::Polygon &bin) { return sl::boundingBox(bin).center(); } // A class encapsulating the libnest2d Nester class and extending it with other // management and spatial index structures for acceleration. template -class _ArrBase { +class AutoArranger { public: // Useful type shortcuts... using Placer = typename placers::_NofitPolyPlacer; @@ -350,7 +217,9 @@ public: protected: Packer m_pck; PConfig m_pconf; // Placement configuration - double m_bin_area; + TBin m_bin; + double m_bin_area; // caching + PointImpl m_bincenter; // caching SpatIndex m_rtree; // spatial index for the normal (bigger) objects SpatIndex m_smallsrtree; // spatial index for only the smaller items double m_norm; // A coefficient to scale distances @@ -358,13 +227,152 @@ protected: Box m_pilebb; // The bounding box of the merged pile. ItemGroup m_remaining; // Remaining items (m_items at the beginning) ItemGroup m_items; // The items to be packed + + // This is "the" object function which is evaluated many times for each + // vertex (decimated with the accuracy parameter) of each object. + // Therefore it is upmost crucial for this function to be as efficient + // as it possibly can be but at the same time, it has to provide + // reasonable results. + std::tuple + objfunc(const Item &item ) + { + const double bin_area = m_bin_area; + const SpatIndex& spatindex = m_rtree; + const SpatIndex& smalls_spatindex = m_smallsrtree; + const ItemGroup& remaining = m_remaining; + + // We will treat big items (compared to the print bed) differently + auto isBig = [bin_area](double a) { + return a/bin_area > BIG_ITEM_TRESHOLD ; + }; + + // Candidate item bounding box + auto ibb = sl::boundingBox(item.transformedShape()); + + // Calculate the full bounding box of the pile with the candidate item + auto fullbb = boundingBox(m_pilebb, ibb); + + // The bounding box of the big items (they will accumulate in the center + // of the pile + Box bigbb; + if(spatindex.empty()) bigbb = fullbb; + else { + auto boostbb = spatindex.bounds(); + boost::geometry::convert(boostbb, bigbb); + } + + // Will hold the resulting score + double score = 0; + + if(isBig(item.area()) || spatindex.empty()) { + // This branch is for the bigger items.. + + auto minc = ibb.minCorner(); // bottom left corner + auto maxc = ibb.maxCorner(); // top right corner + + // top left and bottom right corners + auto top_left = PointImpl{getX(minc), getY(maxc)}; + auto bottom_right = PointImpl{getX(maxc), getY(minc)}; + + // Now the distance of the gravity center will be calculated to the + // five anchor points and the smallest will be chosen. + std::array dists; + auto cc = fullbb.center(); // The gravity center + dists[0] = pl::distance(minc, cc); + dists[1] = pl::distance(maxc, cc); + dists[2] = pl::distance(ibb.center(), cc); + dists[3] = pl::distance(top_left, cc); + dists[4] = pl::distance(bottom_right, cc); + + // The smalles distance from the arranged pile center: + double dist = *(std::min_element(dists.begin(), dists.end())) / m_norm; + double bindist = pl::distance(ibb.center(), m_bincenter) / m_norm; + dist = 0.8*dist + 0.2*bindist; + + // Density is the pack density: how big is the arranged pile + double density = 0; + + if(remaining.empty()) { + + auto mp = m_merged_pile; + mp.emplace_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + + placers::EdgeCache ec(chull); + + double circ = ec.circumference() / m_norm; + double bcirc = 2.0*(fullbb.width() + fullbb.height()) / m_norm; + score = 0.5*circ + 0.5*bcirc; + + } else { + // Prepare a variable for the alignment score. + // This will indicate: how well is the candidate item + // aligned with its neighbors. We will check the alignment + // with all neighbors and return the score for the best + // alignment. So it is enough for the candidate to be + // aligned with only one item. + auto alignment_score = 1.0; + + auto querybb = item.boundingBox(); + density = std::sqrt((fullbb.width() / m_norm )* + (fullbb.height() / m_norm)); + + // Query the spatial index for the neighbors + std::vector result; + result.reserve(spatindex.size()); + if(isBig(item.area())) { + spatindex.query(bgi::intersects(querybb), + std::back_inserter(result)); + } else { + smalls_spatindex.query(bgi::intersects(querybb), + std::back_inserter(result)); + } + + // now get the score for the best alignment + for(auto& e : result) { + auto idx = e.second; + Item& p = m_items[idx]; + auto parea = p.area(); + if(std::abs(1.0 - parea/item.area()) < 1e-6) { + auto bb = boundingBox(p.boundingBox(), ibb); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; + + if(ascore < alignment_score) alignment_score = ascore; + } + } + + // The final mix of the score is the balance between the + // distance from the full pile center, the pack density and + // the alignment with the neighbors + if (result.empty()) + score = 0.5 * dist + 0.5 * density; + else + score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; + } + } else { + // Here there are the small items that should be placed around the + // already processed bigger items. + // No need to play around with the anchor points, the center will be + // just fine for small items + score = pl::distance(ibb.center(), bigbb.center()) / m_norm; + } + + return std::make_tuple(score, fullbb); + } + + std::function get_objfn(); public: - _ArrBase(const TBin& bin, Distance dist, - std::function progressind, - std::function stopcond): - m_pck(bin, dist), m_bin_area(sl::area(bin)), - m_norm(std::sqrt(m_bin_area)) + AutoArranger(const TBin & bin, + Distance dist, + std::function progressind, + std::function stopcond) + : m_pck(bin, dist) + , m_bin(bin) + , m_bin_area(sl::area(bin)) + , m_bincenter(center(bin)) + , m_norm(std::sqrt(m_bin_area)) { fillConfig(m_pconf); @@ -396,8 +404,12 @@ public: } }; + m_pconf.object_function = get_objfn(); + if (progressind) m_pck.progressIndicator(progressind); if (stopcond) m_pck.stopCondition(stopcond); + + m_pck.configure(m_pconf); } template inline PackGroup operator()(Args&&...args) { @@ -405,15 +417,16 @@ public: return m_pck.execute(std::forward(args)...); } - inline void preload(const PackGroup& pg) { + inline void preload(std::vector& fixeditems) { m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; - m_pconf.object_function = nullptr; // drop the special objectfunction - m_pck.preload(pg); +// m_pconf.object_function = nullptr; // drop the special objectfunction +// m_pck.preload(pg); // Build the rtree for queries to work - for(const ItemGroup& grp : pg) - for(unsigned idx = 0; idx < grp.size(); ++idx) { - Item& itm = grp[idx]; + + for(unsigned idx = 0; idx < fixeditems.size(); ++idx) { + Item& itm = fixeditems[idx]; + itm.markAsFixed(); m_rtree.insert({itm.boundingBox(), idx}); } @@ -429,125 +442,144 @@ public: } }; -// Arranger specialization for a Box shaped bin. -template<> class AutoArranger: public _ArrBase { -public: +template<> std::function AutoArranger::get_objfn() +{ + return [this](const Item &itm) { + auto result = objfunc(itm); + + double score = std::get<0>(result); + auto& fullbb = std::get<1>(result); - AutoArranger(const Box& bin, Distance dist, - std::function progressind = [](unsigned){}, - std::function stopcond = [](){return false;}): - _ArrBase(bin, dist, progressind, stopcond) - { + double miss = Placer::overfit(fullbb, m_bin); + miss = miss > 0? miss : 0; + score += miss*miss; - // Here we set up the actual object function that calls the common - // object function for all bin shapes than does an additional inside - // check for the arranged pile. - m_pconf.object_function = [this, bin] (const Item &item) { + return score; + }; +} - auto result = objfunc(bin.center(), - m_merged_pile, - m_pilebb, - m_items, - item, - m_bin_area, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); +template<> std::function AutoArranger::get_objfn() +{ + return [this](const Item &item) { + + auto result = objfunc(item); - double score = std::get<0>(result); - auto& fullbb = std::get<1>(result); + double score = std::get<0>(result); - double miss = Placer::overfit(fullbb, bin); - miss = miss > 0? miss : 0; - score += miss*miss; - - return score; + auto isBig = [this](const Item& itm) { + return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; }; - m_pck.configure(m_pconf); - } -}; + if(isBig(item)) { + auto mp = m_merged_pile; + mp.push_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + double miss = Placer::overfit(chull, m_bin); + if(miss < 0) miss = 0; + score += miss*miss; + } + + return score; + }; +} + +template<> std::function AutoArranger::get_objfn() +{ + return [this] (const Item &item) { return std::get<0>(objfunc(item)); }; +} + +// Arranger specialization for a Box shaped bin. +//template<> class AutoArranger: public _ArrBase { +//public: + +// AutoArranger(const Box& bin, Distance dist, +// std::function progressind = [](unsigned){}, +// std::function stopcond = [](){return false;}): +// _ArrBase(bin, dist, progressind, stopcond) +// { + +// // Here we set up the actual object function that calls the common +// // object function for all bin shapes than does an additional inside +// // check for the arranged pile. +// m_pconf.object_function = [this, bin](const Item &item) { + +// auto result = objfunc(bin.center(), item); + +// double score = std::get<0>(result); +// auto& fullbb = std::get<1>(result); + +// double miss = Placer::overfit(fullbb, bin); +// miss = miss > 0? miss : 0; +// score += miss*miss; + +// return score; +// }; + +// m_pck.configure(m_pconf); +// } +//}; inline Circle to_lnCircle(const CircleBed& circ) { return Circle({circ.center()(0), circ.center()(1)}, circ.radius()); } -// Arranger specialization for circle shaped bin. -template<> class AutoArranger: public _ArrBase { -public: +//// Arranger specialization for circle shaped bin. +//template<> class AutoArranger: public _ArrBase { +//public: - AutoArranger(const Circle& bin, Distance dist, - std::function progressind = [](unsigned){}, - std::function stopcond = [](){return false;}): - _ArrBase(bin, dist, progressind, stopcond) { +// AutoArranger(const Circle& bin, Distance dist, +// std::function progressind = [](unsigned){}, +// std::function stopcond = [](){return false;}): +// _ArrBase(bin, dist, progressind, stopcond) { - // As with the box, only the inside check is different. - m_pconf.object_function = [this, &bin] (const Item &item) { +// // As with the box, only the inside check is different. +// m_pconf.object_function = [this, &bin](const Item &item) { + +// auto result = objfunc(bin.center(), item); - auto result = objfunc(bin.center(), - m_merged_pile, - m_pilebb, - m_items, - item, - m_bin_area, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); +// double score = std::get<0>(result); - double score = std::get<0>(result); +// auto isBig = [this](const Item& itm) { +// return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; +// }; - auto isBig = [this](const Item& itm) { - return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; - }; +// if(isBig(item)) { +// auto mp = m_merged_pile; +// mp.push_back(item.transformedShape()); +// auto chull = sl::convexHull(mp); +// double miss = Placer::overfit(chull, bin); +// if(miss < 0) miss = 0; +// score += miss*miss; +// } - if(isBig(item)) { - auto mp = m_merged_pile; - mp.push_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - double miss = Placer::overfit(chull, bin); - if(miss < 0) miss = 0; - score += miss*miss; - } +// return score; +// }; - return score; - }; - - m_pck.configure(m_pconf); - } -}; +// m_pck.configure(m_pconf); +// } +//}; // Arranger specialization for a generalized polygon. // Warning: this is unfinished business. It may or may not work. -template<> class AutoArranger: public _ArrBase { -public: - AutoArranger(const PolygonImpl& bin, Distance dist, - std::function progressind = [](unsigned){}, - std::function stopcond = [](){return false;}): - _ArrBase(bin, dist, progressind, stopcond) - { - m_pconf.object_function = [this, &bin] (const Item &item) { +//template<> class AutoArranger: public _ArrBase { +//public: +// AutoArranger(const PolygonImpl& bin, Distance dist, +// std::function progressind = [](unsigned){}, +// std::function stopcond = [](){return false;}): +// _ArrBase(bin, dist, progressind, stopcond) +// { +// m_pconf.object_function = [this, &bin] (const Item &item) { - auto binbb = sl::boundingBox(bin); - auto result = objfunc(binbb.center(), - m_merged_pile, - m_pilebb, - m_items, - item, - m_bin_area, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - double score = std::get<0>(result); +// auto binbb = sl::boundingBox(bin); +// auto result = objfunc(binbb.center(), item); +// double score = std::get<0>(result); - return score; - }; +// return score; +// }; - m_pck.configure(m_pconf); - } -}; +// m_pck.configure(m_pconf); +// } +//}; // Get the type of bed geometry from a simple vector of points. BedShapeHint bedShape(const Polyline &bed) { @@ -628,9 +660,9 @@ BedShapeHint bedShape(const Polyline &bed) { return ret; } -template +template // Arrange for arbitrary bin type PackGroup _arrange(std::vector & shapes, - const PackGroup & preshapes, + std::vector & excludes, const BinT & bin, coord_t minobjd, std::function prind, @@ -638,9 +670,13 @@ PackGroup _arrange(std::vector & shapes, { AutoArranger arranger{bin, minobjd, prind, stopfn}; + for(auto it = excludes.begin(); it != excludes.end(); ++it) + if (!sl::isInside(it->transformedShape(), bin)) + it = excludes.erase(it); + // If there is something on the plate - if(!preshapes.empty() && !preshapes.front().empty()) { - arranger.preload(preshapes); + if(!excludes.empty()) { +// arranger.preload(preshapes); auto binbb = sl::boundingBox(bin); // Try to put the first item to the center, as the arranger will not @@ -652,7 +688,8 @@ PackGroup _arrange(std::vector & shapes, itm.translate(d); if (!arranger.is_colliding(itm)) { - arranger.preload({{itm}}); + itm.markAsFixed(); +// arranger.preload({{itm}}); // Write the transformation data into the item. The callback // was set on the instantiation of Item and calls the @@ -674,8 +711,8 @@ inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) return w + w / 5; } -//// The final client function to arrange the Model. A progress indicator and -//// a stop predicate can be also be passed to control the process. +// The final client function for arrangement. A progress indicator and +// a stop predicate can be also be passed to control the process. bool arrange(ArrangeablePtrs & arrangables, const ArrangeablePtrs & excludes, coord_t min_obj_distance, @@ -686,11 +723,9 @@ bool arrange(ArrangeablePtrs & arrangables, bool ret = true; namespace clppr = ClipperLib; - std::vector items, excluded_items; + std::vector items, fixeditems; items.reserve(arrangables.size()); coord_t binwidth = 0; - - PackGroup preshapes{ {} }; // pack group with one initial bin for preloading auto process_arrangeable = [](const Arrangeable * arrangeable, @@ -733,9 +768,7 @@ bool arrange(ArrangeablePtrs & arrangables, } for (const Arrangeable * fixed: excludes) - process_arrangeable(fixed, excluded_items, nullptr); - - for(Item& excl : excluded_items) preshapes.front().emplace_back(excl); + process_arrangeable(fixed, fixeditems, nullptr); // Integer ceiling the min distance from the bed perimeters coord_t md = min_obj_distance - SCALED_EPSILON; @@ -751,7 +784,7 @@ bool arrange(ArrangeablePtrs & arrangables, Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; binwidth = coord_t(binbb.width()); - _arrange(items, preshapes, binbb, min_obj_distance, progressind, cfn); + _arrange(items, fixeditems, binbb, min_obj_distance, progressind, cfn); break; } case BedShapeType::CIRCLE: { @@ -759,7 +792,7 @@ bool arrange(ArrangeablePtrs & arrangables, auto cc = to_lnCircle(c); binwidth = scaled(c.radius()); - _arrange(items, preshapes, cc, min_obj_distance, progressind, cfn); + _arrange(items, fixeditems, cc, min_obj_distance, progressind, cfn); break; } case BedShapeType::IRREGULAR: { @@ -768,7 +801,7 @@ bool arrange(ArrangeablePtrs & arrangables, BoundingBox polybb(bedhint.shape.polygon); binwidth = (polybb.max(X) - polybb.min(X)); - _arrange(items, preshapes, irrbed, min_obj_distance, progressind, cfn); + _arrange(items, fixeditems, irrbed, min_obj_distance, progressind, cfn); break; } case BedShapeType::INFINITE: { @@ -776,12 +809,12 @@ bool arrange(ArrangeablePtrs & arrangables, //Box infbb{{nobin.center.x(), nobin.center.y()}}; Box infbb; - _arrange(items, preshapes, infbb, min_obj_distance, progressind, cfn); + _arrange(items, fixeditems, infbb, min_obj_distance, progressind, cfn); break; } case BedShapeType::UNKNOWN: { // We know nothing about the bed, let it be infinite and zero centered - _arrange(items, preshapes, Box{}, min_obj_distance, progressind, cfn); + _arrange(items, fixeditems, Box{}, min_obj_distance, progressind, cfn); break; } }; @@ -791,7 +824,7 @@ bool arrange(ArrangeablePtrs & arrangables, return ret; } -/// Arrange, without the fixed items (excludes) +// Arrange, without the fixed items (excludes) bool arrange(ArrangeablePtrs & inp, coord_t min_d, const BedShapeHint & bedhint, From d15698e21e86a4e896bbb5f3c59440ec2dc721e9 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 2 Jul 2019 12:55:55 +0200 Subject: [PATCH 244/627] GLVolume and GLIndexedVertexArray refactored to send data to gpu at the first render call --- src/slic3r/GUI/3DScene.cpp | 225 +++++++++++----------------------- src/slic3r/GUI/3DScene.hpp | 74 ++++++----- src/slic3r/GUI/GLCanvas3D.cpp | 61 ++------- 3 files changed, 128 insertions(+), 232 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index c483f0ec33..fe0cd9ffbd 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -74,13 +74,13 @@ void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh) } } -void GLIndexedVertexArray::finalize_geometry() +void GLIndexedVertexArray::finalize_geometry() const { assert(this->vertices_and_normals_interleaved_VBO_id == 0); assert(this->triangle_indices_VBO_id == 0); assert(this->quad_indices_VBO_id == 0); - this->setup_sizes(); + this->shrink_to_fit(); if (! empty()) { glsafe(::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id)); @@ -103,8 +103,6 @@ void GLIndexedVertexArray::finalize_geometry() glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); this->quad_indices.clear(); } - - this->shrink_to_fit(); } void GLIndexedVertexArray::release_geometry() @@ -122,89 +120,78 @@ void GLIndexedVertexArray::release_geometry() this->quad_indices_VBO_id = 0; } this->clear(); - this->shrink_to_fit(); } void GLIndexedVertexArray::render() const { - if (this->vertices_and_normals_interleaved_VBO_id) { - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); - } else { - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data() + 3)); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data())); + if (this->vertices_and_normals_interleaved_VBO_id == 0) + { + // sends data to gpu, if not done yet + finalize_geometry(); + if (this->vertices_and_normals_interleaved_VBO_id == 0) + return; } + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); + glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - if (this->indexed()) { - if (this->vertices_and_normals_interleaved_VBO_id) { - // Render using the Vertex Buffer Objects. - if (this->triangle_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_size), GL_UNSIGNED_INT, nullptr)); - } - if (this->quad_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); - glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size), GL_UNSIGNED_INT, nullptr)); - } - glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } else { - // Render in an immediate mode. - if (! this->triangle_indices.empty()) - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_size), GL_UNSIGNED_INT, this->triangle_indices.data())); - if (! this->quad_indices.empty()) - glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size), GL_UNSIGNED_INT, this->quad_indices.data())); - } - } else - glsafe(::glDrawArrays(GL_TRIANGLES, 0, GLsizei(this->vertices_and_normals_interleaved_size / 6))); - - if (this->vertices_and_normals_interleaved_VBO_id) - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); -} - -void GLIndexedVertexArray::render( - const std::pair &tverts_range, - const std::pair &qverts_range) const -{ - assert(this->indexed()); - if (! this->indexed()) - return; - - if (this->vertices_and_normals_interleaved_VBO_id) { - // Render using the Vertex Buffer Objects. - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - if (this->triangle_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(std::min(this->triangle_indices_size, tverts_range.second - tverts_range.first)), GL_UNSIGNED_INT, (const void*)(tverts_range.first * 4))); - } - if (this->quad_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); - glsafe(::glDrawElements(GL_QUADS, GLsizei(std::min(this->quad_indices_size, qverts_range.second - qverts_range.first)), GL_UNSIGNED_INT, (const void*)(qverts_range.first * 4))); - } - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } else { - // Render in an immediate mode. - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data() + 3)); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), this->vertices_and_normals_interleaved.data())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - if (! this->triangle_indices.empty()) - glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(std::min(this->triangle_indices_size, tverts_range.second - tverts_range.first)), GL_UNSIGNED_INT, (const void*)(this->triangle_indices.data() + tverts_range.first))); - if (! this->quad_indices.empty()) - glsafe(::glDrawElements(GL_QUADS, GLsizei(std::min(this->quad_indices_size, qverts_range.second - qverts_range.first)), GL_UNSIGNED_INT, (const void*)(this->quad_indices.data() + qverts_range.first))); + // Render using the Vertex Buffer Objects. + if (this->triangle_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); + glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_size), GL_UNSIGNED_INT, nullptr)); + glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + if (this->quad_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); + glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size), GL_UNSIGNED_INT, nullptr)); + glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); } glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} + +void GLIndexedVertexArray::render( + const std::pair& tverts_range, + const std::pair& qverts_range) const +{ + if (this->vertices_and_normals_interleaved_VBO_id == 0) + { + // sends data to gpu, if not done yet + finalize_geometry(); + if (this->vertices_and_normals_interleaved_VBO_id == 0) + return; + } + + // Render using the Vertex Buffer Objects. + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); + glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); + + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + + if (this->triangle_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_id)); + glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(std::min(this->triangle_indices_size, tverts_range.second - tverts_range.first)), GL_UNSIGNED_INT, (const void*)(tverts_range.first * 4))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + if (this->quad_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->quad_indices_VBO_id)); + glsafe(::glDrawElements(GL_QUADS, GLsizei(std::min(this->quad_indices_size, qverts_range.second - qverts_range.first)), GL_UNSIGNED_INT, (const void*)(qverts_range.first * 4))); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); } const float GLVolume::SELECTED_COLOR[4] = { 0.0f, 1.0f, 0.0f, 1.0f }; @@ -357,9 +344,9 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d & void GLVolume::set_range(double min_z, double max_z) { - this->qverts_range.first = 0; + this->qverts_range.first = 0; this->qverts_range.second = this->indexed_vertex_array.quad_indices_size; - this->tverts_range.first = 0; + this->tverts_range.first = 0; this->tverts_range.second = this->indexed_vertex_array.triangle_indices_size; if (! this->print_zs.empty()) { // The Z layer range is specified. @@ -399,12 +386,10 @@ void GLVolume::render() const glFrontFace(GL_CW); glsafe(::glCullFace(GL_BACK)); glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(world_matrix().data())); - if (this->indexed_vertex_array.indexed()) - this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); - else - this->indexed_vertex_array.render(); + + this->indexed_vertex_array.render(this->tverts_range, this->qverts_range); + glsafe(::glPopMatrix()); if (this->is_left_handed()) glFrontFace(GL_CCW); @@ -412,45 +397,6 @@ void GLVolume::render() const void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const { - if (!is_active) - return; - - if (!indexed_vertex_array.vertices_and_normals_interleaved_VBO_id) - return; - - GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first)); - GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first)); - if (n_triangles + n_quads == 0) - { - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - if (color_id >= 0) - { - float color[4]; - ::memcpy((void*)color, (const void*)render_color, 4 * sizeof(float)); - glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)color)); - } - else - glsafe(::glColor4fv(render_color)); - - if (detection_id != -1) - glsafe(::glUniform1i(detection_id, shader_outside_printer_detection_enabled ? 1 : 0)); - - if (worldmatrix_id != -1) - glsafe(::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().cast().data())); - - render(); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - - return; - } - - if (this->is_left_handed()) - glFrontFace(GL_CW); - if (color_id >= 0) glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)render_color)); else @@ -462,29 +408,7 @@ void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const if (worldmatrix_id != -1) glsafe(::glUniformMatrix4fv(worldmatrix_id, 1, GL_FALSE, (const GLfloat*)world_matrix().cast().data())); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr)); - - glsafe(::glPushMatrix()); - - glsafe(::glMultMatrixd(world_matrix().data())); - - if (n_triangles > 0) - { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexed_vertex_array.triangle_indices_VBO_id)); - glsafe(::glDrawElements(GL_TRIANGLES, n_triangles, GL_UNSIGNED_INT, (const void*)(tverts_range.first * 4))); - } - if (n_quads > 0) - { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexed_vertex_array.quad_indices_VBO_id)); - glsafe(::glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, (const void*)(qverts_range.first * 4))); - } - - glsafe(::glPopMatrix()); - - if (this->is_left_handed()) - glFrontFace(GL_CCW); + render(); } bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); } @@ -531,8 +455,6 @@ int GLVolumeCollection::load_object_volume( GLVolume& v = *this->volumes.back(); v.set_color_from_model_volume(model_volume); v.indexed_vertex_array.load_mesh(mesh); - - v.indexed_vertex_array.finalize_geometry(); v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); if (model_volume->is_model_part()) { @@ -573,7 +495,6 @@ void GLVolumeCollection::load_object_auxiliary( this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); GLVolume& v = *this->volumes.back(); v.indexed_vertex_array.load_mesh(mesh); - v.indexed_vertex_array.finalize_geometry(); v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); v.geometry_id = std::pair(timestamp, model_instance.id().id); // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. @@ -645,8 +566,6 @@ int GLVolumeCollection::load_wipe_tower_preview( v.indexed_vertex_array.load_mesh(mesh); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); - - v.indexed_vertex_array.finalize_geometry(); v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); v.geometry_id.first = 0; v.geometry_id.second = wipe_tower_instance_id().id; @@ -1576,7 +1495,9 @@ void GLModel::set_scale(const Vec3d& scale) void GLModel::reset() { - m_volume.release_geometry(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// m_volume.release_geometry(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_filename = ""; } @@ -1659,7 +1580,6 @@ bool GLArrow::on_init() triangles.emplace_back(7, 13, 6); m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles)); - m_volume.finalize_geometry(); return true; } @@ -1773,7 +1693,6 @@ bool GLCurvedArrow::on_init() triangles.emplace_back(vertices_per_level, 2 * vertices_per_level + 1, vertices_per_level + 1); m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles)); - m_volume.finalize_geometry(); return true; } @@ -1810,8 +1729,6 @@ bool GLBed::on_init_from_file(const std::string& filename) float color[4] = { 0.235f, 0.235f, 0.235f, 1.0f }; set_color(color, 4); - m_volume.finalize_geometry(); - return true; } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index f3db004621..bb629a1d8b 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -56,7 +56,7 @@ public: vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) - { this->setup_sizes(); } + {} GLIndexedVertexArray(const GLIndexedVertexArray &rhs) : vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved), triangle_indices(rhs.triangle_indices), @@ -64,7 +64,7 @@ public: vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) - { this->setup_sizes(); } + {} GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), triangle_indices(std::move(rhs.triangle_indices)), @@ -72,7 +72,11 @@ public: vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) - { this->setup_sizes(); } + {} + +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + ~GLIndexedVertexArray() { release_geometry(); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs) { @@ -83,7 +87,9 @@ public: this->triangle_indices = rhs.triangle_indices; this->quad_indices = rhs.quad_indices; this->m_bounding_box = rhs.m_bounding_box; - this->setup_sizes(); + vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; + triangle_indices_size = rhs.triangle_indices_size; + quad_indices_size = rhs.quad_indices_size; return *this; } @@ -96,26 +102,28 @@ public: this->triangle_indices = std::move(rhs.triangle_indices); this->quad_indices = std::move(rhs.quad_indices); this->m_bounding_box = std::move(rhs.m_bounding_box); - this->setup_sizes(); + vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; + triangle_indices_size = rhs.triangle_indices_size; + quad_indices_size = rhs.quad_indices_size; return *this; } // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x) - std::vector vertices_and_normals_interleaved; - std::vector triangle_indices; - std::vector quad_indices; + mutable std::vector vertices_and_normals_interleaved; + mutable std::vector triangle_indices; + mutable std::vector quad_indices; // When the geometry data is loaded into the graphics card as Vertex Buffer Objects, // the above mentioned std::vectors are cleared and the following variables keep their original length. - size_t vertices_and_normals_interleaved_size; - size_t triangle_indices_size; - size_t quad_indices_size; + size_t vertices_and_normals_interleaved_size{ 0 }; + size_t triangle_indices_size{ 0 }; + size_t quad_indices_size{ 0 }; // IDs of the Vertex Array Objects, into which the geometry has been loaded. // Zero if the VBOs are not sent to GPU yet. - unsigned int vertices_and_normals_interleaved_VBO_id; - unsigned int triangle_indices_VBO_id; - unsigned int quad_indices_VBO_id; + mutable unsigned int vertices_and_normals_interleaved_VBO_id{ 0 }; + mutable unsigned int triangle_indices_VBO_id{ 0 }; + mutable unsigned int quad_indices_VBO_id{ 0 }; void load_mesh_full_shading(const TriangleMesh &mesh); void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } @@ -129,6 +137,10 @@ public: } inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity()) this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6)); this->vertices_and_normals_interleaved.push_back(nx); @@ -138,6 +150,7 @@ public: this->vertices_and_normals_interleaved.push_back(y); this->vertices_and_normals_interleaved.push_back(z); + this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); m_bounding_box.merge(Vec3f(x, y, z).cast()); }; @@ -150,50 +163,57 @@ public: } inline void push_triangle(int idx1, int idx2, int idx3) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity()) this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3)); this->triangle_indices.push_back(idx1); this->triangle_indices.push_back(idx2); this->triangle_indices.push_back(idx3); + this->triangle_indices_size = this->triangle_indices.size(); }; inline void push_quad(int idx1, int idx2, int idx3, int idx4) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity()) this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4)); this->quad_indices.push_back(idx1); this->quad_indices.push_back(idx2); this->quad_indices.push_back(idx3); this->quad_indices.push_back(idx4); + this->quad_indices_size = this->quad_indices.size(); }; // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(); + void finalize_geometry() const; // Release the geometry data, release OpenGL VBOs. void release_geometry(); void render() const; - void render(const std::pair &tverts_range, const std::pair &qverts_range) const; + void render(const std::pair& tverts_range, const std::pair& qverts_range) const; // Is there any geometry data stored? bool empty() const { return vertices_and_normals_interleaved_size == 0; } - // Is this object indexed, or is it just a set of triangles? - bool indexed() const { return ! this->empty() && this->triangle_indices_size + this->quad_indices_size > 0; } - void clear() { this->vertices_and_normals_interleaved.clear(); this->triangle_indices.clear(); this->quad_indices.clear(); this->m_bounding_box.reset(); - this->setup_sizes(); + vertices_and_normals_interleaved_size = 0; + triangle_indices_size = 0; + quad_indices_size = 0; } // Shrink the internal storage to tighly fit the data stored. - void shrink_to_fit() { - if (! this->has_VBOs()) - this->setup_sizes(); + void shrink_to_fit() const { this->vertices_and_normals_interleaved.shrink_to_fit(); this->triangle_indices.shrink_to_fit(); this->quad_indices.shrink_to_fit(); @@ -203,12 +223,6 @@ public: private: BoundingBoxf3 m_bounding_box; - - inline void setup_sizes() { - vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); - triangle_indices_size = this->triangle_indices.size(); - quad_indices_size = this->quad_indices.size(); - } }; class GLVolume { @@ -400,9 +414,9 @@ public: const BoundingBoxf3& transformed_convex_hull_bounding_box() const; bool empty() const { return this->indexed_vertex_array.empty(); } - bool indexed() const { return this->indexed_vertex_array.indexed(); } void set_range(coordf_t low, coordf_t high); + void render() const; void render(int color_id, int detection_id, int worldmatrix_id) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5655390ecd..dbca770d6f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -524,7 +524,7 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data())); glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4)); - for (const GLVolume *glvolume : volumes.volumes) { + for (const GLVolume* glvolume : volumes.volumes) { // Render the object using the layer editing shader and texture. if (! glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) continue; @@ -543,8 +543,8 @@ void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D& canvas, const G { // Something went wrong. Just render the object. assert(false); - for (const GLVolume *glvolume : volumes.volumes) { - // Render the object using the layer editing shader and texture. + for (const GLVolume* glvolume : volumes.volumes) { + // Render the object using the layer editing shader and texture. if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier) continue; glsafe(::glUniformMatrix4fv(world_matrix_id, 1, GL_FALSE, (const GLfloat*)glvolume->world_matrix().cast().data())); @@ -1314,10 +1314,10 @@ bool GLCanvas3D::init() return false; } - // on linux the gl context is not valid until the canvas is not shown on screen - // we defer the geometry finalization of volumes until the first call to render() - if (!m_volumes.empty()) - m_volumes.finalize_geometry(); +// // on linux the gl context is not valid until the canvas is not shown on screen +// // we defer the geometry finalization of volumes until the first call to render() +// if (!m_volumes.empty()) +// m_volumes.finalize_geometry(); if (m_gizmos.is_enabled() && !m_gizmos.init(*this)) std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; @@ -1355,7 +1355,9 @@ void GLCanvas3D::reset_volumes() if (!m_volumes.empty()) { m_selection.clear(); - m_volumes.release_geometry(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// m_volumes.release_geometry(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_volumes.clear(); m_dirty = true; } @@ -1917,7 +1919,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(volume_idx_wipe_tower_old == -1); volume_idx_wipe_tower_old = (int)volume_id; } - volume->release_geometry(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// volume->release_geometry(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (! m_reload_delayed) delete volume; } else { @@ -4474,7 +4478,6 @@ void GLCanvas3D::_load_print_toolpaths() _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), volume); } - volume.indexed_vertex_array.finalize_geometry(); } void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors, const std::vector& color_print_values) @@ -4646,9 +4649,6 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c } } } - for (GLVolume *vol : vols) { - vol->indexed_vertex_array.shrink_to_fit(); - } }); BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results"; @@ -4657,8 +4657,6 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(); BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; } @@ -4814,9 +4812,6 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); } } - for (GLVolume *vol : vols) { - vol->indexed_vertex_array.shrink_to_fit(); - } }); BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; @@ -4825,8 +4820,6 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); - for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) - m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(); BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; } @@ -5003,16 +4996,6 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat } } } - - // finalize volumes and sends geometry to gpu - if (m_volumes.volumes.size() > initial_volumes_count) - { - for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) - { - GLVolume* volume = m_volumes.volumes[i]; - volume->indexed_vertex_array.finalize_geometry(); - } - } } void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector& tool_colors) @@ -5057,16 +5040,6 @@ void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, return; } - - // finalize volumes and sends geometry to gpu - if (m_volumes.volumes.size() > initial_volumes_count) - { - for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) - { - GLVolume* volume = m_volumes.volumes[i]; - volume->indexed_vertex_array.finalize_geometry(); - } - } } bool GLCanvas3D::_travel_paths_by_type(const GCodePreviewData& preview_data) @@ -5295,9 +5268,6 @@ void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data) _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); } - - // finalize volumes and sends geometry to gpu - volume->indexed_vertex_array.finalize_geometry(); } } @@ -5325,9 +5295,6 @@ void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data) _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); } - - // finalize volumes and sends geometry to gpu - volume->indexed_vertex_array.finalize_geometry(); } } @@ -5419,8 +5386,6 @@ void GLCanvas3D::_load_sla_shells() double shift_z = obj->get_current_elevation(); for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { GLVolume& v = *m_volumes.volumes[i]; - // finalize volumes and sends geometry to gpu - v.indexed_vertex_array.finalize_geometry(); // apply shift z v.set_sla_shift_z(shift_z); } From 136e5156bc0b9fcbeeda52dcbab8f9f993ba639e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 2 Jul 2019 13:13:17 +0200 Subject: [PATCH 245/627] Fixed colot_print issues: * Disabled color change information for the SLA and FFF-multimaterial presets * Corrected switch between "color print" and "feature type" on Preview --- src/libslic3r/GCode.cpp | 4 ++- src/slic3r/GUI/GLCanvas3D.cpp | 3 ++- src/slic3r/GUI/GUI_Preview.cpp | 47 ++++++++++++++++++++++----------- src/slic3r/GUI/GUI_Preview.hpp | 2 ++ src/slic3r/GUI/Plater.cpp | 3 +++ src/slic3r/GUI/wxExtensions.cpp | 10 +++++-- 6 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c42669de0d..c03431a308 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1405,7 +1405,9 @@ void GCode::process_layer( m_colorprint_heights.erase(m_colorprint_heights.begin()); colorprint_change = true; } - if (colorprint_change && print.extruders().size()==1) + + // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count + if (colorprint_change && print./*extruders()*/config().nozzle_diameter.size()==1) gcode += "M600\n"; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9bb6f45534..74b3e685aa 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -912,7 +912,8 @@ GLCanvas3D::LegendTexture::LegendTexture() void GLCanvas3D::LegendTexture::fill_color_print_legend_values(const GCodePreviewData& preview_data, const GLCanvas3D& canvas, std::vector>& cp_legend_values) { - if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint) + if (preview_data.extrusion.view_type == GCodePreviewData::Extrusion::ColorPrint && + wxGetApp().extruders_edited_cnt() == 1) // show color change legend only for single-material presets { auto& config = wxGetApp().preset_bundle->project_config; const std::vector& color_print_values = config.option("colorprint_heights")->values; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 671c49eefa..0ba447c1bc 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -541,6 +541,26 @@ void Preview::on_checkbox_shells(wxCommandEvent& evt) refresh_print(); } +void Preview::update_view_type() +{ + const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config; + + const wxString& choice = !config.option("colorprint_heights")->values.empty() && + wxGetApp().extruders_edited_cnt()==1 ? + _(L("Color Print")) : + config.option("wiping_volumes_matrix")->values.size() > 1 ? + _(L("Tool")) : + _(L("Feature type")); + + int type = m_choice_view_type->FindString(choice); + if (m_choice_view_type->GetSelection() != type) { + m_choice_view_type->SetSelection(type); + if (0 <= type && type < (int)GCodePreviewData::Extrusion::Num_View_Types) + m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; + m_preferred_color_mode = "feature"; + } +} + void Preview::create_double_slider() { m_slider = new DoubleSlider(this, wxID_ANY, 0, 0, 0, 100); @@ -553,23 +573,13 @@ void Preview::create_double_slider() Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { - auto& config = wxGetApp().preset_bundle->project_config; - ((config.option("colorprint_heights"))->values) = (m_slider->GetTicksValues()); - m_schedule_background_process(); + wxGetApp().preset_bundle->project_config.option("colorprint_heights")->values = m_slider->GetTicksValues(); + m_schedule_background_process(); - const wxString& choise = !config.option("colorprint_heights")->values.empty() ? _(L("Color Print")) : - config.option("wiping_volumes_matrix")->values.size() > 1 ? - _(L("Tool")) : _(L("Feature type")); + update_view_type(); - int type = m_choice_view_type->FindString(choise); - if (m_choice_view_type->GetSelection() != type) { - m_choice_view_type->SetSelection(type); - if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types)) - m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; - m_preferred_color_mode = "feature"; - } - reload_print(); - }); + reload_print(); + }); } // Find an index of a value in a sorted vector, which is in . @@ -787,9 +797,14 @@ void Preview::load_print_as_fff(bool keep_z_range) // Load the real G-code preview. m_canvas->load_gcode_preview(*m_gcode_preview_data, colors); m_loaded = true; - } else + } else { + // disable color change information for multi-material presets + if (wxGetApp().extruders_edited_cnt() > 1) + color_print_values.clear(); + // Load the initial preview based on slices, not the final G-code. m_canvas->load_preview(colors, color_print_values); + } show_hide_ui_elements(gcode_preview_data_valid ? "full" : "simple"); // recalculates zs and update sliders accordingly std::vector zs = m_canvas->get_current_print_zs(true); diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 993e260e41..e86d0e4306 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -127,6 +127,8 @@ public: void move_double_slider(wxKeyEvent& evt); void edit_double_slider(wxKeyEvent& evt); + void update_view_type(); + private: bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a0deb52e32..09ab91ef10 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3947,6 +3947,9 @@ void Plater::reslice() } else if (!p->background_process.empty() && !p->background_process.idle()) p->show_action_buttons(true); + + // update type of preview + p->preview->update_view_type(); } void Plater::reslice_SLA_supports(const ModelObject &object) diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index aed4236749..55044138f5 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -2027,6 +2027,9 @@ void DoubleSlider::draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord void DoubleSlider::draw_ticks(wxDC& dc) { + if (!m_is_enabled_tick_manipulation) + return; + dc.SetPen(m_is_enabled_tick_manipulation ? DARK_GREY_PEN : LIGHT_GREY_PEN ); int height, width; get_size(&width, &height); @@ -2044,6 +2047,9 @@ void DoubleSlider::draw_ticks(wxDC& dc) void DoubleSlider::draw_colored_band(wxDC& dc) { + if (!m_is_enabled_tick_manipulation) + return; + int height, width; get_size(&width, &height); @@ -2113,7 +2119,7 @@ void DoubleSlider::draw_one_layer_icon(wxDC& dc) void DoubleSlider::draw_revert_icon(wxDC& dc) { - if (m_ticks.empty()) + if (m_ticks.empty() || !m_is_enabled_tick_manipulation) return; int width, height; @@ -2218,7 +2224,7 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event) m_selection == ssLower ? correct_lower_value() : correct_higher_value(); if (!m_selection) m_selection = ssHigher; } - else if (is_point_in_rect(pos, m_rect_revert_icon)) { + else if (is_point_in_rect(pos, m_rect_revert_icon) && m_is_enabled_tick_manipulation) { // discard all color changes SetLowerValue(m_min_value); SetHigherValue(m_max_value); From 2a71665de90f043d25a9b8f52281a1fc1072fdf2 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 2 Jul 2019 15:10:59 +0200 Subject: [PATCH 246/627] Follow-up of d07b3fb08b5f8e81899a4772f8219fde49ef2126 -> Show current bed shape for custom bed in bed shape dialog --- src/slic3r/GUI/BedShapeDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index cec0f50677..20aa68ef2a 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -234,8 +234,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points) // This is a custom bed shape, use the polygon provided. m_shape_options_book->SetSelection(SHAPE_CUSTOM); // Copy the polygon to the canvas, make a copy of the array. - m_canvas->m_bed_shape = points->values; - update_shape(); + m_loaded_bed_shape = points->values; + update_shape(); } void BedShapePanel::update_preview() From 87c5e9bbaa264db564bfd8bd6a2f9238791ee2e8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Jul 2019 15:24:40 +0200 Subject: [PATCH 247/627] Partial arrange starts to work again. --- .../include/libnest2d/geometry_traits.hpp | 26 +- src/libnest2d/include/libnest2d/libnest2d.hpp | 5 +- .../include/libnest2d/placers/nfpplacer.hpp | 31 +- src/libslic3r/Arrange.cpp | 339 +++++------------- 4 files changed, 123 insertions(+), 278 deletions(-) diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 6c55d0e3f2..345252f12c 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -875,6 +875,28 @@ inline _Box> boundingBox(const S& sh) return boundingBox(sh, Tag() ); } +template _Box

boundingBox(const _Box

& bb1, const _Box

& bb2 ) +{ + auto& pminc = bb1.minCorner(); + auto& pmaxc = bb1.maxCorner(); + auto& iminc = bb2.minCorner(); + auto& imaxc = bb2.maxCorner(); + P minc, maxc; + + setX(minc, std::min(getX(pminc), getX(iminc))); + setY(minc, std::min(getY(pminc), getY(iminc))); + + setX(maxc, std::max(getX(pmaxc), getX(imaxc))); + setY(maxc, std::max(getY(pmaxc), getY(imaxc))); + return _Box

(minc, maxc); +} + +template +_Box> boundingBox(const S1 &s1, const S2 &s2) +{ + return boundingBox(boundingBox(s1), boundingBox(s2)); +} + template inline double area(const Box& box, const BoxTag& ) { @@ -1060,8 +1082,8 @@ template inline bool isInside(const TB& box, const TC& circ, const BoxTag&, const CircleTag&) { - return isInside(box.minCorner(), circ, BoxTag(), CircleTag()) && - isInside(box.maxCorner(), circ, BoxTag(), CircleTag()); + return isInside(box.minCorner(), circ, PointTag(), CircleTag()) && + isInside(box.maxCorner(), circ, PointTag(), CircleTag()); } template diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index c7d515d49b..ab018f3f8d 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -895,7 +895,10 @@ private: template inline void __execute(TIter from, TIter to) { if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { - item.addOffset(static_cast(std::ceil(min_obj_distance_/2.0))); + auto offs = min_obj_distance_; + if (item.isFixed()) offs *= 0.99; + + item.addOffset(static_cast(std::ceil(offs/2.0))); }); selector_.template packItems( diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index c1f15fe61f..b94443bff5 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -801,7 +801,6 @@ private: // optimize config_.object_function = prev_func; } - } struct Optimum { @@ -816,29 +815,14 @@ private: class Optimizer: public opt::TOptimizer { public: - Optimizer() { + Optimizer(float accuracy = 1.f) { opt::StopCriteria stopcr; - stopcr.max_iterations = 200; + stopcr.max_iterations = unsigned(std::floor(1000 * accuracy)); stopcr.relative_score_difference = 1e-20; this->stopcr_ = stopcr; } }; - static Box boundingBox(const Box& pilebb, const Box& ibb ) { - auto& pminc = pilebb.minCorner(); - auto& pmaxc = pilebb.maxCorner(); - auto& iminc = ibb.minCorner(); - auto& imaxc = ibb.maxCorner(); - Vertex minc, maxc; - - setX(minc, std::min(getX(pminc), getX(iminc))); - setY(minc, std::min(getY(pminc), getY(iminc))); - - setX(maxc, std::max(getX(pmaxc), getX(imaxc))); - setY(maxc, std::max(getY(pmaxc), getY(imaxc))); - return Box(minc, maxc); - } - using Edges = EdgeCache; template> @@ -935,7 +919,7 @@ private: _objfunc = [norm, binbb, pbb, ins_check](const Item& item) { auto ibb = item.boundingBox(); - auto fullbb = boundingBox(pbb, ibb); + auto fullbb = sl::boundingBox(pbb, ibb); double score = pl::distance(ibb.center(), binbb.center()); @@ -1005,14 +989,15 @@ private: auto& rofn = rawobjfunc; auto& nfpoint = getNfpPoint; + float accuracy = config_.accuracy; __parallel::enumerate( cache.corners().begin(), cache.corners().end(), - [&results, &item, &rofn, &nfpoint, ch] + [&results, &item, &rofn, &nfpoint, ch, accuracy] (double pos, size_t n) { - Optimizer solver; + Optimizer solver(accuracy); Item itemcpy = item; auto contour_ofn = [&rofn, &nfpoint, ch, &itemcpy] @@ -1059,10 +1044,10 @@ private: __parallel::enumerate(cache.corners(hidx).begin(), cache.corners(hidx).end(), [&results, &item, &nfpoint, - &rofn, ch, hidx] + &rofn, ch, hidx, accuracy] (double pos, size_t n) { - Optimizer solver; + Optimizer solver(accuracy); Item itmcpy = item; auto hole_ofn = diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index debd29024c..2c4417b4d2 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -65,89 +65,6 @@ using Segment = _Segment; using MultiPolygon = TMultiShape; using PackGroup = _PackGroup; -// Only for debugging. Prints the model object vertices on stdout. -//std::string toString(const Model& model, bool holes = true) { -// std::stringstream ss; - -// ss << "{\n"; - -// for(auto objptr : model.objects) { -// if(!objptr) continue; - -// auto rmesh = objptr->raw_mesh(); - -// for(auto objinst : objptr->instances) { -// if(!objinst) continue; - -// Slic3r::TriangleMesh tmpmesh = rmesh; -// // CHECK_ME -> Is the following correct ? -// tmpmesh.scale(objinst->get_scaling_factor()); -// objinst->transform_mesh(&tmpmesh); -// ExPolygons expolys = tmpmesh.horizontal_projection(); -// for(auto& expoly_complex : expolys) { - -// ExPolygons tmp = expoly_complex.simplify(scaled(1.)); -// if(tmp.empty()) continue; -// ExPolygon expoly = tmp.front(); -// expoly.contour.make_clockwise(); -// for(auto& h : expoly.holes) h.make_counter_clockwise(); - -// ss << "\t{\n"; -// ss << "\t\t{\n"; - -// for(auto v : expoly.contour.points) ss << "\t\t\t{" -// << v(0) << ", " -// << v(1) << "},\n"; -// { -// auto v = expoly.contour.points.front(); -// ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; -// } -// ss << "\t\t},\n"; - -// // Holes: -// ss << "\t\t{\n"; -// if(holes) for(auto h : expoly.holes) { -// ss << "\t\t\t{\n"; -// for(auto v : h.points) ss << "\t\t\t\t{" -// << v(0) << ", " -// << v(1) << "},\n"; -// { -// auto v = h.points.front(); -// ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; -// } -// ss << "\t\t\t},\n"; -// } -// ss << "\t\t},\n"; - -// ss << "\t},\n"; -// } -// } -// } - -// ss << "}\n"; - -// return ss.str(); -//} - -// Debugging: Save model to svg file. -//void toSVG(SVG& svg, const Model& model) { -// for(auto objptr : model.objects) { -// if(!objptr) continue; - -// auto rmesh = objptr->raw_mesh(); - -// for(auto objinst : objptr->instances) { -// if(!objinst) continue; - -// Slic3r::TriangleMesh tmpmesh = rmesh; -// tmpmesh.scale(objinst->get_scaling_factor()); -// objinst->transform_mesh(&tmpmesh); -// ExPolygons expolys = tmpmesh.horizontal_projection(); -// svg.draw(expolys); -// } -// } -//} - namespace bgi = boost::geometry::index; using SpatElement = std::pair; @@ -156,21 +73,6 @@ using ItemGroup = std::vector>; const double BIG_ITEM_TRESHOLD = 0.02; -Box boundingBox(const Box& pilebb, const Box& ibb ) { - auto& pminc = pilebb.minCorner(); - auto& pmaxc = pilebb.maxCorner(); - auto& iminc = ibb.minCorner(); - auto& imaxc = ibb.maxCorner(); - PointImpl minc, maxc; - - setX(minc, std::min(getX(pminc), getX(iminc))); - setY(minc, std::min(getY(pminc), getY(iminc))); - - setX(maxc, std::max(getX(pmaxc), getX(imaxc))); - setY(maxc, std::max(getY(pmaxc), getY(imaxc))); - return Box(minc, maxc); -} - // Fill in the placer algorithm configuration with values carefully chosen for // Slic3r. template @@ -194,13 +96,18 @@ void fillConfig(PConf& pcfg) { pcfg.parallel = true; } -// Type trait for an arranger class for different bin types (box, circle, -// polygon, etc...) -//template -//class AutoArranger {}; - -template clppr::IntPoint center(const Bin& bin) { return bin.center(); } -template<> clppr::IntPoint center(const clppr::Polygon &bin) { return sl::boundingBox(bin).center(); } +// Apply penality to object function result. This is used only when alignment +// after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN) +double fixed_overfit(const std::tuple& result, const Box &binbb) +{ + double score = std::get<0>(result); + Box pilebb = std::get<1>(result); + Box fullbb = sl::boundingBox(pilebb, binbb); + double diff = fullbb.area() - binbb.area(); + if(diff > 0) score += diff; + + return score; +} // A class encapsulating the libnest2d Nester class and extending it with other // management and spatial index structures for acceleration. @@ -218,8 +125,7 @@ protected: Packer m_pck; PConfig m_pconf; // Placement configuration TBin m_bin; - double m_bin_area; // caching - PointImpl m_bincenter; // caching + double m_bin_area; SpatIndex m_rtree; // spatial index for the normal (bigger) objects SpatIndex m_smallsrtree; // spatial index for only the smaller items double m_norm; // A coefficient to scale distances @@ -234,7 +140,7 @@ protected: // as it possibly can be but at the same time, it has to provide // reasonable results. std::tuple - objfunc(const Item &item ) + objfunc(const Item &item, const clppr::IntPoint &bincenter) { const double bin_area = m_bin_area; const SpatIndex& spatindex = m_rtree; @@ -250,7 +156,7 @@ protected: auto ibb = sl::boundingBox(item.transformedShape()); // Calculate the full bounding box of the pile with the candidate item - auto fullbb = boundingBox(m_pilebb, ibb); + auto fullbb = sl::boundingBox(m_pilebb, ibb); // The bounding box of the big items (they will accumulate in the center // of the pile @@ -286,7 +192,7 @@ protected: // The smalles distance from the arranged pile center: double dist = *(std::min_element(dists.begin(), dists.end())) / m_norm; - double bindist = pl::distance(ibb.center(), m_bincenter) / m_norm; + double bindist = pl::distance(ibb.center(), bincenter) / m_norm; dist = 0.8*dist + 0.2*bindist; // Density is the pack density: how big is the arranged pile @@ -334,7 +240,7 @@ protected: Item& p = m_items[idx]; auto parea = p.area(); if(std::abs(1.0 - parea/item.area()) < 1e-6) { - auto bb = boundingBox(p.boundingBox(), ibb); + auto bb = sl::boundingBox(p.boundingBox(), ibb); auto bbarea = bb.area(); auto ascore = 1.0 - (item.area() + parea)/bbarea; @@ -370,9 +276,7 @@ public: std::function stopcond) : m_pck(bin, dist) , m_bin(bin) - , m_bin_area(sl::area(bin)) - , m_bincenter(center(bin)) - , m_norm(std::sqrt(m_bin_area)) + , m_norm(std::sqrt(sl::area(bin))) { fillConfig(m_pconf); @@ -391,10 +295,10 @@ public: m_rtree.clear(); m_smallsrtree.clear(); - + // We will treat big items (compared to the print bed) differently auto isBig = [this](double a) { - return a/m_bin_area > BIG_ITEM_TRESHOLD ; + return a / m_bin_area > BIG_ITEM_TRESHOLD ; }; for(unsigned idx = 0; idx < items.size(); ++idx) { @@ -419,8 +323,11 @@ public: inline void preload(std::vector& fixeditems) { m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; -// m_pconf.object_function = nullptr; // drop the special objectfunction -// m_pck.preload(pg); + auto bb = sl::boundingBox(m_bin); + auto bbcenter = bb.center(); + m_pconf.object_function = [this, bb, bbcenter](const Item &item) { + return fixed_overfit(objfunc(item, bbcenter), bb); + }; // Build the rtree for queries to work @@ -442,34 +349,39 @@ public: } }; + + template<> std::function AutoArranger::get_objfn() { - return [this](const Item &itm) { - auto result = objfunc(itm); + auto bincenter = m_bin.center(); + + return [this, bincenter](const Item &itm) { + auto result = objfunc(itm, bincenter); double score = std::get<0>(result); auto& fullbb = std::get<1>(result); - + double miss = Placer::overfit(fullbb, m_bin); miss = miss > 0? miss : 0; score += miss*miss; - + return score; }; } template<> std::function AutoArranger::get_objfn() { - return [this](const Item &item) { + auto bincenter = m_bin.center(); + return [this, bincenter](const Item &item) { + + auto result = objfunc(item, bincenter); - auto result = objfunc(item); - double score = std::get<0>(result); - + auto isBig = [this](const Item& itm) { - return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; + return itm.area() / m_bin_area > BIG_ITEM_TRESHOLD ; }; - + if(isBig(item)) { auto mp = m_merged_pile; mp.push_back(item.transformedShape()); @@ -478,109 +390,26 @@ template<> std::function AutoArranger::get_objfn() if(miss < 0) miss = 0; score += miss*miss; } - + return score; }; } -template<> std::function AutoArranger::get_objfn() +// Specialization for a generalized polygon. +// Warning: this is unfinished business. It may or may not work. +template<> +std::function AutoArranger::get_objfn() { - return [this] (const Item &item) { return std::get<0>(objfunc(item)); }; + auto bincenter = sl::boundingBox(m_bin).center(); + return [this, bincenter](const Item &item) { + return std::get<0>(objfunc(item, bincenter)); + }; } -// Arranger specialization for a Box shaped bin. -//template<> class AutoArranger: public _ArrBase { -//public: - -// AutoArranger(const Box& bin, Distance dist, -// std::function progressind = [](unsigned){}, -// std::function stopcond = [](){return false;}): -// _ArrBase(bin, dist, progressind, stopcond) -// { - -// // Here we set up the actual object function that calls the common -// // object function for all bin shapes than does an additional inside -// // check for the arranged pile. -// m_pconf.object_function = [this, bin](const Item &item) { - -// auto result = objfunc(bin.center(), item); - -// double score = std::get<0>(result); -// auto& fullbb = std::get<1>(result); - -// double miss = Placer::overfit(fullbb, bin); -// miss = miss > 0? miss : 0; -// score += miss*miss; - -// return score; -// }; - -// m_pck.configure(m_pconf); -// } -//}; - inline Circle to_lnCircle(const CircleBed& circ) { return Circle({circ.center()(0), circ.center()(1)}, circ.radius()); } -//// Arranger specialization for circle shaped bin. -//template<> class AutoArranger: public _ArrBase { -//public: - -// AutoArranger(const Circle& bin, Distance dist, -// std::function progressind = [](unsigned){}, -// std::function stopcond = [](){return false;}): -// _ArrBase(bin, dist, progressind, stopcond) { - -// // As with the box, only the inside check is different. -// m_pconf.object_function = [this, &bin](const Item &item) { - -// auto result = objfunc(bin.center(), item); - -// double score = std::get<0>(result); - -// auto isBig = [this](const Item& itm) { -// return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; -// }; - -// if(isBig(item)) { -// auto mp = m_merged_pile; -// mp.push_back(item.transformedShape()); -// auto chull = sl::convexHull(mp); -// double miss = Placer::overfit(chull, bin); -// if(miss < 0) miss = 0; -// score += miss*miss; -// } - -// return score; -// }; - -// m_pck.configure(m_pconf); -// } -//}; - -// Arranger specialization for a generalized polygon. -// Warning: this is unfinished business. It may or may not work. -//template<> class AutoArranger: public _ArrBase { -//public: -// AutoArranger(const PolygonImpl& bin, Distance dist, -// std::function progressind = [](unsigned){}, -// std::function stopcond = [](){return false;}): -// _ArrBase(bin, dist, progressind, stopcond) -// { -// m_pconf.object_function = [this, &bin] (const Item &item) { - -// auto binbb = sl::boundingBox(bin); -// auto result = objfunc(binbb.center(), item); -// double score = std::get<0>(result); - -// return score; -// }; - -// m_pck.configure(m_pconf); -// } -//}; - // Get the type of bed geometry from a simple vector of points. BedShapeHint bedShape(const Polyline &bed) { BedShapeHint ret; @@ -670,40 +499,46 @@ PackGroup _arrange(std::vector & shapes, { AutoArranger arranger{bin, minobjd, prind, stopfn}; - for(auto it = excludes.begin(); it != excludes.end(); ++it) - if (!sl::isInside(it->transformedShape(), bin)) - it = excludes.erase(it); - - // If there is something on the plate - if(!excludes.empty()) { -// arranger.preload(preshapes); - auto binbb = sl::boundingBox(bin); - - // Try to put the first item to the center, as the arranger will not - // do this for us. - for (auto it = shapes.begin(); it != shapes.end(); ++it) { - Item &itm = *it; - auto ibb = itm.boundingBox(); - auto d = binbb.center() - ibb.center(); - itm.translate(d); + auto it = excludes.begin(); + while (it != excludes.end()) + sl::isInside(it->transformedShape(), bin) ? + ++it : it = excludes.erase(it); - if (!arranger.is_colliding(itm)) { - itm.markAsFixed(); -// arranger.preload({{itm}}); - - // Write the transformation data into the item. The callback - // was set on the instantiation of Item and calls the - // Arrangeable interface. - it->callApplyFunction(0); - - // Remove this item, as it is arranged now - it = shapes.erase(it); - break; + // If there is something on the plate + if(!excludes.empty()) + { + arranger.preload(excludes); + auto binbb = sl::boundingBox(bin); + + // Try to put the first item to the center, as the arranger + // will not do this for us. + for (auto it = shapes.begin(); it != shapes.end(); ++it) { + Item &itm = *it; + auto ibb = itm.boundingBox(); + auto d = binbb.center() - ibb.center(); + itm.translate(d); + + if (!arranger.is_colliding(itm)) { + itm.markAsFixed(); + + // Write the transformation data into the item. The + // callback was set on the instantiation of Item and + // calls the Arrangeable interface. + it->callApplyFunction(0); + + // Remove this item, as it is arranged now + it = shapes.erase(it); + break; + } } } - } + + std::vector> inp; + inp.reserve(shapes.size() + excludes.size()); + for (auto &itm : shapes ) inp.emplace_back(itm); + for (auto &itm : excludes) inp.emplace_back(itm); - return arranger(shapes.begin(), shapes.end()); + return arranger(inp.begin(), inp.end()); } inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) @@ -713,8 +548,8 @@ inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) // The final client function for arrangement. A progress indicator and // a stop predicate can be also be passed to control the process. -bool arrange(ArrangeablePtrs & arrangables, - const ArrangeablePtrs & excludes, +bool arrange(ArrangeablePtrs & arrangables, + const ArrangeablePtrs & excludes, coord_t min_obj_distance, const BedShapeHint & bedhint, std::function progressind, From 0bcad2a5c52b774eba6472077cbe6cb786c20f27 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 2 Jul 2019 15:26:11 +0200 Subject: [PATCH 248/627] Fix for the last commit --- src/slic3r/GUI/GUI_ObjectLayers.cpp | 22 ++++++++++------------ src/slic3r/GUI/GUI_ObjectLayers.hpp | 7 +++---- src/slic3r/GUI/GUI_ObjectList.cpp | 20 ++++++++++++++++++-- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index f9d3a89567..b30d3ecd31 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -197,25 +197,17 @@ void ObjectLayers::update_layers_list() // Add new control according to the selected item if (type & itLayerRoot) - { - wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); - m_selectable_range = { 0.0, 0.0 }; create_layers_list(); - } else - { - t_layer_height_range range = objects_ctrl->GetModel()->GetLayerRangeByItem(item); - create_layer(range); - update_scene_from_editor_selection(range, etLayerHeight); - } + create_layer(objects_ctrl->GetModel()->GetLayerRangeByItem(item)); m_parent->Layout(); } -void ObjectLayers::update_scene_from_editor_selection(const t_layer_height_range& range, EditorType type) const +void ObjectLayers::update_scene_from_editor_selection() const { // needed to show the visual hints in 3D scene - wxGetApp().plater()->canvas3D()->handle_layers_data_focus_event(range, type); + wxGetApp().plater()->canvas3D()->handle_layers_data_focus_event(m_selectable_range, m_selection_type); } void ObjectLayers::UpdateAndShow(const bool show) @@ -232,6 +224,12 @@ void ObjectLayers::msw_rescale() m_bmp_add.msw_rescale(); } +void ObjectLayers::reset_selection() +{ + m_selectable_range = { 0.0, 0.0 }; + m_selection_type = etLayerHeight; +} + LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, const wxString& value, EditorType type, @@ -297,7 +295,7 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent, this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e) { set_focus_data(); - parent->update_scene_from_editor_selection(parent->get_selectable_range(), parent->get_selection_type()); + parent->update_scene_from_editor_selection(); e.Skip(); }, this->GetId()); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 253cbf0a4f..f274183e2b 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -73,13 +73,12 @@ public: void create_layers_list(); void update_layers_list(); - void update_scene_from_editor_selection(const t_layer_height_range& range, EditorType type) const; + void update_scene_from_editor_selection() const; void UpdateAndShow(const bool show) override; void msw_rescale(); - - const t_layer_height_range& get_selectable_range() const { return m_selectable_range; } - EditorType get_selection_type() const { return m_selection_type; } + void reset_selection(); + void set_selectable_range(const t_layer_height_range& range) { m_selectable_range = range; } friend class LayerRangeEditor; }; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index af1a9908d1..5c421705bb 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -578,6 +578,22 @@ void ObjectList::selection_changed() wxPostEvent(this, event); } + if (const wxDataViewItem item = GetSelection()) + { + const ItemType type = m_objects_model->GetItemType(item); + // to correct visual hints for layers editing on the Scene + if (type & (itLayer|itLayerRoot)) { + wxGetApp().obj_layers()->reset_selection(); + + if (type & itLayerRoot) + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); + else { + wxGetApp().obj_layers()->set_selectable_range(m_objects_model->GetLayerRangeByItem(item)); + wxGetApp().obj_layers()->update_scene_from_editor_selection(); + } + } + } + part_selection_changed(); } @@ -1864,7 +1880,7 @@ void ObjectList::layers_editing() // set some default value if (ranges.empty()) - ranges[{ 0.0f, 0.6f }] = get_default_layer_config(obj_idx); + ranges[{ 0.0f, 2.0f }] = get_default_layer_config(obj_idx); // create layer root item layers_item = add_layer_root_item(obj_item); @@ -2330,7 +2346,7 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre if (current_range == last_range) { - const t_layer_height_range& new_range = { last_range.second, last_range.second + 0.5f }; + const t_layer_height_range& new_range = { last_range.second, last_range.second + 2.0f }; ranges[new_range] = get_default_layer_config(obj_idx); add_layer_item(new_range, layers_item); } From 90daffccf2b5fa0a5ab21cec8581e8bf1e92e72f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 2 Jul 2019 15:49:18 +0200 Subject: [PATCH 249/627] View dependent order of rendering for layers editing visual hints to keep the correct transparency --- src/slic3r/GUI/Plater.cpp | 5 +++++ src/slic3r/GUI/Plater.hpp | 2 ++ src/slic3r/GUI/Selection.cpp | 30 +++++++++++++++++------------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 98b7c1303c..0a154034fb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4273,6 +4273,11 @@ void Plater::msw_rescale() GetParent()->Layout(); } +const Camera& Plater::get_camera() const +{ + return p->camera; +} + bool Plater::can_delete() const { return p->can_delete(); } bool Plater::can_delete_all() const { return p->can_delete_all(); } bool Plater::can_increase_instances() const { return p->can_increase_instances(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index c3f8927c03..34fd7984e2 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -220,6 +220,8 @@ public: void msw_rescale(); + const Camera& get_camera() const; + private: struct priv; std::unique_ptr p; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 9939c291e0..bea94e2e6f 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -6,7 +6,8 @@ #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectList.hpp" #include "Gizmos/GLGizmoBase.hpp" -#include "slic3r/GUI/3DScene.hpp" +#include "3DScene.hpp" +#include "Camera.hpp" #include @@ -1761,33 +1762,36 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co const float min_y = box.min(1) - Margin; const float max_y = box.max(1) + Margin; + // view dependend order of rendering to keep correct transparency + bool camera_on_top = wxGetApp().plater()->get_camera().get_theta() <= 90.0f; + float z1 = camera_on_top ? min_z : max_z; + float z2 = camera_on_top ? max_z : min_z; + glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_CULL_FACE)); glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - // Draw the min_z plane ::glBegin(GL_QUADS); - if (type == 1) + if ((camera_on_top && (type == 1)) || (!camera_on_top && (type == 2))) ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); else ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - ::glVertex3f(min_x, min_y, min_z); - ::glVertex3f(max_x, min_y, min_z); - ::glVertex3f(max_x, max_y, min_z); - ::glVertex3f(min_x, max_y, min_z); + ::glVertex3f(min_x, min_y, z1); + ::glVertex3f(max_x, min_y, z1); + ::glVertex3f(max_x, max_y, z1); + ::glVertex3f(min_x, max_y, z1); glsafe(::glEnd()); - // Draw the max_z plane ::glBegin(GL_QUADS); - if (type == 2) + if ((camera_on_top && (type == 2)) || (!camera_on_top && (type == 1))) ::glColor4f(1.0f, 0.38f, 0.0f, 1.0f); else ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - ::glVertex3f(min_x, min_y, max_z); - ::glVertex3f(max_x, min_y, max_z); - ::glVertex3f(max_x, max_y, max_z); - ::glVertex3f(min_x, max_y, max_z); + ::glVertex3f(min_x, min_y, z2); + ::glVertex3f(max_x, min_y, z2); + ::glVertex3f(max_x, max_y, z2); + ::glVertex3f(min_x, max_y, z2); glsafe(::glEnd()); glsafe(::glEnable(GL_CULL_FACE)); From 320f2ecefd953cdab7089c0037bc29d0471fed35 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 2 Jul 2019 16:08:13 +0200 Subject: [PATCH 250/627] Try to deal with infinite bin. --- .../include/libnest2d/geometry_traits.hpp | 532 +++++++++--------- 1 file changed, 280 insertions(+), 252 deletions(-) diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 345252f12c..8b87229c08 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -30,11 +30,11 @@ template struct ShapeTag { using Type = typename Shape::Tag; }; template using Tag = typename ShapeTag>::Type; /// Meta function to derive the contour type for a polygon which could be itself -template struct ContourType { using Type = RawShape; }; +template struct ContourType { using Type = S; }; -/// TContour instead of `typename ContourType::type` -template -using TContour = typename ContourType>::Type; +/// TContour instead of `typename ContourType::type` +template +using TContour = typename ContourType>::Type; /// Getting the type of point structure used by a shape. template struct PointType { @@ -83,12 +83,12 @@ template struct ComputeType { template using TCompute = typename ComputeType>::Type; /// A meta function to derive a container type for holes in a polygon -template -struct HolesContainer { using Type = std::vector>; }; +template +struct HolesContainer { using Type = std::vector>; }; -/// Shorthand for `typename HolesContainer::Type` -template -using THolesContainer = typename HolesContainer>::Type; +/// Shorthand for `typename HolesContainer::Type` +template +using THolesContainer = typename HolesContainer>::Type; /* * TContour, TPoint, TCoord and TCompute should be usable for any type for which @@ -132,7 +132,7 @@ enum class Orientation { COUNTER_CLOCKWISE }; -template +template struct OrientationType { // Default Polygon orientation that the library expects @@ -146,45 +146,46 @@ template inline /*constexpr*/ bool is_clockwise() { /** * \brief A point pair base class for other point pairs (segment, box, ...). - * \tparam RawPoint The actual point type to use. + * \tparam P The actual point type to use. */ -template +template struct PointPair { - RawPoint p1; - RawPoint p2; + P p1; + P p2; }; /** * \brief An abstraction of a box; */ -template -class _Box: PointPair { - using PointPair::p1; - using PointPair::p2; +template +class _Box: PointPair

{ + using PointPair

::p1; + using PointPair

::p2; public: using Tag = BoxTag; - using PointType = RawPoint; + using PointType = P; - inline _Box() = default; - inline _Box(const RawPoint& p, const RawPoint& pp): - PointPair({p, pp}) {} + inline _Box(const P& p = {TCoord

(0), TCoord

(0)}); + inline _Box(const P& p, const P& pp): + PointPair

({p, pp}) {} + + inline _Box(TCoord

width, TCoord

height, + const P& p = {TCoord

(0), TCoord

(0)});/*: + _Box(p, P{width, height}) {}*/ - inline _Box(TCoord width, TCoord height): - _Box(RawPoint{0, 0}, RawPoint{width, height}) {} + inline const P& minCorner() const BP2D_NOEXCEPT { return p1; } + inline const P& maxCorner() const BP2D_NOEXCEPT { return p2; } - inline const RawPoint& minCorner() const BP2D_NOEXCEPT { return p1; } - inline const RawPoint& maxCorner() const BP2D_NOEXCEPT { return p2; } + inline P& minCorner() BP2D_NOEXCEPT { return p1; } + inline P& maxCorner() BP2D_NOEXCEPT { return p2; } - inline RawPoint& minCorner() BP2D_NOEXCEPT { return p1; } - inline RawPoint& maxCorner() BP2D_NOEXCEPT { return p2; } + inline TCoord

width() const BP2D_NOEXCEPT; + inline TCoord

height() const BP2D_NOEXCEPT; - inline TCoord width() const BP2D_NOEXCEPT; - inline TCoord height() const BP2D_NOEXCEPT; + inline P center() const BP2D_NOEXCEPT; - inline RawPoint center() const BP2D_NOEXCEPT; - - template> + template> inline Unit area() const BP2D_NOEXCEPT { return Unit(width())*height(); } @@ -194,20 +195,20 @@ template struct PointType<_Box> { using Type = typename _Box::PointType; }; -template +template class _Circle { - RawPoint center_; + P center_; double radius_ = 0; public: using Tag = CircleTag; - using PointType = RawPoint; + using PointType = P; _Circle() = default; - _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} + _Circle(const P& center, double r): center_(center), radius_(r) {} - inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; } - inline void center(const RawPoint& c) { center_ = c; } + inline const P& center() const BP2D_NOEXCEPT { return center_; } + inline void center(const P& c) { center_ = c; } inline double radius() const BP2D_NOEXCEPT { return radius_; } inline void radius(double r) { radius_ = r; } @@ -224,38 +225,38 @@ template struct PointType<_Circle> { /** * \brief An abstraction of a directed line segment with two points. */ -template -class _Segment: PointPair { - using PointPair::p1; - using PointPair::p2; +template +class _Segment: PointPair

{ + using PointPair

::p1; + using PointPair

::p2; mutable Radians angletox_ = std::nan(""); public: - using PointType = RawPoint; + using PointType = P; inline _Segment() = default; - inline _Segment(const RawPoint& p, const RawPoint& pp): - PointPair({p, pp}) {} + inline _Segment(const P& p, const P& pp): + PointPair

({p, pp}) {} /** * @brief Get the first point. * @return Returns the starting point. */ - inline const RawPoint& first() const BP2D_NOEXCEPT { return p1; } + inline const P& first() const BP2D_NOEXCEPT { return p1; } /** * @brief The end point. * @return Returns the end point of the segment. */ - inline const RawPoint& second() const BP2D_NOEXCEPT { return p2; } + inline const P& second() const BP2D_NOEXCEPT { return p2; } - inline void first(const RawPoint& p) BP2D_NOEXCEPT + inline void first(const P& p) BP2D_NOEXCEPT { angletox_ = std::nan(""); p1 = p; } - inline void second(const RawPoint& p) BP2D_NOEXCEPT { + inline void second(const P& p) BP2D_NOEXCEPT { angletox_ = std::nan(""); p2 = p; } @@ -263,7 +264,7 @@ public: inline Radians angleToXaxis() const; /// The length of the segment in the measure of the coordinate system. - template> inline Unit sqlength() const; + template> inline Unit sqlength() const; }; @@ -275,42 +276,42 @@ template struct PointType<_Segment> { // used in friend declarations. namespace pointlike { -template -inline TCoord x(const RawPoint& p) +template +inline TCoord

x(const P& p) { return p.x(); } -template -inline TCoord y(const RawPoint& p) +template +inline TCoord

y(const P& p) { return p.y(); } -template -inline TCoord& x(RawPoint& p) +template +inline TCoord

& x(P& p) { return p.x(); } -template -inline TCoord& y(RawPoint& p) +template +inline TCoord

& y(P& p) { return p.y(); } -template> -inline Unit squaredDistance(const RawPoint& p1, const RawPoint& p2) +template> +inline Unit squaredDistance(const P& p1, const P& p2) { auto x1 = Unit(x(p1)), y1 = Unit(y(p1)), x2 = Unit(x(p2)), y2 = Unit(y(p2)); Unit a = (x2 - x1), b = (y2 - y1); return a * a + b * b; } -template -inline double distance(const RawPoint& p1, const RawPoint& p2) +template +inline double distance(const P& p1, const P& p2) { - return std::sqrt(squaredDistance(p1, p2)); + return std::sqrt(squaredDistance(p1, p2)); } // create perpendicular vector @@ -339,9 +340,9 @@ inline Unit magnsq(const Pt& p) return Unit(x(p)) * x(p) + Unit(y(p)) * y(p); } -template> +template> inline std::pair horizontalDistance( - const RawPoint& p, const _Segment& s) + const P& p, const _Segment

& s) { namespace pl = pointlike; auto x = Unit(pl::x(p)), y = Unit(pl::y(p)); @@ -364,9 +365,9 @@ inline std::pair horizontalDistance( return {ret, true}; } -template> +template> inline std::pair verticalDistance( - const RawPoint& p, const _Segment& s) + const P& p, const _Segment

& s) { namespace pl = pointlike; auto x = Unit(pl::x(p)), y = Unit(pl::y(p)); @@ -390,42 +391,42 @@ inline std::pair verticalDistance( } } -template -TCoord _Box::width() const BP2D_NOEXCEPT +template +TCoord

_Box

::width() const BP2D_NOEXCEPT { return pointlike::x(maxCorner()) - pointlike::x(minCorner()); } -template -TCoord _Box::height() const BP2D_NOEXCEPT +template +TCoord

_Box

::height() const BP2D_NOEXCEPT { return pointlike::y(maxCorner()) - pointlike::y(minCorner()); } -template -TCoord getX(const RawPoint& p) { return pointlike::x(p); } +template +TCoord

getX(const P& p) { return pointlike::x

(p); } -template -TCoord getY(const RawPoint& p) { return pointlike::y(p); } +template +TCoord

getY(const P& p) { return pointlike::y

(p); } -template -void setX(RawPoint& p, const TCoord& val) +template +void setX(P& p, const TCoord

& val) { - pointlike::x(p) = val; + pointlike::x

(p) = val; } -template -void setY(RawPoint& p, const TCoord& val) +template +void setY(P& p, const TCoord

& val) { - pointlike::y(p) = val; + pointlike::y

(p) = val; } -template -inline Radians _Segment::angleToXaxis() const +template +inline Radians _Segment

::angleToXaxis() const { if(std::isnan(static_cast(angletox_))) { - TCoord dx = getX(second()) - getX(first()); - TCoord dy = getY(second()) - getY(first()); + TCoord

dx = getX(second()) - getX(first()); + TCoord

dy = getY(second()) - getY(first()); double a = std::atan2(dy, dx); auto s = std::signbit(a); @@ -436,21 +437,48 @@ inline Radians _Segment::angleToXaxis() const return angletox_; } -template +template template -inline Unit _Segment::sqlength() const +inline Unit _Segment

::sqlength() const { - return pointlike::squaredDistance(first(), second()); + return pointlike::squaredDistance(first(), second()); } -template -inline RawPoint _Box::center() const BP2D_NOEXCEPT { +template +enable_if_t::value, T> modulo(const T &v, const T &m) +{ + return 0; +} +template +enable_if_t::value, T> modulo(const T &v, const T &m) +{ + return v % m; +} + +template +inline _Box

::_Box(TCoord

width, TCoord

height, const P & center) : + PointPair

({center - P{width / 2, height / 2}, + center + P{width / 2, height / 2} + + P{modulo(width, TCoord

(2)), + modulo(height, TCoord

(2))}}) {} + +template +inline _Box

::_Box(const P& center) { + using C = TCoord

; + TCoord

M = std::max(getX(center), getY(center)) - + std::numeric_limits::lowest(); + maxCorner() = center + P{M, M}; + minCorner() = center - P{M, M}; +} + +template +inline P _Box

::center() const BP2D_NOEXCEPT { auto& minc = minCorner(); auto& maxc = maxCorner(); - using Coord = TCoord; + using Coord = TCoord

; - RawPoint ret = { // No rounding here, we dont know if these are int coords + P ret = { // No rounding here, we dont know if these are int coords Coord( (getX(minc) + getX(maxc)) / Coord(2) ), Coord( (getY(minc) + getY(maxc)) / Coord(2) ) }; @@ -467,76 +495,76 @@ enum class Formats { // used in friend declarations and can be aliased at class scope. namespace shapelike { -template -inline RawShape create(const TContour& contour, - const THolesContainer& holes) +template +inline S create(const TContour& contour, + const THolesContainer& holes) { - return RawShape(contour, holes); + return S(contour, holes); } -template -inline RawShape create(TContour&& contour, - THolesContainer&& holes) +template +inline S create(TContour&& contour, + THolesContainer&& holes) { - return RawShape(contour, holes); + return S(contour, holes); } -template -inline RawShape create(const TContour& contour) +template +inline S create(const TContour& contour) { - return create(contour, {}); + return create(contour, {}); } -template -inline RawShape create(TContour&& contour) +template +inline S create(TContour&& contour) { - return create(contour, {}); + return create(contour, {}); } -template -inline THolesContainer& holes(RawShape& /*sh*/) +template +inline THolesContainer& holes(S& /*sh*/) { - static THolesContainer empty; + static THolesContainer empty; return empty; } -template -inline const THolesContainer& holes(const RawShape& /*sh*/) +template +inline const THolesContainer& holes(const S& /*sh*/) { - static THolesContainer empty; + static THolesContainer empty; return empty; } -template -inline TContour& hole(RawShape& sh, unsigned long idx) +template +inline TContour& hole(S& sh, unsigned long idx) { return holes(sh)[idx]; } -template -inline const TContour& hole(const RawShape& sh, unsigned long idx) +template +inline const TContour& hole(const S& sh, unsigned long idx) { return holes(sh)[idx]; } -template -inline size_t holeCount(const RawShape& sh) +template +inline size_t holeCount(const S& sh) { return holes(sh).size(); } -template -inline TContour& contour(RawShape& sh) +template +inline TContour& contour(S& sh) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::contour() unimplemented!"); return sh; } -template -inline const TContour& contour(const RawShape& sh) +template +inline const TContour& contour(const S& sh) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::contour() unimplemented!"); return sh; } @@ -548,71 +576,71 @@ inline void reserve(RawPath& p, size_t vertex_capacity, const PathTag&) p.reserve(vertex_capacity); } -template -inline void addVertex(RawShape& sh, const PathTag&, Args...args) +template +inline void addVertex(S& sh, const PathTag&, Args...args) { sh.emplace_back(std::forward(args)...); } -template -inline void foreachVertex(RawShape& sh, Fn fn, const PathTag&) { +template +inline void foreachVertex(S& sh, Fn fn, const PathTag&) { std::for_each(sh.begin(), sh.end(), fn); } -template -inline typename RawShape::iterator begin(RawShape& sh, const PathTag&) +template +inline typename S::iterator begin(S& sh, const PathTag&) { return sh.begin(); } -template -inline typename RawShape::iterator end(RawShape& sh, const PathTag&) +template +inline typename S::iterator end(S& sh, const PathTag&) { return sh.end(); } -template -inline typename RawShape::const_iterator -cbegin(const RawShape& sh, const PathTag&) +template +inline typename S::const_iterator +cbegin(const S& sh, const PathTag&) { return sh.cbegin(); } -template -inline typename RawShape::const_iterator -cend(const RawShape& sh, const PathTag&) +template +inline typename S::const_iterator +cend(const S& sh, const PathTag&) { return sh.cend(); } -template -inline std::string toString(const RawShape& /*sh*/) +template +inline std::string toString(const S& /*sh*/) { return ""; } -template -inline std::string serialize(const RawShape& /*sh*/, double /*scale*/=1) +template +inline std::string serialize(const S& /*sh*/, double /*scale*/=1) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::serialize() unimplemented!"); return ""; } -template -inline void unserialize(RawShape& /*sh*/, const std::string& /*str*/) +template +inline void unserialize(S& /*sh*/, const std::string& /*str*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::unserialize() unimplemented!"); } template inline Unit area(const Cntr& poly, const PathTag& ); -template -inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) +template +inline bool intersects(const S& /*sh*/, const S& /*sh*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::intersects() unimplemented!"); return false; } @@ -633,29 +661,29 @@ inline bool isInside(const TGuest&, const THost&, return false; } -template -inline bool touches( const RawShape& /*shape*/, - const RawShape& /*shape*/) +template +inline bool touches( const S& /*shape*/, + const S& /*shape*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::touches(shape, shape) unimplemented!"); return false; } -template -inline bool touches( const TPoint& /*point*/, - const RawShape& /*shape*/) +template +inline bool touches( const TPoint& /*point*/, + const S& /*shape*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::touches(point, shape) unimplemented!"); return false; } -template -inline _Box> boundingBox(const RawShape& /*sh*/, +template +inline _Box> boundingBox(const S& /*sh*/, const PathTag&) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::boundingBox(shape) unimplemented!"); } @@ -667,34 +695,34 @@ boundingBox(const RawShapes& /*sh*/, const MultiPolygonTag&) "shapelike::boundingBox(shapes) unimplemented!"); } -template -inline RawShape convexHull(const RawShape& sh, const PathTag&); +template +inline S convexHull(const S& sh, const PathTag&); template inline S convexHull(const RawShapes& sh, const MultiPolygonTag&); -template -inline void rotate(RawShape& /*sh*/, const Radians& /*rads*/) +template +inline void rotate(S& /*sh*/, const Radians& /*rads*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::rotate() unimplemented!"); } -template -inline void translate(RawShape& /*sh*/, const RawPoint& /*offs*/) +template +inline void translate(S& /*sh*/, const P& /*offs*/) { - static_assert(always_false::value, + static_assert(always_false::value, "shapelike::translate() unimplemented!"); } -template -inline void offset(RawShape& /*sh*/, TCoord> /*distance*/) +template +inline void offset(S& /*sh*/, TCoord> /*distance*/) { dout() << "The current geometry backend does not support offsetting!\n"; } -template -inline std::pair isValid(const RawShape& /*sh*/) +template +inline std::pair isValid(const S& /*sh*/) { return {false, "shapelike::isValid() unimplemented!"}; } @@ -735,56 +763,56 @@ template inline bool isConvex(const RawPath& sh, const PathTag&) // No need to implement these // ***************************************************************************** -template -inline typename TContour::iterator -begin(RawShape& sh, const PolygonTag&) +template +inline typename TContour::iterator +begin(S& sh, const PolygonTag&) { return begin(contour(sh), PathTag()); } -template // Tag dispatcher -inline auto begin(RawShape& sh) -> decltype(begin(sh, Tag())) +template // Tag dispatcher +inline auto begin(S& sh) -> decltype(begin(sh, Tag())) { - return begin(sh, Tag()); + return begin(sh, Tag()); } -template -inline typename TContour::const_iterator -cbegin(const RawShape& sh, const PolygonTag&) +template +inline typename TContour::const_iterator +cbegin(const S& sh, const PolygonTag&) { return cbegin(contour(sh), PathTag()); } -template // Tag dispatcher -inline auto cbegin(const RawShape& sh) -> decltype(cbegin(sh, Tag())) +template // Tag dispatcher +inline auto cbegin(const S& sh) -> decltype(cbegin(sh, Tag())) { - return cbegin(sh, Tag()); + return cbegin(sh, Tag()); } -template -inline typename TContour::iterator -end(RawShape& sh, const PolygonTag&) +template +inline typename TContour::iterator +end(S& sh, const PolygonTag&) { return end(contour(sh), PathTag()); } -template // Tag dispatcher -inline auto end(RawShape& sh) -> decltype(begin(sh, Tag())) +template // Tag dispatcher +inline auto end(S& sh) -> decltype(begin(sh, Tag())) { - return end(sh, Tag()); + return end(sh, Tag()); } -template -inline typename TContour::const_iterator -cend(const RawShape& sh, const PolygonTag&) +template +inline typename TContour::const_iterator +cend(const S& sh, const PolygonTag&) { return cend(contour(sh), PathTag()); } -template // Tag dispatcher -inline auto cend(const RawShape& sh) -> decltype(cend(sh, Tag())) +template // Tag dispatcher +inline auto cend(const S& sh) -> decltype(cend(sh, Tag())) { - return cend(sh, Tag()); + return cend(sh, Tag()); } template std::reverse_iterator _backward(It iter) { @@ -817,8 +845,8 @@ template TPoint

back (const P& p) { } // Optional, does nothing by default -template -inline void reserve(RawShape& sh, size_t vertex_capacity, const PolygonTag&) +template +inline void reserve(S& sh, size_t vertex_capacity, const PolygonTag&) { reserve(contour(sh), vertex_capacity, PathTag()); } @@ -828,20 +856,20 @@ inline void reserve(T& sh, size_t vertex_capacity) { reserve(sh, vertex_capacity, Tag()); } -template -inline void addVertex(RawShape& sh, const PolygonTag&, Args...args) +template +inline void addVertex(S& sh, const PolygonTag&, Args...args) { addVertex(contour(sh), PathTag(), std::forward(args)...); } -template // Tag dispatcher -inline void addVertex(RawShape& sh, Args...args) +template // Tag dispatcher +inline void addVertex(S& sh, Args...args) { - addVertex(sh, Tag(), std::forward(args)...); + addVertex(sh, Tag(), std::forward(args)...); } -template -inline _Box> boundingBox(const RawShape& poly, const PolygonTag&) +template +inline _Box> boundingBox(const S& poly, const PolygonTag&) { return boundingBox(contour(poly), PathTag()); } @@ -938,40 +966,40 @@ template inline double area(const S& poly, const PolygonTag& ) }); } -template // Dispatching function -inline double area(const RawShape& sh) +template // Dispatching function +inline double area(const S& sh) { - return area(sh, Tag()); + return area(sh, Tag()); } template inline double area(const RawShapes& shapes, const MultiPolygonTag&) { - using RawShape = typename RawShapes::value_type; + using S = typename RawShapes::value_type; return std::accumulate(shapes.begin(), shapes.end(), 0.0, - [](double a, const RawShape& b) { + [](double a, const S& b) { return a += area(b); }); } -template -inline RawShape convexHull(const RawShape& sh, const PolygonTag&) +template +inline S convexHull(const S& sh, const PolygonTag&) { - return create(convexHull(contour(sh), PathTag())); + return create(convexHull(contour(sh), PathTag())); } -template -inline auto convexHull(const RawShape& sh) - -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce +template +inline auto convexHull(const S& sh) + -> decltype(convexHull(sh, Tag())) // TODO: C++14 could deduce { - return convexHull(sh, Tag()); + return convexHull(sh, Tag()); } -template -inline RawShape convexHull(const RawShape& sh, const PathTag&) +template +inline S convexHull(const S& sh, const PathTag&) { - using Unit = TCompute; - using Point = TPoint; + using Unit = TCompute; + using Point = TPoint; namespace sl = shapelike; size_t edges = sl::cend(sh) - sl::cbegin(sh); @@ -1016,8 +1044,8 @@ inline RawShape convexHull(const RawShape& sh, const PathTag&) ++ik; } - RawShape ret; reserve(ret, U.size() + L.size()); - if(is_clockwise()) { + S ret; reserve(ret, U.size() + L.size()); + if(is_clockwise()) { for(auto it = U.begin(); it != std::prev(U.end()); ++it) addVertex(ret, *it); for(auto it = L.rbegin(); it != std::prev(L.rend()); ++it) @@ -1068,11 +1096,11 @@ inline bool isInside(const TP& point, const TB& box, return px > minx && px < maxx && py > miny && py < maxy; } -template -inline bool isInside(const RawShape& sh, const TC& circ, +template +inline bool isInside(const S& sh, const TC& circ, const PolygonTag&, const CircleTag&) { - return std::all_of(cbegin(sh), cend(sh), [&circ](const TPoint& p) + return std::all_of(cbegin(sh), cend(sh), [&circ](const TPoint& p) { return isInside(p, circ, PointTag(), CircleTag()); }); @@ -1103,8 +1131,8 @@ inline bool isInside(const TBGuest& ibb, const TBHost& box, return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY; } -template -inline bool isInside(const RawShape& poly, const TB& box, +template +inline bool isInside(const S& poly, const TB& box, const PolygonTag&, const BoxTag&) { return isInside(boundingBox(poly), box, BoxTag(), BoxTag()); @@ -1115,36 +1143,36 @@ inline bool isInside(const TGuest& guest, const THost& host) { return isInside(guest, host, Tag(), Tag()); } -template // Potential O(1) implementation may exist -inline TPoint& vertex(RawShape& sh, unsigned long idx, +template // Potential O(1) implementation may exist +inline TPoint& vertex(S& sh, unsigned long idx, const PolygonTag&) { return *(shapelike::begin(contour(sh)) + idx); } -template // Potential O(1) implementation may exist -inline TPoint& vertex(RawShape& sh, unsigned long idx, +template // Potential O(1) implementation may exist +inline TPoint& vertex(S& sh, unsigned long idx, const PathTag&) { return *(shapelike::begin(sh) + idx); } -template // Potential O(1) implementation may exist -inline TPoint& vertex(RawShape& sh, unsigned long idx) +template // Potential O(1) implementation may exist +inline TPoint& vertex(S& sh, unsigned long idx) { - return vertex(sh, idx, Tag()); + return vertex(sh, idx, Tag()); } -template // Potential O(1) implementation may exist -inline const TPoint& vertex(const RawShape& sh, +template // Potential O(1) implementation may exist +inline const TPoint& vertex(const S& sh, unsigned long idx, const PolygonTag&) { return *(shapelike::cbegin(contour(sh)) + idx); } -template // Potential O(1) implementation may exist -inline const TPoint& vertex(const RawShape& sh, +template // Potential O(1) implementation may exist +inline const TPoint& vertex(const S& sh, unsigned long idx, const PathTag&) { @@ -1152,28 +1180,28 @@ inline const TPoint& vertex(const RawShape& sh, } -template // Potential O(1) implementation may exist -inline const TPoint& vertex(const RawShape& sh, +template // Potential O(1) implementation may exist +inline const TPoint& vertex(const S& sh, unsigned long idx) { - return vertex(sh, idx, Tag()); + return vertex(sh, idx, Tag()); } -template -inline size_t contourVertexCount(const RawShape& sh) +template +inline size_t contourVertexCount(const S& sh) { return shapelike::cend(sh) - shapelike::cbegin(sh); } -template -inline void foreachVertex(RawShape& sh, Fn fn, const PolygonTag&) { +template +inline void foreachVertex(S& sh, Fn fn, const PolygonTag&) { foreachVertex(contour(sh), fn, PathTag()); for(auto& h : holes(sh)) foreachVertex(h, fn, PathTag()); } -template -inline void foreachVertex(RawShape& sh, Fn fn) { - foreachVertex(sh, fn, Tag()); +template +inline void foreachVertex(S& sh, Fn fn) { + foreachVertex(sh, fn, Tag()); } template inline bool isConvex(const Poly& sh, const PolygonTag&) @@ -1184,9 +1212,9 @@ template inline bool isConvex(const Poly& sh, const PolygonTag&) return convex; } -template inline bool isConvex(const RawShape& sh) // dispatch +template inline bool isConvex(const S& sh) // dispatch { - return isConvex(sh, Tag()); + return isConvex(sh, Tag()); } } From 5e846112eee7ff881b2fe5754d3136e152fe3220 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 2 Jul 2019 16:42:23 +0200 Subject: [PATCH 251/627] WIP UndoRedo: Added Undo/Redo stack, added Platter::take_snapshot(), experimental snapshots on loading STLs and increasing / decreasing model instances. --- src/libslic3r/Model.cpp | 27 +- src/libslic3r/Model.hpp | 62 ++-- src/libslic3r/ObjectID.cpp | 13 + src/libslic3r/ObjectID.hpp | 9 + src/libslic3r/SLA/SLACommon.hpp | 2 + src/libslic3r/Slicing.hpp | 5 + src/libslic3r/TriangleMesh.hpp | 19 ++ src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/Plater.cpp | 31 +- src/slic3r/GUI/Plater.hpp | 3 + src/slic3r/GUI/Selection.hpp | 3 +- src/slic3r/Utils/UndoRedo.cpp | 559 ++++++++++++++++++++++++++++++++ src/slic3r/Utils/UndoRedo.hpp | 58 ++++ 13 files changed, 742 insertions(+), 51 deletions(-) create mode 100644 src/slic3r/Utils/UndoRedo.cpp create mode 100644 src/slic3r/Utils/UndoRedo.hpp diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 2533f288ba..5e166b44f9 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -22,19 +22,6 @@ namespace Slic3r { unsigned int Model::s_auto_extruder_id = 1; -// Unique object / instance ID for the wipe tower. -ObjectID wipe_tower_object_id() -{ - static ObjectBase mine; - return mine.id(); -} - -ObjectID wipe_tower_instance_id() -{ - static ObjectBase mine; - return mine.id(); -} - Model& Model::assign_copy(const Model &rhs) { this->copy_id(rhs); @@ -1068,11 +1055,11 @@ void ModelObject::mirror(Axis axis) } // This method could only be called before the meshes of this ModelVolumes are not shared! -void ModelObject::scale_mesh(const Vec3d &versor) +void ModelObject::scale_mesh_after_creation(const Vec3d &versor) { for (ModelVolume *v : this->volumes) { - v->scale_geometry(versor); + v->scale_geometry_after_creation(versor); v->set_offset(versor.cwiseProduct(v->get_offset())); } this->invalidate_bounding_box(); @@ -1562,8 +1549,8 @@ void ModelVolume::center_geometry_after_creation() Vec3d shift = this->mesh().bounding_box().center(); if (!shift.isApprox(Vec3d::Zero())) { - m_mesh->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); - m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + const_cast(m_mesh.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + const_cast(m_convex_hull.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); translate(shift); } } @@ -1716,10 +1703,10 @@ void ModelVolume::mirror(Axis axis) } // This method could only be called before the meshes of this ModelVolumes are not shared! -void ModelVolume::scale_geometry(const Vec3d& versor) +void ModelVolume::scale_geometry_after_creation(const Vec3d& versor) { - m_mesh->scale(versor); - m_convex_hull->scale(versor); + const_cast(m_mesh.get())->scale(versor); + const_cast(m_convex_hull.get())->scale(versor); } void ModelVolume::transform_this_mesh(const Transform3d &mesh_trafo, bool fix_left_handed) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 850ea42022..8e9f7ecaa9 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -27,6 +27,10 @@ class ModelVolume; class Print; class SLAPrint; +namespace UndoRedo { + class StackImpl; +} + typedef std::string t_model_material_id; typedef std::string t_model_material_attribute; typedef std::map t_model_material_attributes; @@ -36,10 +40,6 @@ typedef std::vector ModelObjectPtrs; typedef std::vector ModelVolumePtrs; typedef std::vector ModelInstancePtrs; -// Unique object / instance ID for the wipe tower. -extern ObjectID wipe_tower_object_id(); -extern ObjectID wipe_tower_instance_id(); - #define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \ /* to make a private copy for background processing. */ \ @@ -75,7 +75,7 @@ private: \ void assign_new_unique_ids_recursive(); // Material, which may be shared across multiple ModelObjects of a single Model. -class ModelMaterial : public ObjectBase +class ModelMaterial final : public ObjectBase { public: // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. @@ -104,6 +104,7 @@ private: ModelMaterial& operator=(ModelMaterial &&rhs) = delete; friend class cereal::access; + friend class UndoRedo::StackImpl; ModelMaterial() : m_model(nullptr) {} template void serialize(Archive &ar) { ar(cereal::base_class(this)); ar(attributes, config); } }; @@ -112,7 +113,7 @@ private: // and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. // Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, // different rotation and different uniform scaling. -class ModelObject : public ObjectBase +class ModelObject final : public ObjectBase { friend class Model; public: @@ -211,7 +212,7 @@ public: void mirror(Axis axis); // This method could only be called before the meshes of this ModelVolumes are not shared! - void scale_mesh(const Vec3d& versor); + void scale_mesh_after_creation(const Vec3d& versor); size_t materials_count() const; size_t facets_count() const; @@ -240,12 +241,6 @@ public: // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) int get_mesh_errors_count(const int vol_idx = -1) const; -protected: - friend class Print; - friend class SLAPrint; - // Called by Print::apply() to set the model pointer after making a copy. - void set_model(Model *model) { m_model = model; } - private: ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} @@ -272,7 +267,14 @@ private: mutable BoundingBoxf3 m_raw_mesh_bounding_box; mutable bool m_raw_mesh_bounding_box_valid; + // Called by Print::apply() to set the model pointer after making a copy. + friend class Print; + friend class SLAPrint; + void set_model(Model *model) { m_model = model; } + + // Undo / Redo through the cereal serialization library friend class cereal::access; + friend class UndoRedo::StackImpl; ModelObject() : m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} template void serialize(Archive &ar) { ar(cereal::base_class(this)); @@ -292,17 +294,17 @@ enum class ModelVolumeType : int { // An object STL, or a modifier volume, over which a different set of parameters shall be applied. // ModelVolume instances are owned by a ModelObject. -class ModelVolume : public ObjectBase +class ModelVolume final : public ObjectBase { public: std::string name; // The triangular model. const TriangleMesh& mesh() const { return *m_mesh.get(); } - void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } - void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } - void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } - void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } - void reset_mesh() { m_mesh = std::make_shared(); } + void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } + void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } + void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } + void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } + void reset_mesh() { m_mesh = std::make_shared(); } // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. DynamicPrintConfig config; @@ -340,7 +342,7 @@ public: void mirror(Axis axis); // This method could only be called before the meshes of this ModelVolumes are not shared! - void scale_geometry(const Vec3d& versor); + void scale_geometry_after_creation(const Vec3d& versor); // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box. // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared! @@ -400,15 +402,15 @@ protected: private: // Parent object owning this ModelVolume. - ModelObject* object; + ModelObject* object; // The triangular model. - std::shared_ptr m_mesh; + std::shared_ptr m_mesh; // Is it an object to be printed, or a modifier volume? - ModelVolumeType m_type; - t_model_material_id m_material_id; + ModelVolumeType m_type; + t_model_material_id m_material_id; // The convex hull of this model's mesh. - std::shared_ptr m_convex_hull; - Geometry::Transformation m_transformation; + std::shared_ptr m_convex_hull; + Geometry::Transformation m_transformation; // flag to optimize the checking if the volume is splittable // -1 -> is unknown value (before first cheking) @@ -443,16 +445,16 @@ private: ModelVolume& operator=(ModelVolume &rhs) = delete; friend class cereal::access; + friend class UndoRedo::StackImpl; ModelVolume() : object(nullptr) {} template void serialize(Archive &ar) { - ar(cereal::base_class(this)); ar(name, config, m_mesh, m_type, m_material_id, m_convex_hull, m_transformation, m_is_splittable); } }; // A single instance of a ModelObject. // Knows the affine transformation of an object. -class ModelInstance : public ObjectBase +class ModelInstance final : public ObjectBase { public: enum EPrintVolumeState : unsigned char @@ -538,6 +540,7 @@ private: ModelInstance& operator=(ModelInstance &&rhs) = delete; friend class cereal::access; + friend class UndoRedo::StackImpl; ModelInstance() : object(nullptr) {} template void serialize(Archive &ar) { ar(cereal::base_class(this)); @@ -550,7 +553,7 @@ private: // and with multiple modifier meshes. // A model groups multiple objects, each object having possibly multiple instances, // all objects may share mutliple materials. -class Model : public ObjectBase +class Model final : public ObjectBase { static unsigned int s_auto_extruder_id; @@ -633,6 +636,7 @@ private: OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE(Model) friend class cereal::access; + friend class UndoRedo::StackImpl; template void serialize(Archive &ar) { ar(cereal::base_class(this), materials, objects); } diff --git a/src/libslic3r/ObjectID.cpp b/src/libslic3r/ObjectID.cpp index 90681a5a6f..b188d84c06 100644 --- a/src/libslic3r/ObjectID.cpp +++ b/src/libslic3r/ObjectID.cpp @@ -4,6 +4,19 @@ namespace Slic3r { size_t ObjectBase::s_last_id = 0; +// Unique object / instance ID for the wipe tower. +ObjectID wipe_tower_object_id() +{ + static ObjectBase mine; + return mine.id(); +} + +ObjectID wipe_tower_instance_id() +{ + static ObjectBase mine; + return mine.id(); +} + } // namespace Slic3r // CEREAL_REGISTER_TYPE(Slic3r::ObjectBase) diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 6f9c3fcff6..f00d6f61ea 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -9,6 +9,10 @@ namespace Slic3r { +namespace UndoRedo { + class StackImpl; +}; + // Unique identifier of a mutable object accross the application. // Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject) // (for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial classes) @@ -75,6 +79,7 @@ private: friend ObjectID wipe_tower_object_id(); friend ObjectID wipe_tower_instance_id(); + friend class Slic3r::UndoRedo::StackImpl; friend class cereal::access; template void serialize(Archive &ar) { ar(m_id); } @@ -82,6 +87,10 @@ private: template static void load_and_construct(Archive & ar, cereal::construct &construct) { ObjectID id; ar(id); construct(id); } }; +// Unique object / instance ID for the wipe tower. +extern ObjectID wipe_tower_object_id(); +extern ObjectID wipe_tower_instance_id(); + } // namespace Slic3r #endif /* slic3r_ObjectID_hpp_ */ diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index 855802759e..247e89dbbf 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -43,6 +43,8 @@ struct SupportPoint { bool operator==(const SupportPoint& sp) const { return (pos==sp.pos) && head_front_radius==sp.head_front_radius && is_new_island==sp.is_new_island; } bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); } + + template void serialize(Archive &ar) { ar(pos, head_front_radius, is_new_island); } }; /// An index-triangle structure for libIGL functions. Also serves as an diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index fa5a12f9ce..cd6affdeb6 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -171,4 +171,9 @@ extern int generate_layer_height_texture( }; // namespace Slic3r +namespace cereal +{ + template void serialize(Archive& archive, Slic3r::t_layer_height_range &lhr) { archive(lhr.first, lhr.second); } +} + #endif /* slic3r_Slicing_hpp_ */ diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 054a98935a..1fc5128932 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -195,4 +195,23 @@ TriangleMesh make_sphere(double rho, double fa=(2*PI/360)); } +// Serialization through the Cereal library +namespace cereal { + template struct specialize {}; + template void load(Archive &archive, Slic3r::TriangleMesh &mesh) { + stl_file &stl = mesh.stl; + stl.stats.type = inmemory; + archive(stl.stats.number_of_facets, stl.stats.original_num_facets); + stl_allocate(&stl); + archive.loadBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); + stl_get_size(&stl); + mesh.repair(); + } + template void save(Archive &archive, const Slic3r::TriangleMesh &mesh) { + const stl_file& stl = mesh.stl; + archive(stl.stats.number_of_facets, stl.stats.original_num_facets); + archive.saveBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); + } +} + #endif diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 1867a8186f..da1afdfeea 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -146,6 +146,8 @@ set(SLIC3R_GUI_SOURCES Utils/PresetUpdater.hpp Utils/Time.cpp Utils/Time.hpp + Utils/UndoRedo.cpp + Utils/UndoRedo.hpp Utils/HexFile.cpp Utils/HexFile.hpp ) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 9d0c979b66..f084805590 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -69,6 +69,7 @@ #include "../Utils/ASCIIFolding.hpp" #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" +#include "../Utils/UndoRedo.hpp" #include // Needs to be last because reasons :-/ #include "WipeTowerDialog.hpp" @@ -1182,6 +1183,8 @@ const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames) { + plater->take_snapshot(_(L("Load Files"))); + std::vector paths; for (const auto &filename : filenames) { @@ -1247,6 +1250,7 @@ struct Plater::priv Slic3r::Model model; PrinterTechnology printer_technology = ptFFF; Slic3r::GCodePreviewData gcode_preview_data; + Slic3r::UndoRedo::Stack undo_redo_stack; // GUI elements wxSizer* panel_sizer{ nullptr }; @@ -1545,6 +1549,10 @@ struct Plater::priv void split_object(); void split_volume(); void scale_selection_to_fit_print_volume(); + + void take_snapshot(const std::string& snapshot_name) { this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection()); } + void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } + bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; } void update_print_volume_state(); void schedule_background_process(); @@ -1775,6 +1783,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // updates camera type from .ini file camera.set_type(get_config("use_perspective_camera")); + + this->undo_redo_stack.initialize(model, view3D->get_canvas3d()->get_selection()); } void Plater::priv::update(bool force_full_scene_refresh) @@ -2139,7 +2149,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode // the size of the object is too big -> this could lead to overflow when moving to clipper coordinates, // so scale down the mesh double inv = 1. / max_ratio; - object->scale_mesh(Vec3d(inv, inv, inv)); + object->scale_mesh_after_creation(Vec3d(inv, inv, inv)); object->origin_translation = Vec3d::Zero(); object->center_around_origin(); scaled_down = true; @@ -2355,6 +2365,8 @@ void Plater::priv::delete_object_from_model(size_t obj_idx) void Plater::priv::reset() { + this->take_snapshot(_(L("Reset Project"))); + set_project_filename(wxEmptyString); // Prevent toolpaths preview from rendering while we modify the Print object @@ -3515,6 +3527,8 @@ void Plater::new_project() void Plater::load_project() { + this->take_snapshot(_(L("Load Project"))); + wxString input_file; wxGetApp().load_project(this, input_file); @@ -3593,6 +3607,7 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo void Plater::remove_selected() { + this->take_snapshot(_(L("Delete Selected Objects"))); this->p->view3D->delete_selected(); } @@ -3600,6 +3615,8 @@ void Plater::increase_instances(size_t num) { if (! can_increase_instances()) { return; } + this->take_snapshot(_(L("Increase Instances"))); + int obj_idx = p->get_selected_object_idx(); ModelObject* model_object = p->model.objects[obj_idx]; @@ -3634,6 +3651,8 @@ void Plater::decrease_instances(size_t num) { if (! can_decrease_instances()) { return; } + this->take_snapshot(_(L("Decrease Instances"))); + int obj_idx = p->get_selected_object_idx(); ModelObject* model_object = p->model.objects[obj_idx]; @@ -3982,6 +4001,16 @@ void Plater::send_gcode() } } +void Plater::take_snapshot(const std::string &snapshot_name) +{ + p->take_snapshot(snapshot_name); +} + +void Plater::take_snapshot(const wxString &snapshot_name) +{ + p->take_snapshot(snapshot_name); +} + void Plater::on_extruders_change(int num_extruders) { auto& choices = sidebar().combos_filament(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 2851af6544..9a6bcda7b4 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -179,6 +179,9 @@ public: void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); void send_gcode(); + void take_snapshot(const std::string &snapshot_name); + void take_snapshot(const wxString &snapshot_name); + void on_extruders_change(int extruders_count); void on_config_change(const DynamicPrintConfig &config); // On activating the parent window. diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index c23f23e6b0..17ae72356f 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -3,6 +3,7 @@ #include #include "libslic3r/Geometry.hpp" +#include "libslic3r/ObjectID.hpp" #include "3DScene.hpp" #if ENABLE_RENDER_SELECTION_CENTER @@ -66,7 +67,7 @@ private: Enum m_value; }; -class Selection +class Selection : public Slic3r::ObjectBase { public: typedef std::set IndicesList; diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp new file mode 100644 index 0000000000..9263db65ea --- /dev/null +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -0,0 +1,559 @@ +#include "UndoRedo.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#define CEREAL_FUTURE_EXPERIMENTAL +#include + +#include + +#include + +namespace Slic3r { +namespace UndoRedo { + +// Time interval, start is closed, end is open. +struct Interval +{ +public: + Interval(size_t begin, size_t end) : m_begin(begin), m_end(end) {} + + size_t begin() const { return m_begin; } + size_t end() const { return m_end; } + + bool is_valid() const { return m_begin >= 0 && m_begin < m_end; } + // This interval comes strictly before the rhs interval. + bool strictly_before(const Interval &rhs) const { return this->is_valid() && rhs.is_valid() && m_end <= rhs.m_begin; } + // This interval comes strictly after the rhs interval. + bool strictly_after(const Interval &rhs) const { return this->is_valid() && rhs.is_valid() && rhs.m_end <= m_begin; } + + bool operator<(const Interval &rhs) const { return (m_begin < rhs.m_begin) || (m_begin == rhs.m_begin && m_end < rhs.m_end); } + bool operator==(const Interval &rhs) const { return m_begin == rhs.m_begin && m_end == rhs.m_end; } + + void trim_end(size_t new_end) { m_end = std::min(m_end, new_end); } + void extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; } + +private: + size_t m_begin; + size_t m_end; +}; + +// History of a single object tracked by the Undo / Redo stack. The object may be mutable or immutable. +class ObjectHistoryBase +{ +public: + virtual ~ObjectHistoryBase() {} + + // If the history is empty, the ObjectHistory object could be released. + virtual bool empty() = 0; + + // Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. + virtual void relese_after_timestamp(size_t timestamp) = 0; + +#ifndef NDEBUG + virtual bool validate() = 0; +#endif /* NDEBUG */ +}; + +template class ObjectHistory : public ObjectHistoryBase +{ +public: + ~ObjectHistory() override {} + + // If the history is empty, the ObjectHistory object could be released. + bool empty() override { return m_history.empty(); } + + // Release all data after the given timestamp. The shared pointer is NOT released. + void relese_after_timestamp(size_t timestamp) override { + assert(! m_history.empty()); + assert(this->validate()); + // it points to an interval which either starts with timestamp, or follows the timestamp. + auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp)); + if (it == m_history.end()) { + auto it_prev = it; + -- it_prev; + assert(it_prev->begin() < timestamp); + // Trim the last interval with timestamp. + it_prev->trim_end(timestamp); + } + m_history.erase(it, m_history.end()); + assert(this->validate()); + } + +protected: + std::vector m_history; +}; + +// Big objects (mainly the triangle meshes) are tracked by Slicer using the shared pointers +// and they are immutable. +// The Undo / Redo stack therefore may keep a shared pointer to these immutable objects +// and as long as the ref counter of these objects is higher than 1 (1 reference is held +// by the Undo / Redo stack), there is no cost associated to holding the object +// at the Undo / Redo stack. Once the reference counter drops to 1 (only the Undo / Redo +// stack holds the reference), the shared pointer may get serialized (and possibly compressed) +// and the shared pointer may be released. +// The history of a single immutable object may not be continuous, as an immutable object may +// be removed from the scene while being kept at the Copy / Paste stack. +template +class ImmutableObjectHistory : public ObjectHistory +{ +public: + ImmutableObjectHistory(std::shared_ptr shared_object) : m_shared_object(shared_object) {} + ~ImmutableObjectHistory() override {} + + void save(size_t active_snapshot_time, size_t current_time) { + assert(m_history.empty() || m_history.back().end() <= active_snapshot_time); + if (m_history.empty() || m_history.back().end() < active_snapshot_time) + m_history.emplace_back(active_snapshot_time, current_time + 1); + else + m_history.back().extend_end(current_time + 1); + } + + bool has_snapshot(size_t timestamp) { + if (m_history.empty()) + return false; + auto it = std::lower_bound(m_history.begin(), m_history.end(), Interval(timestamp, timestamp)); + if (it == m_history.end() || it->begin() >= timestamp) { + if (it == m_history.begin()) + return false; + -- it; + } + return timestamp >= it->begin() && timestamp < it->end(); + } + + bool is_serialized() const { return m_shared_object.get() == nullptr; } + const std::string& serialized_data() const { return m_serialized; } + std::shared_ptr& shared_ptr(StackImpl &stack) { + if (m_shared_object.get() == nullptr && ! this->m_serialized.empty()) { + // Deserialize the object. + std::istringstream iss(m_serialized); + { + Slic3r::UndoRedo::InputArchive archive(stack, iss); + std::unique_ptr::type> mesh(new std::remove_const::type()); + archive(*mesh.get()); + m_shared_object = std::move(mesh); + } + } + return m_shared_object; + } + +#ifndef NDEBUG + bool validate() override; +#endif /* NDEBUG */ + +private: + // Either the source object is held by a shared pointer and the m_serialized field is empty, + // or the shared pointer is null and the object is being serialized into m_serialized. + std::shared_ptr m_shared_object; + std::string m_serialized; +}; + +struct MutableHistoryInterval +{ +private: + struct Data + { + // Reference counter of this data chunk. We may have used shared_ptr, but the shared_ptr is thread safe + // with the associated cost of CPU cache invalidation on refcount change. + size_t refcnt; + size_t size; + char data[1]; + + bool matches(const std::string& rhs) { return this->size == rhs.size() && memcmp(this->data, rhs.data(), this->size) == 0; } + }; + + Interval m_interval; + Data *m_data; + +public: + MutableHistoryInterval(const Interval &interval, const std::string &input_data) : m_interval(interval), m_data(nullptr) { + m_data = (Data*)new char[offsetof(Data, data) + input_data.size()]; + m_data->refcnt = 1; + m_data->size = input_data.size(); + memcpy(m_data->data, input_data.data(), input_data.size()); + } + + MutableHistoryInterval(const Interval &interval, MutableHistoryInterval &other) : m_interval(interval), m_data(other.m_data) { + ++ m_data->refcnt; + } + + // as a key for std::lower_bound + MutableHistoryInterval(const size_t begin, const size_t end) : m_interval(begin, end), m_data(nullptr) {} + + MutableHistoryInterval(MutableHistoryInterval&& rhs) : m_interval(rhs.m_interval), m_data(rhs.m_data) { rhs.m_data = nullptr; } + MutableHistoryInterval& operator=(MutableHistoryInterval&& rhs) { m_interval = rhs.m_interval; m_data = rhs.m_data; rhs.m_data = nullptr; return *this; } + + ~MutableHistoryInterval() { + if (m_data != nullptr && -- m_data->refcnt == 0) + delete[] (char*)m_data; + } + + const Interval& interval() const { return m_interval; } + size_t begin() const { return m_interval.begin(); } + size_t end() const { return m_interval.end(); } + void trim_end(size_t timestamp) { m_interval.trim_end(timestamp); } + void extend_end(size_t timestamp) { m_interval.extend_end(timestamp); } + + bool operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; } + bool operator==(const MutableHistoryInterval& rhs) const { return m_interval == rhs.m_interval; } + + const char* data() const { return m_data->data; } + size_t size() const { return m_data->size; } + size_t refcnt() const { return m_data->refcnt; } + bool matches(const std::string& data) { return m_data->matches(data); } + +private: + MutableHistoryInterval(const MutableHistoryInterval &rhs); + MutableHistoryInterval& operator=(const MutableHistoryInterval &rhs); +}; + +// Smaller objects (Model, ModelObject, ModelInstance, ModelVolume, DynamicPrintConfig) +// are mutable and there is not tracking of the changes, therefore a snapshot needs to be +// taken every time and compared to the previous data at the Undo / Redo stack. +// The serialized data is stored if it is different from the last value on the stack, otherwise +// the serialized data is discarded. +// The history of a single mutable object may not be continuous, as an mutable object may +// be removed from the scene while being kept at the Copy / Paste stack, therefore an object snapshot +// with the same serialized object data may be shared by multiple history intervals. +template +class MutableObjectHistory : public ObjectHistory +{ +public: + ~MutableObjectHistory() override {} + + void save(size_t active_snapshot_time, size_t current_time, const std::string &data) { + assert(m_history.empty() || m_history.back().end() <= active_snapshot_time); + if (m_history.empty() || m_history.back().end() < active_snapshot_time) { + if (! m_history.empty() && m_history.back().matches(data)) + // Share the previous data by reference counting. + m_history.emplace_back(Interval(current_time, current_time + 1), m_history.back()); + else + // Allocate new data. + m_history.emplace_back(Interval(current_time, current_time + 1), data); + } else { + assert(! m_history.empty()); + assert(m_history.back().end() == active_snapshot_time); + if (m_history.back().matches(data)) + // Just extend the last interval using the old data. + m_history.back().extend_end(current_time + 1); + else + // Allocate new data time continuous with the previous data. + m_history.emplace_back(Interval(active_snapshot_time, current_time + 1), data); + } + } + + std::string load(size_t timestamp) const { + assert(! m_history.empty()); + auto it = std::lower_bound(m_history.begin(), m_history.end(), MutableHistoryInterval(timestamp, timestamp)); + if (it == m_history.end() || it->begin() >= timestamp) { + assert(it != m_history.begin()); + -- it; + } + assert(timestamp >= it->begin() && timestamp < it->end()); + return std::string(it->data(), it->data() + it->size()); + } + +#ifndef NDEBUG + bool validate() override; +#endif /* NDEBUG */ +}; + +#ifndef NDEBUG +template +bool ImmutableObjectHistory::validate() +{ + // The immutable object content is captured either by a shared object, or by its serialization, but not both. + assert(! m_shared_object == ! m_serialized.empty()); + // Verify that the history intervals are sorted and do not overlap. + if (! m_history.empty()) + for (size_t i = 1; i < m_history.size(); ++ i) + assert(m_history[i - 1].strictly_before(m_history[i])); + return true; +} +#endif /* NDEBUG */ + +#ifndef NDEBUG +template +bool MutableObjectHistory::validate() +{ + // Verify that the history intervals are sorted and do not overlap, and that the data reference counters are correct. + if (! m_history.empty()) { + std::map refcntrs; + assert(m_history.front().data() != nullptr); + ++ refcntrs[m_history.front().data()]; + for (size_t i = 1; i < m_history.size(); ++ i) { + assert(m_history[i - 1].interval().strictly_before(m_history[i].interval())); + ++ refcntrs[m_history[i].data()]; + } + for (const auto &hi : m_history) { + assert(hi.data() != nullptr); + assert(refcntrs[hi.data()] == hi.refcnt()); + } + } + return true; +} +#endif /* NDEBUG */ + +class StackImpl +{ +public: + // Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning. + StackImpl() : m_active_snapshot_time(0), m_current_time(0) {} + + // The Undo / Redo stack is being initialized with an empty model and an empty selection. + // The first snapshot cannot be removed. + void initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); + + // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. + void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); + void load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection); + + // Snapshot history (names with timestamps). + const std::vector& snapshots() const { return m_snapshots; } + +//protected: + void save_model(const Slic3r::Model &model, size_t snapshot_time); + void save_selection(const Slic3r::Model& model, const Slic3r::GUI::Selection &selection, size_t snapshot_time); + void load_model(const Slic3r::Model &model, size_t snapshot_time); + void load_selection(const Slic3r::GUI::Selection &selection, size_t snapshot_time); + + template ObjectID save_mutable_object(const T &object); + template ObjectID save_immutable_object(std::shared_ptr &object); + template T* load_mutable_object(const Slic3r::ObjectID id); + template std::shared_ptr load_immutable_object(const Slic3r::ObjectID id); + template void load_mutable_object(const Slic3r::ObjectID id, T &target); + +private: + template ObjectID immutable_object_id(const std::shared_ptr &ptr) { + return this->immutable_object_id_impl((const void*)ptr.get()); + } + ObjectID immutable_object_id_impl(const void *ptr) { + auto it = m_shared_ptr_to_object_id.find(ptr); + if (it == m_shared_ptr_to_object_id.end()) { + // Allocate a new temporary ObjectID for this shared pointer. + ObjectBase object_with_id; + it = m_shared_ptr_to_object_id.insert(it, std::make_pair(ptr, object_with_id.id())); + } + return it->second; + } + + // Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh) + // is stored with its own history, referenced by the ObjectID. Immutable objects do not provide + // their own IDs, therefore there are temporary IDs generated for them and stored to m_shared_ptr_to_object_id. + std::map> m_objects; + std::map m_shared_ptr_to_object_id; + // Snapshot history (names with timestamps). + std::vector m_snapshots; + // Timestamp of the active snapshot. + size_t m_active_snapshot_time; + // Logical time counter. m_current_time is being incremented with each snapshot taken. + size_t m_current_time; +}; + +using InputArchive = cereal::UserDataAdapter; +using OutputArchive = cereal::UserDataAdapter; + +} // namespace UndoRedo + +class Model; +class ModelObject; +class ModelVolume; +class ModelInstance; +class ModelMaterial; + +} // namespace Slic3r + +namespace cereal +{ + // Let cereal know that there are load / save non-member functions declared for ModelObject*, ignore serialization of pointers triggering + // static assert, that cereal does not support serialization of raw pointers. + template struct specialize {}; + template struct specialize {}; + template struct specialize {}; + template struct specialize {}; + template struct specialize {}; + + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, + // store just the ObjectID to this stream. + template void save(BinaryOutputArchive& ar, T* const& ptr) + { + ar(cereal::get_user_data(ar).save_mutable_object(*ptr)); + } + + // Load ObjectBase derived class from the Undo / Redo stack as a separate object + // based on the ObjectID loaded from this stream. + template void load(BinaryInputArchive& ar, T*& ptr) + { + Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data(ar); + size_t id; + ar(id); + ptr = stack.load_mutable_object(Slic3r::ObjectID(id)); + } + + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, + // store just the ObjectID to this stream. + template void save(BinaryOutputArchive& ar, std::shared_ptr& ptr) + { + ar(cereal::get_user_data(ar).save_immutable_object(ptr)); + } + + // Load ObjectBase derived class from the Undo / Redo stack as a separate object + // based on the ObjectID loaded from this stream. + template void load(BinaryInputArchive& ar, std::shared_ptr& ptr) + { + Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data(ar); + size_t id; + ar(id); + ptr = std::const_pointer_cast(stack.load_immutable_object(Slic3r::ObjectID(id))); + } + +#if 0 + void save(BinaryOutputArchive &ar, const Slic3r::GUI::Selection &selection) + { + size_t num = selection.get_volume_idxs().size(); + ar(num); + for (unsigned int volume_idx : selection.get_volume_idxs()) { + const Slic3r::GLVolume::CompositeID &id = selection.get_volume(volume_idx)->composite_id; + ar(id.object_id, id.volume_id, id.instance_id); + } + } + + template void load(BinaryInputArchive &ar, Slic3r::GUI::Selection &selection) + { + size_t num; + ar(num); + for (size_t i = 0; i < num; ++ i) { + Slic3r::GLVolume::CompositeID id; + ar(id.object_id, id.volume_id, id.instance_id); + } + } +#endif +} + +#include +#include +#include + +namespace Slic3r { +namespace UndoRedo { + +template ObjectID StackImpl::save_mutable_object(const T &object) +{ + // First find or allocate a history stack for the ObjectID of this object instance. + auto it_object_history = m_objects.find(object.id()); + if (it_object_history == m_objects.end()) + it_object_history = m_objects.insert(it_object_history, std::make_pair(object.id(), std::unique_ptr>(new MutableObjectHistory()))); + auto *object_history = static_cast*>(it_object_history->second.get()); + // Then serialize the object into a string. + std::ostringstream oss; + { + Slic3r::UndoRedo::OutputArchive archive(*this, oss); + archive(object); + } + object_history->save(m_active_snapshot_time, m_current_time, oss.str()); + return object.id(); +} + +template ObjectID StackImpl::save_immutable_object(std::shared_ptr &object) +{ + // First allocate a temporary ObjectID for this pointer. + ObjectID object_id = this->immutable_object_id(object); + // and find or allocate a history stack for the ObjectID associated to this shared_ptr. + auto it_object_history = m_objects.find(object_id); + if (it_object_history == m_objects.end()) + it_object_history = m_objects.insert(it_object_history, ObjectID, std::unique_ptr>(new ImmutableObjectHistory(object))); + auto *object_history = ; + // Then save the interval. + static_cast*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time); + return object_id; +} + +template T* StackImpl::load_mutable_object(const Slic3r::ObjectID id) +{ + T *target = new T(); + this->load_mutable_object(id, *target); + return target; +} + +template std::shared_ptr StackImpl::load_immutable_object(const Slic3r::ObjectID id) +{ + // First find a history stack for the ObjectID of this object instance. + auto it_object_history = m_objects.find(id); + assert(it_object_history != m_objects.end()); + auto *object_history = static_cast*>(it_object_history->second.get()); + assert(object_history->has_snapshot(m_active_snapshot_time)); + return object_history->shared_ptr(*this); +} + +template void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target) +{ + // First find a history stack for the ObjectID of this object instance. + auto it_object_history = m_objects.find(id); + assert(it_object_history != m_objects.end()); + auto *object_history = static_cast*>(it_object_history->second.get()); + // Then get the data associated with the object history and m_active_snapshot_time. + std::istringstream iss(object_history->load(m_active_snapshot_time)); + Slic3r::UndoRedo::InputArchive archive(*this, iss); + archive(target); +} + +// The Undo / Redo stack is being initialized with an empty model and an empty selection. +// The first snapshot cannot be removed. +void StackImpl::initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) +{ + assert(m_active_snapshot_time == 0); + assert(m_current_time == 0); + // The initial time interval will be <0, 1) + m_active_snapshot_time = SIZE_MAX; // let it overflow to zero in take_snapshot + m_current_time = 0; + this->take_snapshot("New Project", model, selection); +} + +// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. +void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) +{ + // Release old snapshot data. + ++ m_active_snapshot_time; + for (auto &kvp : m_objects) + kvp.second->relese_after_timestamp(m_active_snapshot_time); + { + auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + m_snapshots.erase(it, m_snapshots.end()); + } + // Take new snapshots. + this->save_mutable_object(model); +// this->save_mutable_object(selection); + // Save the snapshot info + m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id); +} + +void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection) +{ + // Find the snapshot by time. It must exist. + const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp)); + if (it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) + throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); + + this->load_mutable_object(ObjectID(it_snapshot->model_object_id), model); + this->load_mutable_object(selection.id(), selection); + this->m_active_snapshot_time = timestamp; +} + +// Wrappers of the private implementation. +Stack::Stack() : pimpl(new StackImpl()) {} +Stack::~Stack() {} +void Stack::initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->initialize(model, selection); } +void Stack::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->take_snapshot(snapshot_name, model, selection); } +void Stack::load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection) { pimpl->load_snapshot(timestamp, model, selection); } +const std::vector& Stack::snapshots() const { return pimpl->snapshots(); } + +} // namespace UndoRedo +} // namespace Slic3r diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp new file mode 100644 index 0000000000..d452e777c1 --- /dev/null +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -0,0 +1,58 @@ +#ifndef slic3r_Utils_UndoRedo_hpp_ +#define slic3r_Utils_UndoRedo_hpp_ + +#include +#include + +namespace Slic3r { + +class Model; + +namespace GUI { + class Selection; +} // namespace GUI + +namespace UndoRedo { + +struct Snapshot +{ + Snapshot(size_t timestamp) : timestamp(timestamp) {} + Snapshot(const std::string &name, size_t timestamp, size_t model_object_id) : name(name), timestamp(timestamp), model_object_id(model_object_id) {} + + std::string name; + size_t timestamp; + size_t model_object_id; + + bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; } + bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; } +}; + +class StackImpl; + +class Stack +{ +public: + // Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning. + Stack(); + ~Stack(); + + // The Undo / Redo stack is being initialized with an empty model and an empty selection. + // The first snapshot cannot be removed. + void initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); + + // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. + void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); + void load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection); + + // Snapshot history (names with timestamps). + const std::vector& snapshots() const; + +private: + friend class StackImpl; + std::unique_ptr pimpl; +}; + +}; // namespace UndoRedo +}; // namespace Slic3r + +#endif /* slic3r_Utils_UndoRedo_hpp_ */ From 41255198634067481bc8671e08c819544adaba0e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 2 Jul 2019 17:56:38 +0200 Subject: [PATCH 252/627] WIP Undo / Redo: Capturing of the triangle meshes. --- src/slic3r/Utils/UndoRedo.cpp | 44 ++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 9263db65ea..1e49c13361 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -52,6 +52,10 @@ class ObjectHistoryBase public: virtual ~ObjectHistoryBase() {} + // Is the object captured by this history mutable or immutable? + virtual bool is_mutable() const = 0; + virtual bool is_immutable() const = 0; + // If the history is empty, the ObjectHistory object could be released. virtual bool empty() = 0; @@ -109,6 +113,9 @@ public: ImmutableObjectHistory(std::shared_ptr shared_object) : m_shared_object(shared_object) {} ~ImmutableObjectHistory() override {} + bool is_mutable() const override { return false; } + bool is_immutable() const override { return true; } + void save(size_t active_snapshot_time, size_t current_time) { assert(m_history.empty() || m_history.back().end() <= active_snapshot_time); if (m_history.empty() || m_history.back().end() < active_snapshot_time) @@ -229,6 +236,9 @@ class MutableObjectHistory : public ObjectHistory public: ~MutableObjectHistory() override {} + bool is_mutable() const override { return true; } + bool is_immutable() const override { return false; } + void save(size_t active_snapshot_time, size_t current_time, const std::string &data) { assert(m_history.empty() || m_history.back().end() <= active_snapshot_time); if (m_history.empty() || m_history.back().end() < active_snapshot_time) { @@ -344,6 +354,7 @@ private: } return it->second; } + void collect_garbage(); // Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh) // is stored with its own history, referenced by the ObjectID. Immutable objects do not provide @@ -368,6 +379,7 @@ class ModelObject; class ModelVolume; class ModelInstance; class ModelMaterial; +class TriangleMesh; } // namespace Slic3r @@ -380,6 +392,7 @@ namespace cereal template struct specialize {}; template struct specialize {}; template struct specialize {}; + template struct specialize, cereal::specialization::non_member_load_save> {}; // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, // store just the ObjectID to this stream. @@ -400,19 +413,19 @@ namespace cereal // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, // store just the ObjectID to this stream. - template void save(BinaryOutputArchive& ar, std::shared_ptr& ptr) + template void save(BinaryOutputArchive &ar, const std::shared_ptr &ptr) { - ar(cereal::get_user_data(ar).save_immutable_object(ptr)); + ar(cereal::get_user_data(ar).save_immutable_object(const_cast&>(ptr))); } // Load ObjectBase derived class from the Undo / Redo stack as a separate object // based on the ObjectID loaded from this stream. - template void load(BinaryInputArchive& ar, std::shared_ptr& ptr) + template void load(BinaryInputArchive &ar, std::shared_ptr &ptr) { - Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data(ar); + Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data(ar); size_t id; ar(id); - ptr = std::const_pointer_cast(stack.load_immutable_object(Slic3r::ObjectID(id))); + ptr = stack.load_immutable_object(Slic3r::ObjectID(id)); } #if 0 @@ -469,10 +482,9 @@ template ObjectID StackImpl::save_immutable_object(std::shared_ptr>(new ImmutableObjectHistory(object))); - auto *object_history = ; + it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr>(new ImmutableObjectHistory(object))); // Then save the interval. - static_cast*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time); + static_cast*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time); return object_id; } @@ -533,6 +545,8 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo // this->save_mutable_object(selection); // Save the snapshot info m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id); + // Release empty objects from the history. + this->collect_garbage(); } void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection) @@ -547,6 +561,20 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GU this->m_active_snapshot_time = timestamp; } +void StackImpl::collect_garbage() +{ + // Purge objects with empty histories. + for (auto it = m_objects.begin(); it != m_objects.end();) { + if (it->second->empty()) { + if (it->second->is_immutable()) + // Release the immutable object from the ptr to ObjectID map. + this->m_objects.erase(it->first); + it = m_objects.erase(it); + } else + ++ it; + } +} + // Wrappers of the private implementation. Stack::Stack() : pimpl(new StackImpl()) {} Stack::~Stack() {} From d101ed709c79c305433a14e780be63428f3da12f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 3 Jul 2019 08:58:05 +0200 Subject: [PATCH 253/627] Button 'Load shape from STL' centered into its panel in bed shape dialog --- src/slic3r/GUI/BedShapeDialog.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 20aa68ef2a..4dc7d0f797 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -92,13 +92,15 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) Line line{ "", "" }; line.full_width = 1; line.widget = [this](wxWindow* parent) { - auto btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")), wxDefaultPosition, wxDefaultSize); - - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + auto shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL..."))); + wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL); + shape_sizer->Add(shape_btn, 1, wxEXPAND); - btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) - { + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(shape_sizer, 1, wxEXPAND); + + shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { load_stl(); })); From 7a7316fcbf79a51087266bf57373c8428760eac5 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 3 Jul 2019 09:19:07 +0200 Subject: [PATCH 254/627] Fixed compile warnings in Bed3D --- src/slic3r/GUI/3DBed.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 68a74f61cd..fc07581e67 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -44,8 +44,8 @@ public: const float* get_vertices_data() const; unsigned int get_vertices_data_size() const { return (unsigned int)m_vertices.size() * get_vertex_data_size(); } unsigned int get_vertex_data_size() const { return (unsigned int)(5 * sizeof(float)); } - unsigned int get_position_offset() const { return 0; } - unsigned int get_tex_coords_offset() const { return (unsigned int)(3 * sizeof(float)); } + size_t get_position_offset() const { return 0; } + size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); } unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); } #else const float* get_vertices() const { return m_vertices.data(); } From 2c0f0c85a5335405c09a9ff4fa84d2636de0d68d Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 3 Jul 2019 10:06:22 +0200 Subject: [PATCH 255/627] Fixed bed shape dialog layout --- src/slic3r/GUI/BedShapeDialog.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 4dc7d0f797..d52204d4a7 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -53,15 +53,12 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) { -// on_change(nullptr); - - auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape"))); - auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL); + auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); // shape options m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); - sbsizer->Add(m_shape_options_book); + sbsizer->Add(m_shape_options_book); auto optgroup = init_shape_options_page(_(L("Rectangular"))); ConfigOptionDef def; @@ -108,8 +105,8 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) }; optgroup->append_line(line); - Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e) - { + Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) + { update_shape(); })); @@ -119,8 +116,8 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) // main sizer auto top_sizer = new wxBoxSizer(wxHORIZONTAL); - top_sizer->Add(sbsizer, 0, wxEXPAND | wxLeft | wxTOP | wxBOTTOM, 10); - if (m_canvas) + top_sizer->Add(sbsizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); + if (m_canvas) top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ; SetSizerAndFit(top_sizer); @@ -137,8 +134,7 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) // Create a panel for a rectangular / circular / custom bed shape. ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) { - - auto panel = new wxPanel(m_shape_options_book); + auto panel = new wxPanel(m_shape_options_book); ConfigOptionsGroupShp optgroup; optgroup = std::make_shared(panel, _(L("Settings"))); From e2a670218bd5a47c09595b7bc60e2fd7ee4c7858 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 3 Jul 2019 13:43:54 +0200 Subject: [PATCH 256/627] WIP Undo / Redo: Serializing the configs of ModelObject / ModelVolume / ModelMaterial as separate objects to conserve memory. --- src/libslic3r/Model.cpp | 41 ++++++-- src/libslic3r/Model.hpp | 193 ++++++++++++++++++++++++---------- src/libslic3r/ObjectID.hpp | 2 + src/libslic3r/Print.cpp | 4 +- src/libslic3r/SLAPrint.cpp | 2 +- src/slic3r/GUI/Selection.cpp | 2 +- src/slic3r/Utils/UndoRedo.cpp | 66 +++++++++--- 7 files changed, 229 insertions(+), 81 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 5e166b44f9..16f8fec4e6 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -607,11 +607,15 @@ ModelObject::~ModelObject() // maintains the m_model pointer ModelObject& ModelObject::assign_copy(const ModelObject &rhs) { - this->copy_id(rhs); + assert(this->id().invalid() || this->id() == rhs.id()); + assert(this->config.id().invalid() || this->config.id() == rhs.config.id()); + this->copy_id(rhs); this->name = rhs.name; this->input_file = rhs.input_file; + // Copies the config's ID this->config = rhs.config; + assert(this->config.id() == rhs.config.id()); this->sla_support_points = rhs.sla_support_points; this->sla_points_status = rhs.sla_points_status; this->layer_height_ranges = rhs.layer_height_ranges; @@ -643,11 +647,14 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) // maintains the m_model pointer ModelObject& ModelObject::assign_copy(ModelObject &&rhs) { + assert(this->id().invalid()); this->copy_id(rhs); this->name = std::move(rhs.name); this->input_file = std::move(rhs.input_file); + // Moves the config's ID this->config = std::move(rhs.config); + assert(this->config.id() == rhs.config.id()); this->sla_support_points = std::move(rhs.sla_support_points); this->sla_points_status = std::move(rhs.sla_points_status); this->layer_height_ranges = std::move(rhs.layer_height_ranges); @@ -1176,13 +1183,19 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b if (keep_upper && upper_mesh.facets_count() > 0) { ModelVolume* vol = upper->add_volume(upper_mesh); vol->name = volume->name; - vol->config = volume->config; + // Don't copy the config's ID. + static_cast(vol->config) = static_cast(volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); } if (keep_lower && lower_mesh.facets_count() > 0) { ModelVolume* vol = lower->add_volume(lower_mesh); vol->name = volume->name; - vol->config = volume->config; + // Don't copy the config's ID. + static_cast(vol->config) = static_cast(volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); // Compute the lower part instances' bounding boxes to figure out where to place @@ -1257,7 +1270,10 @@ void ModelObject::split(ModelObjectPtrs* new_objects) // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed? ModelObject* new_object = m_model->add_object(); new_object->name = this->name; - new_object->config = this->config; + // Don't copy the config's ID. + static_cast(new_object->config) = static_cast(this->config); + assert(new_object->config.id().valid()); + assert(new_object->config.id() != this->config.id()); new_object->instances.reserve(this->instances.size()); for (const ModelInstance *model_instance : this->instances) new_object->add_instance(*model_instance); @@ -1852,19 +1868,24 @@ void check_model_ids_validity(const Model &model) { std::set ids; auto check = [&ids](ObjectID id) { - assert(id.id > 0); + assert(id.valid()); assert(ids.find(id) == ids.end()); ids.insert(id); }; for (const ModelObject *model_object : model.objects) { check(model_object->id()); - for (const ModelVolume *model_volume : model_object->volumes) + check(model_object->config.id()); + for (const ModelVolume *model_volume : model_object->volumes) { check(model_volume->id()); + check(model_volume->config.id()); + } for (const ModelInstance *model_instance : model_object->instances) check(model_instance->id()); } - for (const auto mm : model.materials) + for (const auto mm : model.materials) { check(mm.second->id()); + check(mm.second->config.id()); + } } void check_model_ids_equal(const Model &model1, const Model &model2) @@ -1875,10 +1896,13 @@ void check_model_ids_equal(const Model &model1, const Model &model2) const ModelObject &model_object1 = *model1.objects[idx_model]; const ModelObject &model_object2 = * model2.objects[idx_model]; assert(model_object1.id() == model_object2.id()); + assert(model_object1.config.id() == model_object2.config.id()); assert(model_object1.volumes.size() == model_object2.volumes.size()); assert(model_object1.instances.size() == model_object2.instances.size()); - for (size_t i = 0; i < model_object1.volumes.size(); ++ i) + for (size_t i = 0; i < model_object1.volumes.size(); ++ i) { assert(model_object1.volumes[i]->id() == model_object2.volumes[i]->id()); + assert(model_object1.volumes[i]->config.id() == model_object2.volumes[i]->config.id()); + } for (size_t i = 0; i < model_object1.instances.size(); ++ i) assert(model_object1.instances[i]->id() == model_object2.instances[i]->id()); } @@ -1889,6 +1913,7 @@ void check_model_ids_equal(const Model &model1, const Model &model2) for (; it1 != model1.materials.end(); ++ it1, ++ it2) { assert(it1->first == it2->first); // compare keys assert(it1->second->id() == it2->second->id()); + assert(it1->second->config.id() == it2->second->config.id()); } } } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 8e9f7ecaa9..5fd958f862 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -31,6 +31,34 @@ namespace UndoRedo { class StackImpl; } +class ModelConfig : public ObjectBase, public DynamicPrintConfig +{ +private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + friend class ModelObject; + friend class ModelVolume; + friend class ModelMaterial; + + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit ModelConfig() {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit ModelConfig(int) : ObjectBase(-1) {} + // Copy constructor copies the ID. + explicit ModelConfig(const ModelConfig &cfg) : ObjectBase(-1), DynamicPrintConfig(cfg) { this->copy_id(cfg); } + // Move constructor copies the ID. + explicit ModelConfig(ModelConfig &&cfg) : ObjectBase(-1), DynamicPrintConfig(std::move(cfg)) { this->copy_id(cfg); } + + ModelConfig& operator=(const ModelConfig &rhs) = default; + ModelConfig& operator=(ModelConfig &&rhs) = default; + + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + } +}; + typedef std::string t_model_material_id; typedef std::string t_model_material_attribute; typedef std::map t_model_material_attributes; @@ -43,10 +71,10 @@ typedef std::vector ModelInstancePtrs; #define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \ /* to make a private copy for background processing. */ \ - static TYPE* new_copy(const TYPE &rhs) { return new TYPE(rhs); } \ - static TYPE* new_copy(TYPE &&rhs) { return new TYPE(std::move(rhs)); } \ - static TYPE make_copy(const TYPE &rhs) { return TYPE(rhs); } \ - static TYPE make_copy(TYPE &&rhs) { return TYPE(std::move(rhs)); } \ + static TYPE* new_copy(const TYPE &rhs) { auto *ret = new TYPE(rhs); assert(ret->id() == rhs.id()); return ret; } \ + static TYPE* new_copy(TYPE &&rhs) { auto *ret = new TYPE(std::move(rhs)); assert(ret->id() == rhs.id()); return ret; } \ + static TYPE make_copy(const TYPE &rhs) { TYPE ret(rhs); assert(ret.id() == rhs.id()); return ret; } \ + static TYPE make_copy(TYPE &&rhs) { TYPE ret(std::move(rhs)); assert(ret.id() == rhs.id()); return ret; } \ TYPE& assign_copy(const TYPE &rhs); \ TYPE& assign_copy(TYPE &&rhs); \ /* Copy a TYPE, generate new IDs. The front end will use this call. */ \ @@ -54,26 +82,24 @@ typedef std::vector ModelInstancePtrs; /* Default constructor assigning an invalid ID. */ \ auto obj = new TYPE(-1); \ obj->assign_clone(rhs); \ + assert(obj->id().valid() && obj->id() != rhs.id()); \ return obj; \ } \ TYPE make_clone(const TYPE &rhs) { \ /* Default constructor assigning an invalid ID. */ \ TYPE obj(-1); \ obj.assign_clone(rhs); \ + assert(obj.id().valid() && obj.id() != rhs.id()); \ return obj; \ } \ TYPE& assign_clone(const TYPE &rhs) { \ this->assign_copy(rhs); \ + assert(this->id().valid() && this->id() == rhs.id()); \ this->assign_new_unique_ids_recursive(); \ + assert(this->id().valid() && this->id() != rhs.id()); \ return *this; \ } -#define OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE(TYPE) \ -private: \ - /* Private constructor with an unused int parameter will create a TYPE instance with an invalid ID. */ \ - explicit TYPE(int) : ObjectBase(-1) {}; \ - void assign_new_unique_ids_recursive(); - // Material, which may be shared across multiple ModelObjects of a single Model. class ModelMaterial final : public ObjectBase { @@ -81,32 +107,40 @@ public: // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. t_model_material_attributes attributes; // Dynamic configuration storage for the object specific configuration values, overriding the global configuration. - DynamicPrintConfig config; + ModelConfig config; Model* get_model() const { return m_model; } void apply(const t_model_material_attributes &attributes) { this->attributes.insert(attributes.begin(), attributes.end()); } -protected: - friend class Model; - // Constructor, which assigns a new unique ID. - ModelMaterial(Model *model) : m_model(model) {} - // Copy constructor copies the ID and m_model! - ModelMaterial(const ModelMaterial &rhs) = default; - void set_model(Model *model) { m_model = model; } - private: // Parent, owning this material. Model *m_model; - + + // To be accessed by the Model. + friend class Model; + // Constructor, which assigns a new unique ID to the material and to its config. + ModelMaterial(Model *model) : m_model(model) { assert(this->id().valid()); } + // Copy constructor copies the IDs of the ModelMaterial and its config, and m_model! + ModelMaterial(const ModelMaterial &rhs) = default; + void set_model(Model *model) { m_model = model; } + void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } + + // To be accessed by the serialization and Undo/Redo code. + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Create an object for deserialization, don't allocate IDs for ModelMaterial and its config. + ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); } + template void serialize(Archive &ar) { + assert(this->id().invalid()); assert(this->config.id().invalid()); + ar(attributes, config); + // assert(this->id().valid()); assert(this->config.id().valid()); + } + + // Disabled methods. ModelMaterial(ModelMaterial &&rhs) = delete; ModelMaterial& operator=(const ModelMaterial &rhs) = delete; ModelMaterial& operator=(ModelMaterial &&rhs) = delete; - - friend class cereal::access; - friend class UndoRedo::StackImpl; - ModelMaterial() : m_model(nullptr) {} - template void serialize(Archive &ar) { ar(cereal::base_class(this)); ar(attributes, config); } }; // A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), @@ -115,7 +149,6 @@ private: // different rotation and different uniform scaling. class ModelObject final : public ObjectBase { - friend class Model; public: std::string name; std::string input_file; // XXX: consider fs::path @@ -126,7 +159,7 @@ public: // ModelVolumes are owned by this ModelObject. ModelVolumePtrs volumes; // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings. - DynamicPrintConfig config; + ModelConfig config; // Variation of a layer thickness for spans of Z coordinates. t_layer_height_ranges layer_height_ranges; // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. @@ -236,25 +269,53 @@ public: std::string get_export_filename() const; - // Get full stl statistics for all object's meshes + // Get full stl statistics for all object's meshes stl_stats get_object_stl_stats() const; - // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) + // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) int get_mesh_errors_count(const int vol_idx = -1) const; private: - ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()), - m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} - ~ModelObject(); + friend class Model; + // This constructor assigns new ID to this ModelObject and its config. + explicit ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()), + m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) + { assert(this->id().valid()); } + explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) + { assert(this->id().invalid()); assert(this->config.id().invalid()); } + ~ModelObject(); + void assign_new_unique_ids_recursive(); - /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ - /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ - ModelObject(const ModelObject &rhs) : ObjectBase(-1), m_model(rhs.m_model) { this->assign_copy(rhs); } - explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1) { this->assign_copy(std::move(rhs)); } - ModelObject& operator=(const ModelObject &rhs) { this->assign_copy(rhs); m_model = rhs.m_model; return *this; } - ModelObject& operator=(ModelObject &&rhs) { this->assign_copy(std::move(rhs)); m_model = rhs.m_model; return *this; } + // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" + // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). + ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), m_model(rhs.m_model) { + assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id()); + this->assign_copy(rhs); + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id()); + } + explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1) { + assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id()); + this->assign_copy(std::move(rhs)); + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id()); + } + ModelObject& operator=(const ModelObject &rhs) { + this->assign_copy(rhs); + m_model = rhs.m_model; + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id()); + return *this; + } + ModelObject& operator=(ModelObject &&rhs) { + this->assign_copy(std::move(rhs)); + m_model = rhs.m_model; + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id()); + return *this; + } + void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) - OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE(ModelObject) // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized. Model *m_model = nullptr; @@ -275,8 +336,11 @@ private: // Undo / Redo through the cereal serialization library friend class cereal::access; friend class UndoRedo::StackImpl; - ModelObject() : m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} - template void serialize(Archive &ar) { + // Used for deserialization -> Don't allocate any IDs for the ModelObject or its config. + ModelObject() : ObjectBase(-1), config(-1), m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) { + assert(this->id().invalid()); assert(this->config.id().invalid()); + } + template void serialize(Archive &ar) { ar(cereal::base_class(this)); ar(name, input_file, instances, volumes, config, layer_height_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation, m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); @@ -307,7 +371,7 @@ public: void reset_mesh() { m_mesh = std::make_shared(); } // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. - DynamicPrintConfig config; + ModelConfig config; // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; }; @@ -388,13 +452,14 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - using ObjectBase::set_new_unique_id; + void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } protected: friend class Print; friend class SLAPrint; friend class ModelObject; + // Copies IDs of both the ModelVolume and its config. explicit ModelVolume(const ModelVolume &rhs) = default; void set_model_object(ModelObject *model_object) { object = model_object; } void transform_this_mesh(const Transform3d& t, bool fix_left_handed); @@ -420,33 +485,46 @@ private: ModelVolume(ModelObject *object, const TriangleMesh &mesh) : m_mesh(new TriangleMesh(mesh)), m_type(ModelVolumeType::MODEL_PART), object(object) { + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); } ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : - m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) {} + m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) { + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + } // Copying an existing volume, therefore this volume will get a copy of the ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other) : - ObjectBase(other), // copy the ID + ObjectBase(other), name(other.name), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == other.id() && this->config.id() == other.config.id()); this->set_material_id(other.material_id()); } // Providing a new mesh, therefore this volume will get a new unique ID assigned. ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() == other.id() && this->config.id() == other.config.id()); this->set_material_id(other.material_id()); + this->config.set_new_unique_id(); if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() != other.id() && this->config.id() != other.config.id()); } ModelVolume& operator=(ModelVolume &rhs) = delete; friend class cereal::access; friend class UndoRedo::StackImpl; - ModelVolume() : object(nullptr) {} + // Used for deserialization, therefore no IDs are allocated. + ModelVolume() : ObjectBase(-1), config(-1), object(nullptr) { + assert(this->id().invalid()); assert(this->config.id().invalid()); + } template void serialize(Archive &ar) { ar(name, config, m_mesh, m_type, m_material_id, m_convex_hull, m_transformation, m_is_splittable); } @@ -530,10 +608,10 @@ private: ModelObject* object; // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) {} + explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) { assert(this->id().valid()); } // Constructor, which assigns a new unique ID. explicit ModelInstance(ModelObject *object, const ModelInstance &other) : - m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) {} + m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) { assert(this->id().valid() && this->id() != other.id()); } explicit ModelInstance(ModelInstance &&rhs) = delete; ModelInstance& operator=(const ModelInstance &rhs) = delete; @@ -541,9 +619,9 @@ private: friend class cereal::access; friend class UndoRedo::StackImpl; - ModelInstance() : object(nullptr) {} + // Used for deserialization, therefore no IDs are allocated. + ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); } template void serialize(Archive &ar) { - ar(cereal::base_class(this)); ar(m_transformation, print_volume_state); } }; @@ -565,15 +643,15 @@ public: ModelObjectPtrs objects; // Default constructor assigns a new ID to the model. - Model() {} + Model() { assert(this->id().valid()); } ~Model() { this->clear_objects(); this->clear_materials(); } /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ - Model(const Model &rhs) : ObjectBase(-1) { this->assign_copy(rhs); } - explicit Model(Model &&rhs) : ObjectBase(-1) { this->assign_copy(std::move(rhs)); } - Model& operator=(const Model &rhs) { this->assign_copy(rhs); return *this; } - Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); return *this; } + Model(const Model &rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); } + explicit Model(Model &&rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); } + Model& operator=(const Model &rhs) { this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } + Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) @@ -633,12 +711,13 @@ public: std::string propose_export_file_name_and_path(const std::string &new_extension) const; private: - OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE(Model) + explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }; + void assign_new_unique_ids_recursive(); friend class cereal::access; friend class UndoRedo::StackImpl; template void serialize(Archive &ar) { - ar(cereal::base_class(this), materials, objects); + ar(materials, objects); } }; diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index f00d6f61ea..0988acf5a9 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -33,6 +33,7 @@ public: bool operator>=(const ObjectID &rhs) const { return this->id >= rhs.id; } bool valid() const { return id != 0; } + bool invalid() const { return id == 0; } size_t id; @@ -72,6 +73,7 @@ protected: void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } private: + friend class UndoRedo::StackImpl; ObjectID m_id; static inline ObjectID generate_new_id() { return ObjectID(++ s_last_id); } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 6d8c6e2bbb..e545b9b7bc 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -612,7 +612,7 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, assert(mv_src.id() == mv_dst.id()); // Copy the ModelVolume data. mv_dst.name = mv_src.name; - mv_dst.config = mv_src.config; + static_cast(mv_dst.config) = static_cast(mv_src.config); //FIXME what to do with the materials? // mv_dst.m_material_id = mv_src.m_material_id; ++ i_src; @@ -899,7 +899,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co // Synchronize Object's config. bool object_config_changed = model_object.config != model_object_new.config; if (object_config_changed) - model_object.config = model_object_new.config; + static_cast(model_object.config) = static_cast(model_object_new.config); if (! object_diff.empty() || object_config_changed) { PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders); auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index aa7cf58b53..47b259f647 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -368,7 +368,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf // Synchronize Object's config. bool object_config_changed = model_object.config != model_object_new.config; if (object_config_changed) - model_object.config = model_object_new.config; + static_cast(model_object.config) = static_cast(model_object_new.config); if (! object_diff.empty() || object_config_changed) { SLAPrintObjectConfig new_config = m_default_object_config; normalize_and_apply_config(new_config, model_object.config); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 97168ee045..0784b70ffe 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1160,7 +1160,7 @@ void Selection::copy_to_clipboard() ModelObject* dst_object = m_clipboard.add_object(); dst_object->name = src_object->name; dst_object->input_file = src_object->input_file; - dst_object->config = src_object->config; + static_cast(dst_object->config) = static_cast(src_object->config); dst_object->sla_support_points = src_object->sla_support_points; dst_object->sla_points_status = src_object->sla_points_status; dst_object->layer_height_ranges = src_object->layer_height_ranges; diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 1e49c13361..74565a3010 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -117,7 +117,9 @@ public: bool is_immutable() const override { return true; } void save(size_t active_snapshot_time, size_t current_time) { - assert(m_history.empty() || m_history.back().end() <= active_snapshot_time); + assert(m_history.empty() || m_history.back().end() <= active_snapshot_time || + // The snapshot of an immutable object may have already been taken from another mutable object. + (m_history.back().begin() <= active_snapshot_time && m_history.back().end() == current_time + 1)); if (m_history.empty() || m_history.back().end() < active_snapshot_time) m_history.emplace_back(active_snapshot_time, current_time + 1); else @@ -335,11 +337,11 @@ public: void load_model(const Slic3r::Model &model, size_t snapshot_time); void load_selection(const Slic3r::GUI::Selection &selection, size_t snapshot_time); - template ObjectID save_mutable_object(const T &object); + template ObjectID save_mutable_object(const T &object); template ObjectID save_immutable_object(std::shared_ptr &object); template T* load_mutable_object(const Slic3r::ObjectID id); template std::shared_ptr load_immutable_object(const Slic3r::ObjectID id); - template void load_mutable_object(const Slic3r::ObjectID id, T &target); + template void load_mutable_object(const Slic3r::ObjectID id, T &target); private: template ObjectID immutable_object_id(const std::shared_ptr &ptr) { @@ -379,6 +381,8 @@ class ModelObject; class ModelVolume; class ModelInstance; class ModelMaterial; +class ModelConfig; +class DynamicPrintConfig; class TriangleMesh; } // namespace Slic3r @@ -392,13 +396,14 @@ namespace cereal template struct specialize {}; template struct specialize {}; template struct specialize {}; + template struct specialize {}; template struct specialize, cereal::specialization::non_member_load_save> {}; // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, // store just the ObjectID to this stream. template void save(BinaryOutputArchive& ar, T* const& ptr) { - ar(cereal::get_user_data(ar).save_mutable_object(*ptr)); + ar(cereal::get_user_data(ar).save_mutable_object(*ptr)); } // Load ObjectBase derived class from the Undo / Redo stack as a separate object @@ -411,6 +416,40 @@ namespace cereal ptr = stack.load_mutable_object(Slic3r::ObjectID(id)); } + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, + // store just the ObjectID to this stream. + template void save(BinaryOutputArchive &ar, const std::unique_ptr &ptr) + { + ar(cereal::get_user_data(ar).save_mutable_object(*ptr.get())); + } + + // Load ObjectBase derived class from the Undo / Redo stack as a separate object + // based on the ObjectID loaded from this stream. + template void load(BinaryInputArchive &ar, std::unique_ptr &ptr) + { + Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data(ar); + size_t id; + ar(id); + ptr.reset(stack.load_mutable_object(Slic3r::ObjectID(id))); + } + + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, + // store just the ObjectID to this stream. + void save(BinaryOutputArchive& ar, const Slic3r::ModelConfig &cfg) + { + ar(cereal::get_user_data(ar).save_mutable_object(cfg)); + } + + // Load ObjectBase derived class from the Undo / Redo stack as a separate object + // based on the ObjectID loaded from this stream. + void load(BinaryInputArchive& ar, Slic3r::ModelConfig &cfg) + { + Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data(ar); + size_t id; + ar(id); + stack.load_mutable_object(Slic3r::ObjectID(id), cfg); + } + // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, // store just the ObjectID to this stream. template void save(BinaryOutputArchive &ar, const std::shared_ptr &ptr) @@ -458,7 +497,7 @@ namespace cereal namespace Slic3r { namespace UndoRedo { -template ObjectID StackImpl::save_mutable_object(const T &object) +template ObjectID StackImpl::save_mutable_object(const T &object) { // First find or allocate a history stack for the ObjectID of this object instance. auto it_object_history = m_objects.find(object.id()); @@ -469,7 +508,7 @@ template ObjectID StackImpl::save_mutable_object(const T &object) std::ostringstream oss; { Slic3r::UndoRedo::OutputArchive archive(*this, oss); - archive(object); + archive(static_cast(object)); } object_history->save(m_active_snapshot_time, m_current_time, oss.str()); return object.id(); @@ -491,7 +530,7 @@ template ObjectID StackImpl::save_immutable_object(std::shared_ptr T* StackImpl::load_mutable_object(const Slic3r::ObjectID id) { T *target = new T(); - this->load_mutable_object(id, *target); + this->load_mutable_object(id, *target); return target; } @@ -505,7 +544,7 @@ template std::shared_ptr StackImpl::load_immutable_object(c return object_history->shared_ptr(*this); } -template void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target) +template void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target) { // First find a history stack for the ObjectID of this object instance. auto it_object_history = m_objects.find(id); @@ -514,7 +553,8 @@ template void StackImpl::load_mutable_object(const Slic3r::ObjectID // Then get the data associated with the object history and m_active_snapshot_time. std::istringstream iss(object_history->load(m_active_snapshot_time)); Slic3r::UndoRedo::InputArchive archive(*this, iss); - archive(target); + target.m_id = id; + archive(static_cast(target)); } // The Undo / Redo stack is being initialized with an empty model and an empty selection. @@ -541,7 +581,7 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo m_snapshots.erase(it, m_snapshots.end()); } // Take new snapshots. - this->save_mutable_object(model); + this->save_mutable_object(model); // this->save_mutable_object(selection); // Save the snapshot info m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id); @@ -556,8 +596,10 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GU if (it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); - this->load_mutable_object(ObjectID(it_snapshot->model_object_id), model); - this->load_mutable_object(selection.id(), selection); + model.clear_objects(); + model.clear_materials(); + this->load_mutable_object(ObjectID(it_snapshot->model_object_id), model); + this->load_mutable_object(selection.id(), selection); this->m_active_snapshot_time = timestamp; } From bc315f4c2c87bc6a1262696be96df29af4147d6c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 3 Jul 2019 15:06:10 +0200 Subject: [PATCH 257/627] Deal with infinite box. --- .../libnest2d/backends/clipper/geometries.hpp | 8 +- .../include/libnest2d/geometry_traits.hpp | 25 +- .../include/libnest2d/placers/nfpplacer.hpp | 8 +- src/libnest2d/tests/test.cpp | 1 + src/libslic3r/Arrange.cpp | 258 ++++++++++-------- src/libslic3r/Model.cpp | 3 + 6 files changed, 175 insertions(+), 128 deletions(-) diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index e9fad405b6..c51e0f5c94 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -41,25 +41,25 @@ template<> struct HolesContainer { using Type = ClipperLib::Paths; namespace pointlike { // Tell libnest2d how to extract the X coord from a ClipperPoint object -template<> inline TCoord x(const PointImpl& p) +template<> inline ClipperLib::cInt x(const PointImpl& p) { return p.X; } // Tell libnest2d how to extract the Y coord from a ClipperPoint object -template<> inline TCoord y(const PointImpl& p) +template<> inline ClipperLib::cInt y(const PointImpl& p) { return p.Y; } // Tell libnest2d how to extract the X coord from a ClipperPoint object -template<> inline TCoord& x(PointImpl& p) +template<> inline ClipperLib::cInt& x(PointImpl& p) { return p.X; } // Tell libnest2d how to extract the Y coord from a ClipperPoint object -template<> inline TCoord& y(PointImpl& p) +template<> inline ClipperLib::cInt& y(PointImpl& p) { return p.Y; } diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 8b87229c08..5c213c110c 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -166,7 +166,9 @@ public: using Tag = BoxTag; using PointType = P; - inline _Box(const P& p = {TCoord

(0), TCoord

(0)}); + inline _Box(const P& center = {TCoord

(0), TCoord

(0)}): + _Box(TCoord

(0), TCoord

(0), center) {} + inline _Box(const P& p, const P& pp): PointPair

({p, pp}) {} @@ -189,6 +191,8 @@ public: inline Unit area() const BP2D_NOEXCEPT { return Unit(width())*height(); } + + static inline _Box infinite(const P ¢er); }; template struct PointType<_Box> { @@ -463,12 +467,19 @@ inline _Box

::_Box(TCoord

width, TCoord

height, const P & center) : modulo(height, TCoord

(2))}}) {} template -inline _Box

::_Box(const P& center) { +inline _Box

_Box

::infinite(const P& center) { using C = TCoord

; - TCoord

M = std::max(getX(center), getY(center)) - - std::numeric_limits::lowest(); - maxCorner() = center + P{M, M}; - minCorner() = center - P{M, M}; + _Box

ret; + + // It is important for Mx and My to be strictly less than half of the + // range of type C. width(), height() and area() will not overflow this way. + C Mx = C((std::numeric_limits::lowest() + 2 * getX(center)) / 2.01); + C My = C((std::numeric_limits::lowest() + 2 * getY(center)) / 2.01); + + ret.maxCorner() = center - P{Mx, My}; + ret.minCorner() = center + P{Mx, My}; + + return ret; } template @@ -478,7 +489,7 @@ inline P _Box

::center() const BP2D_NOEXCEPT { using Coord = TCoord

; - P ret = { // No rounding here, we dont know if these are int coords + P ret = { // No rounding here, we dont know if these are int coords Coord( (getX(minc) + getX(maxc)) / Coord(2) ), Coord( (getY(minc) + getY(maxc)) / Coord(2) ) }; diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index b94443bff5..4ef1fe71d9 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -581,8 +581,12 @@ public: static inline double overfit(const Box& bb, const Box& bin) { - auto wdiff = double(bb.width() - bin.width()); - auto hdiff = double(bb.height() - bin.height()); + auto Bw = bin.width(); + auto Bh = bin.height(); + auto mBw = -Bw; + auto mBh = -Bh; + auto wdiff = double(bb.width()) + mBw; + auto hdiff = double(bb.height()) + mBh; double diff = 0; if(wdiff > 0) diff += wdiff; if(hdiff > 0) diff += hdiff; diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 72a600dbbf..d0c660e498 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -379,6 +379,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) for(Item& r2 : result) { if(&r1 != &r2 ) { valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); + ASSERT_TRUE(valid); valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); ASSERT_TRUE(valid); } diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 2c4417b4d2..84a406fd91 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -58,19 +58,24 @@ namespace arrangement { using namespace libnest2d; namespace clppr = ClipperLib; +// Get the libnest2d types for clipper backend using Item = _Item; using Box = _Box; using Circle = _Circle; using Segment = _Segment; using MultiPolygon = TMultiShape; + +// The return value of nesting, a vector (for each logical bed) of Item +// reference vectors. using PackGroup = _PackGroup; +// Summon the spatial indexing facilities from boost namespace bgi = boost::geometry::index; - using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; using ItemGroup = std::vector>; +// A coefficient used in separating bigger items and smaller items. const double BIG_ITEM_TRESHOLD = 0.02; // Fill in the placer algorithm configuration with values carefully chosen for @@ -85,14 +90,14 @@ void fillConfig(PConf& pcfg) { pcfg.starting_point = PConf::Alignment::CENTER; // TODO cannot use rotations until multiple objects of same geometry can - // handle different rotations - // arranger.useMinimumBoundigBoxRotation(); + // handle different rotations. pcfg.rotations = { 0.0 }; // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well pcfg.accuracy = 0.65f; - + + // Allow parallel execution. pcfg.parallel = true; } @@ -153,7 +158,7 @@ protected: }; // Candidate item bounding box - auto ibb = sl::boundingBox(item.transformedShape()); + auto ibb = item.boundingBox(); // Calculate the full bounding box of the pile with the candidate item auto fullbb = sl::boundingBox(m_pilebb, ibb); @@ -170,16 +175,39 @@ protected: // Will hold the resulting score double score = 0; - if(isBig(item.area()) || spatindex.empty()) { - // This branch is for the bigger items.. + // Density is the pack density: how big is the arranged pile + double density = 0; + + const double N = m_norm; + auto norm = [N](double val) { return val / N; }; + + // Distinction of cases for the arrangement scene + enum e_cases { + // This branch is for big items in a mixed (big and small) scene + // OR for all items in a small-only scene. + BIG_ITEM, - auto minc = ibb.minCorner(); // bottom left corner - auto maxc = ibb.maxCorner(); // top right corner + // This branch is for the last big item in a mixed scene + LAST_BIG_ITEM, + // For small items in a mixed scene. + SMALL_ITEM + } compute_case; + + bool bigitems = isBig(item.area()) || spatindex.empty(); + if(bigitems && !remaining.empty()) compute_case = BIG_ITEM; + else if (bigitems && remaining.empty()) compute_case = LAST_BIG_ITEM; + else compute_case = SMALL_ITEM; + + switch (compute_case) { + case BIG_ITEM: { + const clppr::IntPoint& minc = ibb.minCorner(); // bottom left corner + const clppr::IntPoint& maxc = ibb.maxCorner(); // top right corner + // top left and bottom right corners - auto top_left = PointImpl{getX(minc), getY(maxc)}; - auto bottom_right = PointImpl{getX(maxc), getY(minc)}; - + clppr::IntPoint top_left{getX(minc), getY(maxc)}; + clppr::IntPoint bottom_right{getX(maxc), getY(minc)}; + // Now the distance of the gravity center will be calculated to the // five anchor points and the smallest will be chosen. std::array dists; @@ -189,79 +217,75 @@ protected: dists[2] = pl::distance(ibb.center(), cc); dists[3] = pl::distance(top_left, cc); dists[4] = pl::distance(bottom_right, cc); - - // The smalles distance from the arranged pile center: - double dist = *(std::min_element(dists.begin(), dists.end())) / m_norm; - double bindist = pl::distance(ibb.center(), bincenter) / m_norm; - dist = 0.8*dist + 0.2*bindist; - - // Density is the pack density: how big is the arranged pile - double density = 0; - - if(remaining.empty()) { - - auto mp = m_merged_pile; - mp.emplace_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - - placers::EdgeCache ec(chull); - - double circ = ec.circumference() / m_norm; - double bcirc = 2.0*(fullbb.width() + fullbb.height()) / m_norm; - score = 0.5*circ + 0.5*bcirc; - - } else { - // Prepare a variable for the alignment score. - // This will indicate: how well is the candidate item - // aligned with its neighbors. We will check the alignment - // with all neighbors and return the score for the best - // alignment. So it is enough for the candidate to be - // aligned with only one item. - auto alignment_score = 1.0; - - auto querybb = item.boundingBox(); - density = std::sqrt((fullbb.width() / m_norm )* - (fullbb.height() / m_norm)); - - // Query the spatial index for the neighbors - std::vector result; - result.reserve(spatindex.size()); - if(isBig(item.area())) { - spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); - } else { - smalls_spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); - } - - // now get the score for the best alignment - for(auto& e : result) { - auto idx = e.second; - Item& p = m_items[idx]; - auto parea = p.area(); - if(std::abs(1.0 - parea/item.area()) < 1e-6) { - auto bb = sl::boundingBox(p.boundingBox(), ibb); - auto bbarea = bb.area(); - auto ascore = 1.0 - (item.area() + parea)/bbarea; - - if(ascore < alignment_score) alignment_score = ascore; - } - } - // The final mix of the score is the balance between the - // distance from the full pile center, the pack density and - // the alignment with the neighbors - if (result.empty()) - score = 0.5 * dist + 0.5 * density; - else - score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; + // The smalles distance from the arranged pile center: + double dist = norm(*(std::min_element(dists.begin(), dists.end()))); + double bindist = norm(pl::distance(ibb.center(), bincenter)); + dist = 0.8 * dist + 0.2*bindist; + + // Prepare a variable for the alignment score. + // This will indicate: how well is the candidate item + // aligned with its neighbors. We will check the alignment + // with all neighbors and return the score for the best + // alignment. So it is enough for the candidate to be + // aligned with only one item. + auto alignment_score = 1.0; + + auto query = bgi::intersects(ibb); + auto& index = isBig(item.area()) ? spatindex : smalls_spatindex; + + // Query the spatial index for the neighbors + std::vector result; + result.reserve(index.size()); + + index.query(query, std::back_inserter(result)); + + // now get the score for the best alignment + for(auto& e : result) { + auto idx = e.second; + Item& p = m_items[idx]; + auto parea = p.area(); + if(std::abs(1.0 - parea/item.area()) < 1e-6) { + auto bb = sl::boundingBox(p.boundingBox(), ibb); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; + + if(ascore < alignment_score) alignment_score = ascore; + } } - } else { + + density = std::sqrt(norm(fullbb.width()) * norm(fullbb.height())); + + // The final mix of the score is the balance between the + // distance from the full pile center, the pack density and + // the alignment with the neighbors + if (result.empty()) + score = 0.5 * dist + 0.5 * density; + else + score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; + + break; + } + case LAST_BIG_ITEM: { + auto mp = m_merged_pile; + mp.emplace_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + + placers::EdgeCache ec(chull); + + double circ = norm(ec.circumference()); + double bcirc = 2.0 * norm(fullbb.width() + fullbb.height()); + score = 0.5 * circ + 0.5 * bcirc; + break; + } + case SMALL_ITEM: { // Here there are the small items that should be placed around the // already processed bigger items. // No need to play around with the anchor points, the center will be // just fine for small items - score = pl::distance(ibb.center(), bigbb.center()) / m_norm; + score = norm(pl::distance(ibb.center(), bigbb.center())); + break; + } } return std::make_tuple(score, fullbb); @@ -276,7 +300,8 @@ public: std::function stopcond) : m_pck(bin, dist) , m_bin(bin) - , m_norm(std::sqrt(sl::area(bin))) + , m_bin_area(sl::area(bin)) + , m_norm(std::sqrt(m_bin_area)) { fillConfig(m_pconf); @@ -349,8 +374,6 @@ public: } }; - - template<> std::function AutoArranger::get_objfn() { auto bincenter = m_bin.center(); @@ -612,46 +635,51 @@ bool arrange(ArrangeablePtrs & arrangables, auto& cfn = stopcondition; switch (bedhint.type) { - case BedShapeType::BOX: { - // Create the arranger for the box shaped bed - BoundingBox bbb = bedhint.shape.box; - bbb.min -= Point{md, md}, bbb.max += Point{md, md}; - Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; - binwidth = coord_t(binbb.width()); +// case BedShapeType::BOX: { +// // Create the arranger for the box shaped bed +// BoundingBox bbb = bedhint.shape.box; +// bbb.min -= Point{md, md}, bbb.max += Point{md, md}; +// Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; +// binwidth = coord_t(binbb.width()); - _arrange(items, fixeditems, binbb, min_obj_distance, progressind, cfn); - break; - } - case BedShapeType::CIRCLE: { - auto c = bedhint.shape.circ; - auto cc = to_lnCircle(c); - binwidth = scaled(c.radius()); +// _arrange(items, fixeditems, binbb, min_obj_distance, progressind, cfn); +// break; +// } +// case BedShapeType::CIRCLE: { +// auto c = bedhint.shape.circ; +// auto cc = to_lnCircle(c); +// binwidth = scaled(c.radius()); - _arrange(items, fixeditems, cc, min_obj_distance, progressind, cfn); - break; - } - case BedShapeType::IRREGULAR: { - auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon); - auto irrbed = sl::create(std::move(ctour)); - BoundingBox polybb(bedhint.shape.polygon); - binwidth = (polybb.max(X) - polybb.min(X)); +// _arrange(items, fixeditems, cc, min_obj_distance, progressind, cfn); +// break; +// } +// case BedShapeType::IRREGULAR: { +// auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon); +// auto irrbed = sl::create(std::move(ctour)); +// BoundingBox polybb(bedhint.shape.polygon); +// binwidth = (polybb.max(X) - polybb.min(X)); - _arrange(items, fixeditems, irrbed, min_obj_distance, progressind, cfn); - break; - } - case BedShapeType::INFINITE: { - // const InfiniteBed& nobin = bedhint.shape.infinite; - //Box infbb{{nobin.center.x(), nobin.center.y()}}; - Box infbb; +// _arrange(items, fixeditems, irrbed, min_obj_distance, progressind, cfn); +// break; +// } +// case BedShapeType::INFINITE: { +// const InfiniteBed& nobin = bedhint.shape.infinite; +// Box infbb{{nobin.center.x(), nobin.center.y()}}; +// _arrange(items, fixeditems, infbb, min_obj_distance, progressind, cfn); +// break; +// } +// case BedShapeType::UNKNOWN: { +// // We know nothing about the bed, let it be infinite and zero centered +// _arrange(items, fixeditems, Box{}, min_obj_distance, progressind, cfn); +// break; +// } + default: { + Box infbb = Box::infinite({bedhint.shape.box.center().x(), bedhint.shape.box.center().y()}); + _arrange(items, fixeditems, infbb, min_obj_distance, progressind, cfn); break; } - case BedShapeType::UNKNOWN: { - // We know nothing about the bed, let it be infinite and zero centered - _arrange(items, fixeditems, Box{}, min_obj_distance, progressind, cfn); - break; - } }; if(stopcondition()) return false; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index fd48dcec48..2af4e4c275 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -398,6 +398,9 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) // } // o->invalidate_bounding_box(); // } + +// return true; + size_t count = 0; for (auto obj : objects) count += obj->instances.size(); From 040f1fedff1f4caa310622c54adbc3d5d5b97aab Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 3 Jul 2019 14:52:39 +0200 Subject: [PATCH 258/627] Added UI-prototype for FilamentSettings->Overrides page --- src/slic3r/GUI/OptionsGroup.cpp | 2 +- src/slic3r/GUI/OptionsGroup.hpp | 3 ++ src/slic3r/GUI/Tab.cpp | 91 ++++++++++++++++++++++++++++++++- src/slic3r/GUI/Tab.hpp | 2 + 4 files changed, 96 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 0149329005..2b70dfb4c8 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -202,7 +202,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // so we need a horizontal sizer to arrange these things auto sizer = new wxBoxSizer(wxHORIZONTAL); grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1); - sizer->Add(m_near_label_widget_ptrs.back(), 0, wxRIGHT, 7); + sizer->Add(m_near_label_widget_ptrs.back(), 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 7); sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5); } } diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 73b2c5110f..f7b536e9c5 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -244,6 +244,9 @@ public: Option option = get_option(title, idx); return OptionsGroup::create_single_option_line(option); } + Line create_single_option_line(const Option& option) const { + return OptionsGroup::create_single_option_line(option); + } void append_single_option_line(const Option& option) { OptionsGroup::append_single_option_line(option); } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6e8b8a471e..f45b6017a6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1492,6 +1492,91 @@ void TabPrint::OnActivate() Tab::OnActivate(); } +void TabFilament::add_overrides_page() +{ + PageShp page = add_options_page(_(L("Overrides")), "wrench"); + + const DynamicPrintConfig& printer_cfg = wxGetApp().preset_bundle->printers.default_preset().config; + + ConfigOptionsGroupShp optgroup = page->new_optgroup(_(L("Retraction"/*Overrides"*/))); + + auto append_single_option_line = [printer_cfg, optgroup, this](const std::string& opt_key, int opt_index) + { + const std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index); + const Option& option = Option(*printer_cfg.def()->get(opt_key), opt_id); + + Line line = optgroup->create_single_option_line(option); + + line.near_label_widget = [optgroup, opt_id](wxWindow* parent) { + wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); + + check_box->Bind(wxEVT_CHECKBOX, [optgroup, opt_id](wxCommandEvent& evt) + { + Field* field = optgroup->get_field(opt_id); + if (field != nullptr) + field->toggle(evt.IsChecked()); + }, check_box->GetId()); + return check_box; + }; + + optgroup->append_line(line); + + Field* field = optgroup->get_field(opt_id); + if (field != nullptr) + field->toggle(false); + }; + + int extruder_idx = 0; // #ys_FIXME + + append_single_option_line("retract_length", extruder_idx); + append_single_option_line("retract_lift", extruder_idx); + + Line line = { _(L("Only lift Z")), "" }; + + std::vector opt_ids; + opt_ids.reserve(2); + for (const std::string& opt_key : { "retract_lift_above", "retract_lift_below" }) + { + const std::string opt_id = extruder_idx == -1 ? opt_key : opt_key + "#" + std::to_string(extruder_idx); + opt_ids.push_back(opt_id); + const Option& option = Option(*printer_cfg.def()->get(opt_key), opt_id); + + line.append_option(option); + } + + line.near_label_widget = [optgroup, opt_ids](wxWindow* parent) { + wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); + + check_box->Bind(wxEVT_CHECKBOX, [optgroup, opt_ids](wxCommandEvent& evt) + { + Field* field = nullptr; + for (const std::string& opt_id : opt_ids) { + field = optgroup->get_field(opt_id); + if (field != nullptr) + field->toggle(evt.IsChecked()); + } + }, check_box->GetId()); + return check_box; + }; + + optgroup->append_line(line); + + Field* field = nullptr; + for (const std::string& opt_id : opt_ids) { + field = optgroup->get_field(opt_id); + if (field != nullptr) + field->toggle(false); + } + + append_single_option_line("retract_speed", extruder_idx); + append_single_option_line("deretract_speed", extruder_idx); + append_single_option_line("retract_restart_extra", extruder_idx); + append_single_option_line("retract_before_travel", extruder_idx); + append_single_option_line("retract_layer_change", extruder_idx); + append_single_option_line("wipe", extruder_idx); + append_single_option_line("retract_before_wipe", extruder_idx); +} + void TabFilament::build() { m_presets = &m_preset_bundle->filaments; @@ -1587,10 +1672,14 @@ void TabFilament::build() }; optgroup->append_line(line); + + add_overrides_page(); + + const int gcode_field_height = 15; // 150 const int notes_field_height = 25; // 250 - page = add_options_page(_(L("Custom G-code")), "cog"); + page = add_options_page(_(L("Custom G-code")), "cog"); optgroup = page->new_optgroup(_(L("Start G-code")), 0); Option option = optgroup->get_option("start_filament_gcode"); option.opt.full_width = true; diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 6bbe15f7f4..abe6a03440 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -331,6 +331,8 @@ class TabFilament : public Tab { ogStaticText* m_volumetric_speed_description_line; ogStaticText* m_cooling_description_line; + + void add_overrides_page(); public: TabFilament(wxNotebook* parent) : // Tab(parent, _(L("Filament Settings")), L("filament")) {} From b5215dae1be313c8bd2d62f89594e72853ebea82 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 3 Jul 2019 17:05:08 +0200 Subject: [PATCH 259/627] Fix libnest2d tests --- .../libnest2d/placers/bottomleftplacer.hpp | 10 +++++----- .../libnest2d/placers/placer_boilerplate.hpp | 2 -- src/libnest2d/tests/test.cpp | 18 +++++++++++++++--- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp index 28aaad5ce1..e1a4ffbd92 100644 --- a/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/bottomleftplacer.hpp @@ -68,11 +68,11 @@ public: return toWallPoly(item, Dir::DOWN); } - inline Unit availableSpaceLeft(const Item& item) { + inline Coord availableSpaceLeft(const Item& item) { return availableSpace(item, Dir::LEFT); } - inline Unit availableSpaceDown(const Item& item) { + inline Coord availableSpaceDown(const Item& item) { return availableSpace(item, Dir::DOWN); } @@ -83,7 +83,7 @@ protected: // Get initial position for item in the top right corner setInitialPosition(item); - Unit d = availableSpaceDown(item); + Coord d = availableSpaceDown(item); auto eps = config_.epsilon; bool can_move = d > eps; bool can_be_packed = can_move; @@ -179,7 +179,7 @@ protected: return ret; } - Unit availableSpace(const Item& _item, const Dir dir) { + Coord availableSpace(const Item& _item, const Dir dir) { Item item (_item.transformedShape()); @@ -223,7 +223,7 @@ protected: cmp); // Get the initial distance in floating point - Unit m = getCoord(*minvertex_it); + Coord m = getCoord(*minvertex_it); // Check available distance for every vertex of item to the objects // in the way for the nearest intersection diff --git a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp index abcd861830..26681aeec6 100644 --- a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp @@ -18,7 +18,6 @@ public: using Segment = _Segment; using BinType = TBin; using Coord = TCoord; - using Unit = Coord; using Config = Cfg; using ItemGroup = _ItemGroup; using DefaultIter = typename ItemGroup::const_iterator; @@ -131,7 +130,6 @@ using typename Base::Vertex; \ using typename Base::Segment; \ using typename Base::PackResult; \ using typename Base::Coord; \ -using typename Base::Unit; \ private: } diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index d0c660e498..e5bd871820 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -363,8 +363,14 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) {5, 5}, {20, 20} }; + Box bin(210, 250, {105, 125}); - Nester arrange(Box(210, 250)); + ASSERT_EQ(bin.width(), 210); + ASSERT_EQ(bin.height(), 250); + ASSERT_EQ(getX(bin.center()), 105); + ASSERT_EQ(getY(bin.center()), 125); + + Nester arrange(bin); auto groups = arrange.execute(rects.begin(), rects.end()); @@ -416,10 +422,16 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) {5, 5}, {20, 20} }; + Box bin(210, 250, {105, 125}); + + ASSERT_EQ(bin.width(), 210); + ASSERT_EQ(bin.height(), 250); + ASSERT_EQ(getX(bin.center()), 105); + ASSERT_EQ(getY(bin.center()), 125); + Coord min_obj_distance = 5; - Nester arrange(Box(210, 250), - min_obj_distance); + Nester arrange(bin, min_obj_distance); auto groups = arrange.execute(rects.begin(), rects.end()); From e81f8a5fd929f7fb2eaab80b0534ee3b49ecfa67 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 3 Jul 2019 19:24:41 +0200 Subject: [PATCH 260/627] WIP still with arrange return value. --- .../include/libnest2d/geometry_traits.hpp | 2 +- src/libnest2d/include/libnest2d/libnest2d.hpp | 3 +- src/libslic3r/Arrange.cpp | 88 +++++++++---------- src/libslic3r/Arrange.hpp | 2 +- src/libslic3r/Model.hpp | 4 +- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.hpp | 4 +- src/slic3r/GUI/Plater.cpp | 52 +++++------ 8 files changed, 78 insertions(+), 79 deletions(-) diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 5c213c110c..c4f2fcaca5 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -192,7 +192,7 @@ public: return Unit(width())*height(); } - static inline _Box infinite(const P ¢er); + static inline _Box infinite(const P ¢er = {TCoord

(0), TCoord

(0)}); }; template struct PointType<_Box> { diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index ab018f3f8d..f106ad3d8e 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -908,7 +908,8 @@ private: item.removeOffset(); }); - if(stopfn_ && !stopfn_()) { // Ignore results if nesting was stopped. + if(!stopfn_ || (stopfn_ && !stopfn_())) { + // Ignore results if nesting was stopped. const PackGroup& bins = lastResult(); unsigned binidx = 0; for(auto& bin : bins) { diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 84a406fd91..f778a7f756 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -573,7 +573,7 @@ inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) // a stop predicate can be also be passed to control the process. bool arrange(ArrangeablePtrs & arrangables, const ArrangeablePtrs & excludes, - coord_t min_obj_distance, + coord_t min_obj_dist, const BedShapeHint & bedhint, std::function progressind, std::function stopcondition) @@ -615,13 +615,14 @@ bool arrange(ArrangeablePtrs & arrangables, arrangeable, items, // callback called by arrange to apply the result on the arrangeable - [arrangeable, &binwidth](const Item &itm, unsigned binidx) { + [arrangeable, &binwidth, &ret](const Item &itm, unsigned binidx) { + ret = !binidx; // Return value false more bed is required clppr::cInt stride = binidx * stride_padding(binwidth); clppr::IntPoint offs = itm.translation(); arrangeable->apply_arrange_result({unscaled(offs.X + stride), unscaled(offs.Y)}, - itm.rotation()); + itm.rotation(), binidx); }); } @@ -629,66 +630,61 @@ bool arrange(ArrangeablePtrs & arrangables, process_arrangeable(fixed, fixeditems, nullptr); // Integer ceiling the min distance from the bed perimeters - coord_t md = min_obj_distance - SCALED_EPSILON; + coord_t md = min_obj_dist - SCALED_EPSILON; md = (md % 2) ? md / 2 + 1 : md / 2; - auto& cfn = stopcondition; + auto &cfn = stopcondition; + auto &pri = progressind; switch (bedhint.type) { -// case BedShapeType::BOX: { -// // Create the arranger for the box shaped bed -// BoundingBox bbb = bedhint.shape.box; -// bbb.min -= Point{md, md}, bbb.max += Point{md, md}; -// Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; -// binwidth = coord_t(binbb.width()); + case BedShapeType::BOX: { + // Create the arranger for the box shaped bed + BoundingBox bbb = bedhint.shape.box; + bbb.min -= Point{md, md}, bbb.max += Point{md, md}; + Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; + binwidth = coord_t(binbb.width()); -// _arrange(items, fixeditems, binbb, min_obj_distance, progressind, cfn); -// break; -// } -// case BedShapeType::CIRCLE: { -// auto c = bedhint.shape.circ; -// auto cc = to_lnCircle(c); -// binwidth = scaled(c.radius()); + _arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn); + break; + } + case BedShapeType::CIRCLE: { + auto c = bedhint.shape.circ; + auto cc = to_lnCircle(c); + binwidth = scaled(c.radius()); -// _arrange(items, fixeditems, cc, min_obj_distance, progressind, cfn); -// break; -// } -// case BedShapeType::IRREGULAR: { -// auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon); -// auto irrbed = sl::create(std::move(ctour)); -// BoundingBox polybb(bedhint.shape.polygon); -// binwidth = (polybb.max(X) - polybb.min(X)); + _arrange(items, fixeditems, cc, min_obj_dist, pri, cfn); + break; + } + case BedShapeType::IRREGULAR: { + auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon); + auto irrbed = sl::create(std::move(ctour)); + BoundingBox polybb(bedhint.shape.polygon); + binwidth = (polybb.max(X) - polybb.min(X)); -// _arrange(items, fixeditems, irrbed, min_obj_distance, progressind, cfn); -// break; -// } -// case BedShapeType::INFINITE: { -// const InfiniteBed& nobin = bedhint.shape.infinite; -// Box infbb{{nobin.center.x(), nobin.center.y()}}; + _arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn); + break; + } + case BedShapeType::INFINITE: { + const InfiniteBed& nobin = bedhint.shape.infinite; + auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()}); -// _arrange(items, fixeditems, infbb, min_obj_distance, progressind, cfn); -// break; -// } -// case BedShapeType::UNKNOWN: { -// // We know nothing about the bed, let it be infinite and zero centered -// _arrange(items, fixeditems, Box{}, min_obj_distance, progressind, cfn); -// break; -// } - default: { - Box infbb = Box::infinite({bedhint.shape.box.center().x(), bedhint.shape.box.center().y()}); - - _arrange(items, fixeditems, infbb, min_obj_distance, progressind, cfn); + _arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn); + break; + } + case BedShapeType::UNKNOWN: { + // We know nothing about the bed, let it be infinite and zero centered + _arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn); break; } }; - if(stopcondition()) return false; + if(stopcondition && stopcondition()) return false; return ret; } // Arrange, without the fixed items (excludes) -bool arrange(ArrangeablePtrs & inp, +bool arrange(ArrangeablePtrs & inp, coord_t min_d, const BedShapeHint & bedhint, std::function prfn, diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 87514a6003..337a7d9590 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -58,7 +58,7 @@ public: virtual ~Arrangeable() = default; /// Apply the result transformation calculated by the arrangement. - virtual void apply_arrange_result(Vec2d offset, double rotation_rads) = 0; + virtual void apply_arrange_result(Vec2d offset, double rotation_rads, unsigned bed_num) = 0; /// Get the 2D silhouette to arrange and an initial offset and rotation virtual std::tuple get_arrange_polygon() const = 0; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 51759640ce..d0ed0bc88f 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -559,10 +559,10 @@ public: // ///////////////////////////////////////////////////////////////////////// // Getting the input polygon for arrange - virtual std::tuple get_arrange_polygon() const final; + virtual std::tuple get_arrange_polygon() const override; // Apply the arrange result on the ModelInstance - virtual void apply_arrange_result(Vec2d offs, double rot_rads) final + virtual void apply_arrange_result(Vec2d offs, double rot_rads, unsigned /*bed_num*/) override { // write the transformation data into the model instance set_rotation(Z, rot_rads); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 4e30934891..e5b2b38f88 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5739,7 +5739,7 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } -void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2d offset, double rotation_rads) +void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2d offset, double rotation_rads, unsigned /*bed_num*/) { m_pos = offset; m_rotation = rotation_rads; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 524e0c8837..8f419a16db 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -624,9 +624,9 @@ public: return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); } - virtual void apply_arrange_result(Vec2d offset, double rotation_rads) final; + virtual void apply_arrange_result(Vec2d offset, double rotation_rads, unsigned /*bed_num*/) override; - virtual std::tuple get_arrange_polygon() const final + virtual std::tuple get_arrange_polygon() const override { Polygon p({ {coord_t(0), coord_t(0)}, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 08c70dbe06..a49b541b57 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2482,33 +2482,35 @@ arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { return bedshape; } -void Plater::priv::ExclusiveJobGroup::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_obj_distance = scaled(dist); +void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { auto count = unsigned(m_selected.size()); - arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); + plater().model.arrange_objects(6.f, nullptr); +// static const auto arrangestr = _(L("Arranging")); - try { - arrangement::arrange(m_selected, m_unselected, min_obj_distance, - bedshape, - [this, count](unsigned st) { - if (st > 0) // will not finalize after last one - update_status(count - st, arrangestr); - }, - [this]() { return was_canceled(); }); - } catch (std::exception & /*e*/) { - GUI::show_error(plater().q, - _(L("Could not arrange model objects! " - "Some geometries may be invalid."))); - } +// // 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_obj_distance = scaled(dist); +// auto count = unsigned(m_selected.size()); +// arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); + +// try { +// arrangement::arrange(m_selected, m_unselected, min_obj_distance, +// bedshape, +// [this, count](unsigned st) { +// if (st > 0) // will not finalize after last one +// update_status(count - st, arrangestr); +// }, +// [this]() { return was_canceled(); }); +// } 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), From 5a2ace1a6e466c3501817cfa73d3b74606222395 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 10:45:41 +0200 Subject: [PATCH 261/627] WIP Undo / Redo: First Undo in the history of PrusaSlicer! --- src/libslic3r/Model.cpp | 13 +++++++++++ src/libslic3r/Model.hpp | 3 +++ src/slic3r/GUI/GLCanvas3D.cpp | 21 ++++++++++++++++++ src/slic3r/GUI/GLCanvas3D.hpp | 2 ++ src/slic3r/GUI/Plater.cpp | 38 ++++++++++++++++++++++++-------- src/slic3r/GUI/Plater.hpp | 2 ++ src/slic3r/Utils/UndoRedo.cpp | 41 ++++++++++++++++++++++++++++++----- src/slic3r/Utils/UndoRedo.hpp | 3 +++ 8 files changed, 108 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 16f8fec4e6..fbf10bf830 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -72,6 +72,19 @@ void Model::assign_new_unique_ids_recursive() model_object->assign_new_unique_ids_recursive(); } +void Model::update_links_bottom_up_recursive() +{ + for (std::pair &kvp : this->materials) + kvp.second->set_model(this); + for (ModelObject *model_object : this->objects) { + model_object->set_model(this); + for (ModelInstance *model_instance : model_object->instances) + model_instance->set_model_object(model_object); + for (ModelVolume *model_volume : model_object->volumes) + model_volume->set_model_object(model_object); + } +} + Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances) { Model model; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 5fd958f862..398756fc9c 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -457,6 +457,7 @@ public: protected: friend class Print; friend class SLAPrint; + friend class Model; friend class ModelObject; // Copies IDs of both the ModelVolume and its config. @@ -598,6 +599,7 @@ public: protected: friend class Print; friend class SLAPrint; + friend class Model; friend class ModelObject; explicit ModelInstance(const ModelInstance &rhs) = default; @@ -713,6 +715,7 @@ public: private: explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }; void assign_new_unique_ids_recursive(); + void update_links_bottom_up_recursive(); friend class cereal::access; friend class UndoRedo::StackImpl; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 4116ac34bb..511c423e63 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1191,6 +1191,8 @@ wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) : m_canvas(canvas) @@ -2350,6 +2352,25 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #endif /* __APPLE__ */ post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE)); break; + + +#ifdef __APPLE__ + case 'y': + case 'Y': +#else /* __APPLE__ */ + case WXK_CONTROL_Y: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLCANVAS_REDO)); + break; +#ifdef __APPLE__ + case 'z': + case 'Z': +#else /* __APPLE__ */ + case WXK_CONTROL_Z: +#endif /* __APPLE__ */ + post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); + break; + #ifdef __APPLE__ case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead. #else /* __APPLE__ */ diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index d39a910b3b..d71817b340 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -126,6 +126,8 @@ wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); class GLCanvas3D { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f084805590..3e6f3763aa 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1552,6 +1552,8 @@ struct Plater::priv void take_snapshot(const std::string& snapshot_name) { this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection()); } void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } + void undo(); + void redo(); bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; } void update_print_volume_state(); @@ -1745,6 +1747,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this); view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); }); + view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); }); + view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); }); // 3DScene/Toolbar: view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); @@ -1785,6 +1789,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) camera.set_type(get_config("use_perspective_camera")); this->undo_redo_stack.initialize(model, view3D->get_canvas3d()->get_selection()); + this->take_snapshot(_(L("New Project"))); } void Plater::priv::update(bool force_full_scene_refresh) @@ -3490,6 +3495,26 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const } } +void Plater::priv::undo() +{ + if (this->undo_redo_stack.undo(model, const_cast(view3D->get_canvas3d()->get_selection()))) { + this->update(false); + //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) +// wxGetApp().obj_list()->update_selections(); +// selection_changed(); + } +} + +void Plater::priv::redo() +{ + if (this->undo_redo_stack.redo(model, const_cast(view3D->get_canvas3d()->get_selection()))) { + this->update(false); + //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) +// wxGetApp().obj_list()->update_selections(); +// selection_changed(); + } +} + void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const { switch (btn_type) @@ -4001,15 +4026,10 @@ void Plater::send_gcode() } } -void Plater::take_snapshot(const std::string &snapshot_name) -{ - p->take_snapshot(snapshot_name); -} - -void Plater::take_snapshot(const wxString &snapshot_name) -{ - p->take_snapshot(snapshot_name); -} +void Plater::take_snapshot(const std::string &snapshot_name) { p->take_snapshot(snapshot_name); } +void Plater::take_snapshot(const wxString &snapshot_name) { p->take_snapshot(snapshot_name); } +void Plater::undo() { p->undo(); } +void Plater::redo() { p->redo(); } void Plater::on_extruders_change(int num_extruders) { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 9a6bcda7b4..91f218f6c3 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -181,6 +181,8 @@ public: void take_snapshot(const std::string &snapshot_name); void take_snapshot(const wxString &snapshot_name); + void undo(); + void redo(); void on_extruders_change(int extruders_count); void on_config_change(const DynamicPrintConfig &config); diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 74565a3010..978bf149f5 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -130,7 +130,7 @@ public: if (m_history.empty()) return false; auto it = std::lower_bound(m_history.begin(), m_history.end(), Interval(timestamp, timestamp)); - if (it == m_history.end() || it->begin() >= timestamp) { + if (it == m_history.end() || it->begin() > timestamp) { if (it == m_history.begin()) return false; -- it; @@ -265,7 +265,7 @@ public: std::string load(size_t timestamp) const { assert(! m_history.empty()); auto it = std::lower_bound(m_history.begin(), m_history.end(), MutableHistoryInterval(timestamp, timestamp)); - if (it == m_history.end() || it->begin() >= timestamp) { + if (it == m_history.end() || it->begin() > timestamp) { assert(it != m_history.begin()); -- it; } @@ -328,6 +328,9 @@ public: void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); void load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection); + bool undo(Slic3r::Model &model, Slic3r::GUI::Selection &selection); + bool redo(Slic3r::Model &model, Slic3r::GUI::Selection &selection); + // Snapshot history (names with timestamps). const std::vector& snapshots() const { return m_snapshots; } @@ -566,7 +569,7 @@ void StackImpl::initialize(const Slic3r::Model &model, const Slic3r::GUI::Select // The initial time interval will be <0, 1) m_active_snapshot_time = SIZE_MAX; // let it overflow to zero in take_snapshot m_current_time = 0; - this->take_snapshot("New Project", model, selection); + this->take_snapshot("Internal - Initialized", model, selection); } // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. @@ -584,7 +587,8 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo this->save_mutable_object(model); // this->save_mutable_object(selection); // Save the snapshot info - m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id); + m_active_snapshot_time = m_current_time ++; + m_snapshots.emplace_back(snapshot_name, m_active_snapshot_time, model.id().id); // Release empty objects from the history. this->collect_garbage(); } @@ -593,16 +597,38 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GU { // Find the snapshot by time. It must exist. const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp)); - if (it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) + if (it_snapshot == m_snapshots.begin() || it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); + m_active_snapshot_time = timestamp; model.clear_objects(); model.clear_materials(); this->load_mutable_object(ObjectID(it_snapshot->model_object_id), model); - this->load_mutable_object(selection.id(), selection); + model.update_links_bottom_up_recursive(); +// this->load_mutable_object(selection.id(), selection); this->m_active_snapshot_time = timestamp; } +bool StackImpl::undo(Slic3r::Model &model, Slic3r::GUI::Selection &selection) +{ + auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + assert(it_current != m_snapshots.end() && it_current != m_snapshots.begin() && it_current->timestamp == m_active_snapshot_time); + if (-- it_current == m_snapshots.begin()) + return false; + this->load_snapshot(it_current->timestamp, model, selection); + return true; +} + +bool StackImpl::redo(Slic3r::Model &model, Slic3r::GUI::Selection &selection) +{ + auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + assert(it_current != m_snapshots.end() && it_current != m_snapshots.begin() && it_current->timestamp == m_active_snapshot_time); + if (++ it_current == m_snapshots.end()) + return false; + this->load_snapshot(it_current->timestamp, model, selection); + return true; +} + void StackImpl::collect_garbage() { // Purge objects with empty histories. @@ -623,6 +649,9 @@ Stack::~Stack() {} void Stack::initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->initialize(model, selection); } void Stack::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->take_snapshot(snapshot_name, model, selection); } void Stack::load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection) { pimpl->load_snapshot(timestamp, model, selection); } +bool Stack::undo(Slic3r::Model &model, Slic3r::GUI::Selection &selection) { return pimpl->undo(model, selection); } +bool Stack::redo(Slic3r::Model &model, Slic3r::GUI::Selection &selection) { return pimpl->redo(model, selection); } + const std::vector& Stack::snapshots() const { return pimpl->snapshots(); } } // namespace UndoRedo diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index d452e777c1..be024c282e 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -44,6 +44,9 @@ public: void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); void load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection); + bool undo(Slic3r::Model &model, Slic3r::GUI::Selection &selection); + bool redo(Slic3r::Model &model, Slic3r::GUI::Selection &selection); + // Snapshot history (names with timestamps). const std::vector& snapshots() const; From 9372f1c6ad15d6d4f938f167625ff9e8e5905cb1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 4 Jul 2019 13:58:18 +0200 Subject: [PATCH 262/627] Wip in Nester interface --- src/libnest2d/include/libnest2d/libnest2d.hpp | 216 +++++++++++------- src/libslic3r/Arrange.cpp | 49 ++-- 2 files changed, 141 insertions(+), 124 deletions(-) diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index f106ad3d8e..99c8c90c17 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -36,20 +36,20 @@ class _Item { // Transformation data Vertex translation_; Radians rotation_; - Coord offset_distance_; + Coord inflation_; // Info about whether the transformations will have to take place // This is needed because if floating point is used, it is hard to say // that a zero angle is not a rotation because of testing for equality. - bool has_rotation_ = false, has_translation_ = false, has_offset_ = false; + bool has_rotation_ = false, has_translation_ = false, has_inflation_ = false; // For caching the calculations as they can get pretty expensive. mutable RawShape tr_cache_; mutable bool tr_cache_valid_ = false; mutable double area_cache_ = 0; mutable bool area_cache_valid_ = false; - mutable RawShape offset_cache_; - mutable bool offset_cache_valid_ = false; + mutable RawShape inflate_cache_; + mutable bool inflate_cache_valid_ = false; enum class Convexity: char { UNCHECKED, @@ -66,7 +66,9 @@ class _Item { BBCache(): valid(false) {} } bb_cache_; - std::function applyfn_; + static const size_t ID_UNSET = size_t(-1); + + size_t id_{ID_UNSET}; bool fixed_{false}; public: @@ -126,12 +128,12 @@ public: THolesContainer&& holes): sh_(sl::create(std::move(contour), std::move(holes))) {} - template - _Item(std::function applyfn, Args &&... args): - _Item(std::forward(args)...) - { - applyfn_ = std::move(applyfn); - } +// template +// _Item(std::function applyfn, Args &&... args): +// _Item(std::forward(args)...) +// { +// applyfn_ = std::move(applyfn); +// } // Call the apply callback set in constructor. Within the callback, the // original caller can apply the stored transformation to the original @@ -140,13 +142,15 @@ public: // client uses a simplified or processed polygon for nesting) // This callback, if present, will be called for each item after the nesting // is finished. - inline void callApplyFunction(unsigned binidx) const - { - if (applyfn_) applyfn_(*this, binidx); - } +// inline void callApplyFunction(unsigned binidx) const +// { +// if (applyfn_) applyfn_(*this, binidx); +// } inline bool isFixed() const noexcept { return fixed_; } inline void markAsFixed(bool fixed = true) { fixed_ = fixed; } + inline void id(size_t idx) { id_ = idx; } + inline long id() const noexcept { return id_; } /** * @brief Convert the polygon to string representation. The format depends @@ -224,7 +228,7 @@ public: double ret ; if(area_cache_valid_) ret = area_cache_; else { - ret = sl::area(offsettedShape()); + ret = sl::area(infaltedShape()); area_cache_ = ret; area_cache_valid_ = true; } @@ -295,17 +299,21 @@ public: { rotation(rotation() + rads); } - - inline void addOffset(Coord distance) BP2D_NOEXCEPT + + inline void inflation(Coord distance) BP2D_NOEXCEPT { - offset_distance_ = distance; - has_offset_ = true; + inflation_ = distance; + has_inflation_ = true; invalidateCache(); } - - inline void removeOffset() BP2D_NOEXCEPT { - has_offset_ = false; - invalidateCache(); + + inline Coord inflation() const BP2D_NOEXCEPT { + return inflation_; + } + + inline void inflate(Coord distance) BP2D_NOEXCEPT + { + inflation(inflation() + distance); } inline Radians rotation() const BP2D_NOEXCEPT @@ -339,7 +347,7 @@ public: { if(tr_cache_valid_) return tr_cache_; - RawShape cpy = offsettedShape(); + RawShape cpy = infaltedShape(); if(has_rotation_) sl::rotate(cpy, rotation_); if(has_translation_) sl::translate(cpy, translation_); tr_cache_ = cpy; tr_cache_valid_ = true; @@ -360,17 +368,17 @@ public: inline void resetTransformation() BP2D_NOEXCEPT { - has_translation_ = false; has_rotation_ = false; has_offset_ = false; + has_translation_ = false; has_rotation_ = false; has_inflation_ = false; invalidateCache(); } inline Box boundingBox() const { if(!bb_cache_.valid) { if(!has_rotation_) - bb_cache_.bb = sl::boundingBox(offsettedShape()); + bb_cache_.bb = sl::boundingBox(infaltedShape()); else { // TODO make sure this works - auto rotsh = offsettedShape(); + auto rotsh = infaltedShape(); sl::rotate(rotsh, rotation_); bb_cache_.bb = sl::boundingBox(rotsh); } @@ -419,14 +427,14 @@ public: private: - inline const RawShape& offsettedShape() const { - if(has_offset_ ) { - if(offset_cache_valid_) return offset_cache_; + inline const RawShape& infaltedShape() const { + if(has_inflation_ ) { + if(inflate_cache_valid_) return inflate_cache_; - offset_cache_ = sh_; - sl::offset(offset_cache_, offset_distance_); - offset_cache_valid_ = true; - return offset_cache_; + inflate_cache_ = sh_; + sl::offset(inflate_cache_, inflation_); + inflate_cache_valid_ = true; + return inflate_cache_; } return sh_; } @@ -436,7 +444,7 @@ private: tr_cache_valid_ = false; lmb_valid_ = false; rmt_valid_ = false; area_cache_valid_ = false; - offset_cache_valid_ = false; + inflate_cache_valid_ = false; bb_cache_.valid = false; convexity_ = Convexity::UNCHECKED; } @@ -758,6 +766,25 @@ public: void clear() { impl_.clear(); } }; +using BinIdx = unsigned; +template using _NestResult = + std::vector< + std::tuple, // Translation calculated by nesting + Radians, // Rotation calculated by nesting + BinIdx> // Logical bin index, first is zero + >; + +template struct Indexed { + using ShapeType = T; + static T& get(T& obj) { return obj; } +}; + +template struct Indexed> { + using ShapeType = S; + static S& get(std::pair& obj) { return obj.second; } +}; + /** * The Arranger is the front-end class for the libnest2d library. It takes the * input items and outputs the items with the proper transformations to be @@ -769,6 +796,7 @@ class Nester { TSel selector_; public: using Item = typename PlacementStrategy::Item; + using ShapeType = typename Item::ShapeType; using ItemRef = std::reference_wrapper; using TPlacer = PlacementStrategyLike; using BinType = typename TPlacer::BinType; @@ -777,6 +805,7 @@ public: using Coord = TCoord>; using PackGroup = _PackGroup; using ResultType = PackGroup; + template using NestResult = _NestResult; private: BinType bin_; @@ -835,10 +864,12 @@ public: * The number of groups in the pack group is the number of bins opened by * the selection algorithm. */ - template - inline PackGroup execute(TIterator from, TIterator to) + template + inline const NestResult execute(It from, It to, + std::function keyfn = nullptr) { - return _execute(from, to); + if (!keyfn) keyfn = [to](It it) { return to - it; }; + return _execute(from, to, keyfn); } /// Set a progress indicator function object for the selector. @@ -858,65 +889,74 @@ public: return selector_.getResult(); } - inline void preload(const PackGroup& pgrp) - { - selector_.preload(pgrp); - } - private: - - template, - - // This function will be used only if the iterators are pointing to - // a type compatible with the libnets2d::_Item template. - // This way we can use references to input elements as they will - // have to exist for the lifetime of this call. - class T = enable_if_t< std::is_convertible::value, IT> - > - inline const PackGroup& _execute(TIterator from, TIterator to, bool = false) + + template using TVal = remove_cvref_t; + + template + using ConvertibleOnly = + enable_if_t< std::is_convertible, TPItem>::value, void>; + + template + using NotConvertibleOnly = + enable_if_t< ! std::is_convertible, TPItem>::value, void>; + + // This function will be used only if the iterators are pointing to + // a type compatible with the libnets2d::_Item template. + // This way we can use references to input elements as they will + // have to exist for the lifetime of this call. + template + inline ConvertibleOnly> _execute( + It from, It to, std::function keyfn) { - __execute(from, to); - return lastResult(); - } - - template, - class T = enable_if_t::value, IT> - > - inline const PackGroup& _execute(TIterator from, TIterator to, int = false) - { - item_cache_ = {from, to}; - - __execute(item_cache_.begin(), item_cache_.end()); - return lastResult(); - } - - template inline void __execute(TIter from, TIter to) - { - if(min_obj_distance_ > 0) std::for_each(from, to, [this](Item& item) { - auto offs = min_obj_distance_; - if (item.isFixed()) offs *= 0.99; + { + auto it = from; size_t id = 0; + while(it != to) + if (it->id() == Item::ID_UNSET) (it++)->id(id++); + else { id = it->id() + 1; ++it; } + } + + NestResult result(to - from); + + __execute(from, to, keyfn); + + BinIdx binidx = 0; + for(auto &itmgrp : lastResult()) { + for(const Item& itm : itmgrp) + result[itm.id()] = + std::make_tuple(keyfn(from + itm.id()), itm.translation(), + itm.rotation(), binidx); - item.addOffset(static_cast(std::ceil(offs/2.0))); + ++binidx; + } + + return result; + } + + template + inline NotConvertibleOnly> _execute( + It from, It to, std::function keyfn) + { + item_cache_.reserve(to - from); + for(auto it = from; it != to; ++it) + item_cache_.emplace_back(Indexed::get(*it)); + + return _execute(item_cache_.begin(), item_cache_.end(), keyfn); + } + + template inline void __execute(It from, It to) + { + auto infl = static_cast(std::ceil(min_obj_distance_/2.0)); + if(infl > 0) std::for_each(from, to, [this](Item& item) { + item.inflate(infl); }); selector_.template packItems( from, to, bin_, pconfig_); if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { - item.removeOffset(); + item.inflate(-infl); }); - - if(!stopfn_ || (stopfn_ && !stopfn_())) { - // Ignore results if nesting was stopped. - const PackGroup& bins = lastResult(); - unsigned binidx = 0; - for(auto& bin : bins) { - for(const Item& itm : bin) itm.callApplyFunction(binidx); - ++binidx; - } - } } }; diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index f778a7f756..6bbcc9577f 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -513,12 +513,13 @@ BedShapeHint bedShape(const Polyline &bed) { } template // Arrange for arbitrary bin type -PackGroup _arrange(std::vector & shapes, - std::vector & excludes, - const BinT & bin, - coord_t minobjd, - std::function prind, - std::function stopfn) +_NestResult _arrange( + std::vector & shapes, + std::vector & excludes, + const BinT & bin, + coord_t minobjd, + std::function prind, + std::function stopfn) { AutoArranger arranger{bin, minobjd, prind, stopfn}; @@ -535,22 +536,13 @@ PackGroup _arrange(std::vector & shapes, // Try to put the first item to the center, as the arranger // will not do this for us. - for (auto it = shapes.begin(); it != shapes.end(); ++it) { - Item &itm = *it; + for (Item &itm : shapes) { auto ibb = itm.boundingBox(); auto d = binbb.center() - ibb.center(); itm.translate(d); if (!arranger.is_colliding(itm)) { itm.markAsFixed(); - - // Write the transformation data into the item. The - // callback was set on the instantiation of Item and - // calls the Arrangeable interface. - it->callApplyFunction(0); - - // Remove this item, as it is arranged now - it = shapes.erase(it); break; } } @@ -586,9 +578,7 @@ bool arrange(ArrangeablePtrs & arrangables, coord_t binwidth = 0; auto process_arrangeable = - [](const Arrangeable * arrangeable, - std::vector & outp, - std::function applyfn) + [](const Arrangeable *arrangeable, std::vector &outp) { assert(arrangeable); @@ -605,29 +595,16 @@ bool arrange(ArrangeablePtrs & arrangables, auto firstp = clpath.Contour.front(); clpath.Contour.emplace_back(firstp); - outp.emplace_back(applyfn, std::move(clpath)); + outp.emplace_back(std::move(clpath)); outp.back().rotation(rotation); outp.back().translation({offs.x(), offs.y()}); }; - for (Arrangeable *arrangeable : arrangables) { - process_arrangeable( - arrangeable, - items, - // callback called by arrange to apply the result on the arrangeable - [arrangeable, &binwidth, &ret](const Item &itm, unsigned binidx) { - ret = !binidx; // Return value false more bed is required - clppr::cInt stride = binidx * stride_padding(binwidth); - - clppr::IntPoint offs = itm.translation(); - arrangeable->apply_arrange_result({unscaled(offs.X + stride), - unscaled(offs.Y)}, - itm.rotation(), binidx); - }); - } + for (Arrangeable *arrangeable : arrangables) + process_arrangeable(arrangeable, items); for (const Arrangeable * fixed: excludes) - process_arrangeable(fixed, fixeditems, nullptr); + process_arrangeable(fixed, fixeditems); // Integer ceiling the min distance from the bed perimeters coord_t md = min_obj_dist - SCALED_EPSILON; From 74f9a5432f3f72423137b5cd9a56fc2c33e57845 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 4 Jul 2019 14:25:40 +0200 Subject: [PATCH 263/627] Reset previous layers range selection before selection Layers Item --- src/slic3r/GUI/GUI_ObjectList.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 5c421705bb..4c4a302272 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1888,6 +1888,10 @@ void ObjectList::layers_editing() if (!layers_item.IsOk()) return; + // to correct visual hints for layers editing on the Scene, reset previous selection + wxGetApp().obj_layers()->reset_selection(); + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); + // select LayerRoor item and expand select_item(layers_item); Expand(layers_item); From 1798e2a84c57a9ecde31ffbeebdf6da0758bcd94 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 14:35:04 +0200 Subject: [PATCH 264/627] WIP Undo / Redo : serialization / deserialization of object selection. --- src/libslic3r/ObjectID.hpp | 4 +- src/slic3r/GUI/Plater.cpp | 25 +++++++----- src/slic3r/GUI/Selection.cpp | 18 +++++++++ src/slic3r/GUI/Selection.hpp | 6 ++- src/slic3r/Utils/UndoRedo.cpp | 71 +++++++++++++++-------------------- src/slic3r/Utils/UndoRedo.hpp | 19 ++++++++-- 6 files changed, 85 insertions(+), 58 deletions(-) diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 0988acf5a9..c708e5687e 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -24,6 +24,8 @@ class ObjectID { public: ObjectID(size_t id) : id(id) {} + // Default constructor constructs an invalid ObjectID. + ObjectID() : id(0) {} bool operator==(const ObjectID &rhs) const { return this->id == rhs.id; } bool operator!=(const ObjectID &rhs) const { return this->id != rhs.id; } @@ -38,8 +40,6 @@ public: size_t id; private: - ObjectID() {} - friend class cereal::access; template void serialize(Archive &ar) { ar(id); } }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3e6f3763aa..a492584e65 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1645,6 +1645,7 @@ private: void update_fff_scene(); void update_sla_scene(); + void update_after_undo_redo(); // path to project file stored with no extension wxString m_project_filename; @@ -3497,22 +3498,26 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const void Plater::priv::undo() { - if (this->undo_redo_stack.undo(model, const_cast(view3D->get_canvas3d()->get_selection()))) { - this->update(false); - //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) -// wxGetApp().obj_list()->update_selections(); -// selection_changed(); - } + if (this->undo_redo_stack.undo(model)) + this->update_after_undo_redo(); } void Plater::priv::redo() { - if (this->undo_redo_stack.redo(model, const_cast(view3D->get_canvas3d()->get_selection()))) { - this->update(false); - //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) + if (this->undo_redo_stack.redo(model)) + this->update_after_undo_redo(); +} + +void Plater::priv::update_after_undo_redo() +{ + this->view3D->get_canvas3d()->get_selection().clear(); + this->update(false); // update volumes from the deserializd model + //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time) + this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances); // wxGetApp().obj_list()->update_selections(); // selection_changed(); - } + //FIXME what about the state of the manipulators? + //FIXME what about the focus? Cursor in the side panel? } void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 0784b70ffe..2986d97dd1 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -311,6 +311,24 @@ void Selection::add_all() this->set_bounding_boxes_dirty(); } +void Selection::set_deserialized(EMode mode, const std::vector> &volumes_and_instances) +{ + if (! m_valid) + return; + + m_mode = mode; + for (unsigned int i : m_list) + (*m_volumes)[i]->selected = false; + m_list.clear(); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) { + const GLVolume::CompositeID &id = (*m_volumes)[i]->composite_id; + if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), std::make_pair(id.volume_id, id.instance_id))) + this->do_add_volume(i); + } + update_type(); + this->set_bounding_boxes_dirty(); +} + void Selection::clear() { if (!m_valid) diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 17ae72356f..8168e5e88c 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -3,7 +3,6 @@ #include #include "libslic3r/Geometry.hpp" -#include "libslic3r/ObjectID.hpp" #include "3DScene.hpp" #if ENABLE_RENDER_SELECTION_CENTER @@ -67,7 +66,7 @@ private: Enum m_value; }; -class Selection : public Slic3r::ObjectBase +class Selection { public: typedef std::set IndicesList; @@ -238,6 +237,9 @@ public: void add_all(); + // To be called after Undo or Redo once the volumes are updated. + void set_deserialized(EMode mode, const std::vector> &volumes_and_instances); + // Update the selection based on the new instance IDs. void instances_changed(const std::vector &instance_ids_selected); // Update the selection based on the map from old indices to new indices after m_volumes changed. diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 978bf149f5..34d83c4450 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #define CEREAL_FUTURE_EXPERIMENTAL @@ -326,20 +327,17 @@ public: // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); - void load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection); + void load_snapshot(size_t timestamp, Slic3r::Model &model); - bool undo(Slic3r::Model &model, Slic3r::GUI::Selection &selection); - bool redo(Slic3r::Model &model, Slic3r::GUI::Selection &selection); + bool undo(Slic3r::Model &model); + bool redo(Slic3r::Model &model); // Snapshot history (names with timestamps). const std::vector& snapshots() const { return m_snapshots; } -//protected: - void save_model(const Slic3r::Model &model, size_t snapshot_time); - void save_selection(const Slic3r::Model& model, const Slic3r::GUI::Selection &selection, size_t snapshot_time); - void load_model(const Slic3r::Model &model, size_t snapshot_time); - void load_selection(const Slic3r::GUI::Selection &selection, size_t snapshot_time); + const Selection& selection_deserialized() const { return m_selection; } +//protected: template ObjectID save_mutable_object(const T &object); template ObjectID save_immutable_object(std::shared_ptr &object); template T* load_mutable_object(const Slic3r::ObjectID id); @@ -372,6 +370,8 @@ private: size_t m_active_snapshot_time; // Logical time counter. m_current_time is being incremented with each snapshot taken. size_t m_current_time; + // Last selection serialized or deserialized. + Selection m_selection; }; using InputArchive = cereal::UserDataAdapter; @@ -469,28 +469,6 @@ namespace cereal ar(id); ptr = stack.load_immutable_object(Slic3r::ObjectID(id)); } - -#if 0 - void save(BinaryOutputArchive &ar, const Slic3r::GUI::Selection &selection) - { - size_t num = selection.get_volume_idxs().size(); - ar(num); - for (unsigned int volume_idx : selection.get_volume_idxs()) { - const Slic3r::GLVolume::CompositeID &id = selection.get_volume(volume_idx)->composite_id; - ar(id.object_id, id.volume_id, id.instance_id); - } - } - - template void load(BinaryInputArchive &ar, Slic3r::GUI::Selection &selection) - { - size_t num; - ar(num); - for (size_t i = 0; i < num; ++ i) { - Slic3r::GLVolume::CompositeID id; - ar(id.object_id, id.volume_id, id.instance_id); - } - } -#endif } #include @@ -585,15 +563,22 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo } // Take new snapshots. this->save_mutable_object(model); -// this->save_mutable_object(selection); - // Save the snapshot info + m_selection.volumes_and_instances.clear(); + m_selection.volumes_and_instances.reserve(selection.get_volume_idxs().size()); + m_selection.mode = selection.get_mode(); + for (unsigned int volume_idx : selection.get_volume_idxs()) { + const Slic3r::GLVolume::CompositeID &id = selection.get_volume(volume_idx)->composite_id; + m_selection.volumes_and_instances.emplace_back(id.volume_id, id.instance_id); + } + this->save_mutable_object(m_selection); + // Save the snapshot info. m_active_snapshot_time = m_current_time ++; m_snapshots.emplace_back(snapshot_name, m_active_snapshot_time, model.id().id); // Release empty objects from the history. this->collect_garbage(); } -void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection) +void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model) { // Find the snapshot by time. It must exist. const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp)); @@ -605,27 +590,30 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GU model.clear_materials(); this->load_mutable_object(ObjectID(it_snapshot->model_object_id), model); model.update_links_bottom_up_recursive(); -// this->load_mutable_object(selection.id(), selection); + m_selection.volumes_and_instances.clear(); + this->load_mutable_object(m_selection.id(), m_selection); + // Sort the volumes so that we may use binary search. + std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end()); this->m_active_snapshot_time = timestamp; } -bool StackImpl::undo(Slic3r::Model &model, Slic3r::GUI::Selection &selection) +bool StackImpl::undo(Slic3r::Model &model) { auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); assert(it_current != m_snapshots.end() && it_current != m_snapshots.begin() && it_current->timestamp == m_active_snapshot_time); if (-- it_current == m_snapshots.begin()) return false; - this->load_snapshot(it_current->timestamp, model, selection); + this->load_snapshot(it_current->timestamp, model); return true; } -bool StackImpl::redo(Slic3r::Model &model, Slic3r::GUI::Selection &selection) +bool StackImpl::redo(Slic3r::Model &model) { auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); assert(it_current != m_snapshots.end() && it_current != m_snapshots.begin() && it_current->timestamp == m_active_snapshot_time); if (++ it_current == m_snapshots.end()) return false; - this->load_snapshot(it_current->timestamp, model, selection); + this->load_snapshot(it_current->timestamp, model); return true; } @@ -648,9 +636,10 @@ Stack::Stack() : pimpl(new StackImpl()) {} Stack::~Stack() {} void Stack::initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->initialize(model, selection); } void Stack::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->take_snapshot(snapshot_name, model, selection); } -void Stack::load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection) { pimpl->load_snapshot(timestamp, model, selection); } -bool Stack::undo(Slic3r::Model &model, Slic3r::GUI::Selection &selection) { return pimpl->undo(model, selection); } -bool Stack::redo(Slic3r::Model &model, Slic3r::GUI::Selection &selection) { return pimpl->redo(model, selection); } +void Stack::load_snapshot(size_t timestamp, Slic3r::Model &model) { pimpl->load_snapshot(timestamp, model); } +bool Stack::undo(Slic3r::Model &model) { return pimpl->undo(model); } +bool Stack::redo(Slic3r::Model &model) { return pimpl->redo(model); } +const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); } const std::vector& Stack::snapshots() const { return pimpl->snapshots(); } diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index be024c282e..0c97a0307f 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace Slic3r { class Model; @@ -27,6 +29,13 @@ struct Snapshot bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; } }; +// Excerpt of Slic3r::GUI::Selection for serialization onto the Undo / Redo stack. +struct Selection : public Slic3r::ObjectBase { + unsigned char mode; + std::vector> volumes_and_instances; + template void serialize(Archive &ar) { ar(mode, volumes_and_instances); } +}; + class StackImpl; class Stack @@ -42,14 +51,18 @@ public: // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); - void load_snapshot(size_t timestamp, Slic3r::Model &model, Slic3r::GUI::Selection &selection); + void load_snapshot(size_t timestamp, Slic3r::Model &model); - bool undo(Slic3r::Model &model, Slic3r::GUI::Selection &selection); - bool redo(Slic3r::Model &model, Slic3r::GUI::Selection &selection); + bool undo(Slic3r::Model &model); + bool redo(Slic3r::Model &model); // Snapshot history (names with timestamps). const std::vector& snapshots() const; + // After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted + // into the list of GLVolume pointers once the 3D scene is updated. + const Selection& selection_deserialized() const; + private: friend class StackImpl; std::unique_ptr pimpl; From e586475bc32cb8ef8b2569ea60a5aa0d35a9bc09 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 17:14:15 +0200 Subject: [PATCH 265/627] WIP Undo / Redo: Optional debug print outs. --- src/slic3r/GUI/Plater.cpp | 2 +- src/slic3r/Utils/UndoRedo.cpp | 151 ++++++++++++++++++++++++++++++---- src/slic3r/Utils/UndoRedo.hpp | 9 +- 3 files changed, 140 insertions(+), 22 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a492584e65..4a743aab1f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3498,7 +3498,7 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const void Plater::priv::undo() { - if (this->undo_redo_stack.undo(model)) + if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection())) this->update_after_undo_redo(); } diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 34d83c4450..4a9af3b43e 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -1,7 +1,10 @@ #include "UndoRedo.hpp" #include +#include +#include #include +#include #include #include @@ -18,6 +21,10 @@ #include +#ifndef NDEBUG +// #define SLIC3R_UNDOREDO_DEBUG +#endif /* NDEBUG */ + namespace Slic3r { namespace UndoRedo { @@ -63,8 +70,13 @@ public: // Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. virtual void relese_after_timestamp(size_t timestamp) = 0; +#ifdef SLIC3R_UNDOREDO_DEBUG + // Human readable debug information. + virtual std::string format() = 0; +#endif /* SLIC3R_UNDOREDO_DEBUG */ + #ifndef NDEBUG - virtual bool validate() = 0; + virtual bool valid() = 0; #endif /* NDEBUG */ }; @@ -79,7 +91,7 @@ public: // Release all data after the given timestamp. The shared pointer is NOT released. void relese_after_timestamp(size_t timestamp) override { assert(! m_history.empty()); - assert(this->validate()); + assert(this->valid()); // it points to an interval which either starts with timestamp, or follows the timestamp. auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp)); if (it == m_history.end()) { @@ -90,7 +102,7 @@ public: it_prev->trim_end(timestamp); } m_history.erase(it, m_history.end()); - assert(this->validate()); + assert(this->valid()); } protected: @@ -155,8 +167,20 @@ public: return m_shared_object; } +#ifdef SLIC3R_UNDOREDO_DEBUG + std::string format() override { + std::string out = typeid(T).name(); + out += this->is_serialized() ? + std::string(" len:") + std::to_string(m_serialized.size()) : + std::string(" ptr:") + ptr_to_string(m_shared_object.get()); + for (const Interval &interval : m_history) + out += std::string(",<") + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; + return out; + } +#endif /* SLIC3R_UNDOREDO_DEBUG */ + #ifndef NDEBUG - bool validate() override; + bool valid() override; #endif /* NDEBUG */ private: @@ -225,6 +249,13 @@ private: MutableHistoryInterval& operator=(const MutableHistoryInterval &rhs); }; +static inline std::string ptr_to_string(const void* ptr) +{ + char buf[64]; + sprintf(buf, "%p", ptr); + return buf; +} + // Smaller objects (Model, ModelObject, ModelInstance, ModelVolume, DynamicPrintConfig) // are mutable and there is not tracking of the changes, therefore a snapshot needs to be // taken every time and compared to the previous data at the Undo / Redo stack. @@ -274,14 +305,28 @@ public: return std::string(it->data(), it->data() + it->size()); } +#ifdef SLIC3R_UNDOREDO_DEBUG + std::string format() override { + std::string out = typeid(T).name(); + bool first = true; + for (const MutableHistoryInterval &interval : m_history) { + if (! first) + out += ","; + out += std::string("ptr:") + ptr_to_string(interval.data()) + " len:" + std::to_string(interval.size()) + " <" + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; + first = false; + } + return out; + } +#endif /* SLIC3R_UNDOREDO_DEBUG */ + #ifndef NDEBUG - bool validate() override; + bool valid() override; #endif /* NDEBUG */ }; #ifndef NDEBUG template -bool ImmutableObjectHistory::validate() +bool ImmutableObjectHistory::valid() { // The immutable object content is captured either by a shared object, or by its serialization, but not both. assert(! m_shared_object == ! m_serialized.empty()); @@ -295,7 +340,7 @@ bool ImmutableObjectHistory::validate() #ifndef NDEBUG template -bool MutableObjectHistory::validate() +bool MutableObjectHistory::valid() { // Verify that the history intervals are sorted and do not overlap, and that the data reference counters are correct. if (! m_history.empty()) { @@ -329,7 +374,9 @@ public: void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); void load_snapshot(size_t timestamp, Slic3r::Model &model); - bool undo(Slic3r::Model &model); + bool has_undo_snapshot() const; + bool has_redo_snapshot() const; + bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection); bool redo(Slic3r::Model &model); // Snapshot history (names with timestamps). @@ -344,6 +391,41 @@ public: template std::shared_ptr load_immutable_object(const Slic3r::ObjectID id); template void load_mutable_object(const Slic3r::ObjectID id, T &target); +#ifdef SLIC3R_UNDOREDO_DEBUG + std::string format() const { + std::string out = "Objects\n"; + for (const std::pair> &kvp : m_objects) + out += std::string("ObjectID:") + std::to_string(kvp.first.id) + " " + kvp.second->format() + "\n"; + out += "Snapshots\n"; + for (const Snapshot &snapshot : m_snapshots) { + if (snapshot.timestamp == m_active_snapshot_time) + out += ">>> "; + out += std::string("Name:") + snapshot.name + ", timestamp: " + std::to_string(snapshot.timestamp) + ", Model ID:" + std::to_string(snapshot.model_id) + "\n"; + } + if (m_active_snapshot_time > m_snapshots.back().timestamp) + out += ">>>\n"; + out += "Current time: " + std::to_string(m_current_time) + "\n"; + return out; + } + void print() const { + std::cout << "Undo / Redo stack" << std::endl; + std::cout << this->format() << std::endl; + } +#endif /* SLIC3R_UNDOREDO_DEBUG */ + + +#ifndef NDEBUG + bool valid() const { + assert(! m_snapshots.empty()); + auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + assert(it == m_snapshots.end() || (it != m_snapshots.begin() && it->timestamp == m_active_snapshot_time)); + assert(it != m_snapshots.end() || m_active_snapshot_time > m_snapshots.back().timestamp); + for (auto it = m_objects.begin(); it != m_objects.end(); ++ it) + assert(it->second->valid()); + return true; + } +#endif /* NDEBUG */ + private: template ObjectID immutable_object_id(const std::shared_ptr &ptr) { return this->immutable_object_id_impl((const void*)ptr.get()); @@ -554,7 +636,11 @@ void StackImpl::initialize(const Slic3r::Model &model, const Slic3r::GUI::Select void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { // Release old snapshot data. - ++ m_active_snapshot_time; + // The active snapshot may be above the last snapshot if there is no redo data available. + if (! m_snapshots.empty() && m_active_snapshot_time > m_snapshots.back().timestamp) + m_active_snapshot_time = m_snapshots.back().timestamp + 1; + else + ++ m_active_snapshot_time; for (auto &kvp : m_objects) kvp.second->relese_after_timestamp(m_active_snapshot_time); { @@ -572,10 +658,15 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo } this->save_mutable_object(m_selection); // Save the snapshot info. - m_active_snapshot_time = m_current_time ++; - m_snapshots.emplace_back(snapshot_name, m_active_snapshot_time, model.id().id); + m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id); + m_active_snapshot_time = m_current_time; // Release empty objects from the history. this->collect_garbage(); + assert(this->valid()); +#ifdef SLIC3R_UNDOREDO_DEBUG + std::cout << "After snapshot" << std::endl; + this->print(); +#endif /* SLIC3R_UNDOREDO_DEBUG */ } void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model) @@ -588,32 +679,54 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model) m_active_snapshot_time = timestamp; model.clear_objects(); model.clear_materials(); - this->load_mutable_object(ObjectID(it_snapshot->model_object_id), model); + this->load_mutable_object(ObjectID(it_snapshot->model_id), model); model.update_links_bottom_up_recursive(); m_selection.volumes_and_instances.clear(); this->load_mutable_object(m_selection.id(), m_selection); // Sort the volumes so that we may use binary search. std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end()); this->m_active_snapshot_time = timestamp; + assert(this->valid()); } -bool StackImpl::undo(Slic3r::Model &model) +bool StackImpl::has_undo_snapshot() const { + assert(this->valid()); + auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + return -- it != m_snapshots.begin(); +} + +bool StackImpl::has_redo_snapshot() const +{ + auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + return it != m_snapshots.end() && ++ it != m_snapshots.end(); +} + +bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection) +{ + assert(this->valid()); auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); - assert(it_current != m_snapshots.end() && it_current != m_snapshots.begin() && it_current->timestamp == m_active_snapshot_time); if (-- it_current == m_snapshots.begin()) return false; this->load_snapshot(it_current->timestamp, model); +#ifdef SLIC3R_UNDOREDO_DEBUG + std::cout << "After undo" << std::endl; + this->print(); +#endif /* SLIC3R_UNDOREDO_DEBUG */ return true; } bool StackImpl::redo(Slic3r::Model &model) { + assert(this->valid()); auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); - assert(it_current != m_snapshots.end() && it_current != m_snapshots.begin() && it_current->timestamp == m_active_snapshot_time); - if (++ it_current == m_snapshots.end()) + if (it_current == m_snapshots.end() || ++ it_current == m_snapshots.end()) return false; - this->load_snapshot(it_current->timestamp, model); + this->load_snapshot(it_current->timestamp, model); +#ifdef SLIC3R_UNDOREDO_DEBUG + std::cout << "After redo" << std::endl; + this->print(); +#endif /* SLIC3R_UNDOREDO_DEBUG */ return true; } @@ -637,7 +750,9 @@ Stack::~Stack() {} void Stack::initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->initialize(model, selection); } void Stack::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->take_snapshot(snapshot_name, model, selection); } void Stack::load_snapshot(size_t timestamp, Slic3r::Model &model) { pimpl->load_snapshot(timestamp, model); } -bool Stack::undo(Slic3r::Model &model) { return pimpl->undo(model); } +bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); } +bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); } +bool Stack::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { return pimpl->undo(model, selection); } bool Stack::redo(Slic3r::Model &model) { return pimpl->redo(model); } const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); } diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 0c97a0307f..e50f6ad635 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -19,11 +19,11 @@ namespace UndoRedo { struct Snapshot { Snapshot(size_t timestamp) : timestamp(timestamp) {} - Snapshot(const std::string &name, size_t timestamp, size_t model_object_id) : name(name), timestamp(timestamp), model_object_id(model_object_id) {} + Snapshot(const std::string &name, size_t timestamp, size_t model_id) : name(name), timestamp(timestamp), model_id(model_id) {} std::string name; size_t timestamp; - size_t model_object_id; + size_t model_id; bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; } bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; } @@ -53,7 +53,10 @@ public: void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); void load_snapshot(size_t timestamp, Slic3r::Model &model); - bool undo(Slic3r::Model &model); + bool has_undo_snapshot() const; + bool has_redo_snapshot() const; + // Undoing an action may need to take a snapshot of the current application state. + bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection); bool redo(Slic3r::Model &model); // Snapshot history (names with timestamps). From a29cc9e242285bc394fdd4e08a6911830cdd6631 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 4 Jul 2019 17:33:19 +0200 Subject: [PATCH 266/627] Update object list after undo/redo --- src/libslic3r/Model.hpp | 4 +-- src/slic3r/GUI/GUI_ObjectList.cpp | 42 ++++++++++++++++++++++++++++++- src/slic3r/GUI/GUI_ObjectList.hpp | 2 ++ src/slic3r/GUI/Plater.cpp | 40 ++++++++++++++++++++++++++--- src/slic3r/GUI/Selection.cpp | 8 +++--- src/slic3r/GUI/Selection.hpp | 2 +- src/slic3r/Utils/UndoRedo.cpp | 6 ++--- src/slic3r/Utils/UndoRedo.hpp | 4 +-- 8 files changed, 89 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 398756fc9c..0c4c2ed2b9 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -508,8 +508,8 @@ private: ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { - assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); - assert(this->id() == other.id() && this->config.id() == other.config.id()); +// assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); +// assert(this->id() == other.id() && this->config.id() == other.config.id()); this->set_material_id(other.material_id()); this->config.set_new_unique_id(); if (mesh.stl.stats.number_of_facets > 1) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d7d1a1af7b..acb6d3a866 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -65,6 +65,11 @@ static int extruders_count() return wxGetApp().extruders_cnt(); } +static void take_snapshot(const wxString& snapshot_name) +{ + wxGetApp().plater()->take_snapshot(snapshot_name); +} + ObjectList::ObjectList(wxWindow* parent) : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), m_parent(parent) @@ -580,7 +585,6 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol if (volumes.empty()) return; - ModelObject& model_object = *(*m_objects)[obj_idx]; const auto object_item = m_objects_model->GetItemById(obj_idx); wxDataViewItemArray items; @@ -816,6 +820,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event) if (m_dragged_data.type() == itInstance) { + take_snapshot(_(L("Instances to Separated Objects"))); instances_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.inst_idxs()); m_dragged_data.clear(); return; @@ -833,6 +838,8 @@ void ObjectList::OnDrop(wxDataViewEvent &event) // if (to_volume_id > from_volume_id) to_volume_id--; // #endif // __WXGTK__ + take_snapshot(_(L("Remov Volume(s)"))); + auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes; auto delta = to_volume_id < from_volume_id ? -1 : 1; int cnt = 0; @@ -1427,6 +1434,8 @@ void ObjectList::load_subobject(ModelVolumeType type) if (m_objects_model->GetItemType(item)&itInstance) item = m_objects_model->GetItemById(obj_idx); + take_snapshot(_(L("Load Part"))); + std::vector> volumes_info; load_part((*m_objects)[obj_idx], volumes_info, type); @@ -1502,6 +1511,8 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode if (instance_idx == -1) return; + take_snapshot(_(L("Add Generic Subobject"))); + // Selected object ModelObject &model_object = *(*m_objects)[obj_idx]; // Bounding box of the selected instance in world coordinate system including the translation, without modifiers. @@ -1616,6 +1627,8 @@ void ObjectList::del_instances_from_object(const int obj_idx) if (instances.size() <= 1) return; + take_snapshot(_(L("Delete All Instances from Object"))); + while ( instances.size()> 1) instances.pop_back(); @@ -1645,6 +1658,8 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con return false; } + take_snapshot(_(L("Delete Subobject"))); + object->delete_volume(idx); if (object->volumes.size() == 1) @@ -1661,6 +1676,8 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last intance from object."))); return false; } + + take_snapshot(_(L("Delete Instance"))); object->delete_instance(idx); } else @@ -1688,6 +1705,8 @@ void ObjectList::split() return; } + take_snapshot(_(L("Split to Parts"))); + wxBusyCursor wait; auto model_object = (*m_objects)[obj_idx]; @@ -1945,6 +1964,8 @@ void ObjectList::delete_from_model_and_list(const ItemType type, const int obj_i if ( !(type&(itObject|itVolume|itInstance)) ) return; + take_snapshot(_(L("Delete Selected Item"))); + if (type&itObject) { del_object(obj_idx); delete_object_from_list(obj_idx); @@ -2502,6 +2523,8 @@ void ObjectList::change_part_type() if (new_type == type || new_type == ModelVolumeType::INVALID) return; + take_snapshot(_(L("Paste from Clipboard"))); + const auto item = GetSelection(); volume->set_type(new_type); m_objects_model->SetVolumeType(item, new_type); @@ -2862,5 +2885,22 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const wxGetApp().plater()->update(); } +void ObjectList::recreate_object_list() +{ + m_prevent_list_events = true; + m_prevent_canvas_selection_update = true; + + m_objects_model->DeleteAll(); + + size_t obj_idx = 0; + while (obj_idx < m_objects->size()) { + add_object_to_list(obj_idx); + ++obj_idx; + } + + m_prevent_canvas_selection_update = false; + m_prevent_list_events = false; +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 166606e2ee..3d312d9c98 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -300,6 +300,8 @@ public: void msw_rescale(); + void recreate_object_list(); + private: #ifdef __WXOSX__ // void OnChar(wxKeyEvent& event); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a492584e65..567be0d412 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1738,7 +1738,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event &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(); }); + view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { + this->take_snapshot(_(L("Instance Moved"))); + update(); }); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); }); @@ -2337,17 +2339,21 @@ void Plater::priv::object_list_changed() void Plater::priv::select_all() { +// this->take_snapshot(_(L("Select All"))); + view3D->select_all(); this->sidebar->obj_list()->update_selections(); } void Plater::priv::deselect_all() { +// this->take_snapshot(_(L("Deselect All"))); view3D->deselect_all(); } void Plater::priv::remove(size_t obj_idx) { + this->take_snapshot(_(L("Remove Object"))); // Prevent toolpaths preview from rendering while we modify the Print object preview->set_enabled(false); @@ -2364,6 +2370,7 @@ void Plater::priv::remove(size_t obj_idx) void Plater::priv::delete_object_from_model(size_t obj_idx) { +// this->take_snapshot(_(L("Delete Object"))); // ys_FIXME What is the difference with "Remove Object"? model.delete_object(obj_idx); update(); object_list_changed(); @@ -2398,17 +2405,20 @@ void Plater::priv::reset() void Plater::priv::mirror(Axis axis) { + this->take_snapshot(_(L("Mirror"))); 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); } @@ -2564,6 +2574,8 @@ void Plater::priv::split_object() Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part."))); else { + this->take_snapshot(_(L("Split to Objects"))); + unsigned int counter = 1; for (ModelObject* m : new_objects) m->name = current_model_object->name + "_" + std::to_string(counter++); @@ -2835,6 +2847,9 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = { if (obj_idx < 0) return; + + this->take_snapshot(_(L("Fix Throught NetFabb"))); + fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx); this->update(); this->object_list_changed(); @@ -3074,6 +3089,8 @@ void Plater::priv::on_action_layersediting(SimpleEvent&) void Plater::priv::on_object_select(SimpleEvent& evt) { +// this->take_snapshot(_(L("Object Selection"))); + wxGetApp().obj_list()->update_selections(); selection_changed(); } @@ -3135,6 +3152,8 @@ void Plater::priv::on_right_click(Vec2dEvent& evt) void Plater::priv::on_wipetower_moved(Vec3dEvent &evt) { + this->take_snapshot(_(L("Wipe Tower Moved"))); + DynamicPrintConfig cfg; cfg.opt("wipe_tower_x", true)->value = evt.data(0); cfg.opt("wipe_tower_y", true)->value = evt.data(1); @@ -3143,6 +3162,8 @@ void Plater::priv::on_wipetower_moved(Vec3dEvent &evt) void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt) { + this->take_snapshot(_(L("Wipe Tower Rotated"))); + DynamicPrintConfig cfg; cfg.opt("wipe_tower_x", true)->value = evt.data(0); cfg.opt("wipe_tower_y", true)->value = evt.data(1); @@ -3514,7 +3535,9 @@ void Plater::priv::update_after_undo_redo() this->update(false); // update volumes from the deserializd model //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time) this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances); -// wxGetApp().obj_list()->update_selections(); + + wxGetApp().obj_list()->recreate_object_list(); + wxGetApp().obj_list()->update_selections(); // selection_changed(); //FIXME what about the state of the manipulators? //FIXME what about the focus? Cursor in the side panel? @@ -3580,6 +3603,8 @@ void Plater::add_model() if (input_files.empty()) return; + this->take_snapshot(_(L("Add object(s)"))); + std::vector input_paths; for (const auto &file : input_files) { input_paths.push_back(into_path(file)); @@ -3717,6 +3742,8 @@ void Plater::set_number_of_copies(/*size_t num*/) if (num < 0) return; + this->take_snapshot(wxString::Format(_(L("Set numbers of copies to %d")), num)); + int diff = (int)num - (int)model_object->instances.size(); if (diff > 0) increase_instances(diff); @@ -3745,6 +3772,8 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe return; } + this->take_snapshot(_(L("Cut"))); + wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); @@ -4245,8 +4274,11 @@ void Plater::copy_selection_to_clipboard() void Plater::paste_from_clipboard() { - if (can_paste_from_clipboard()) - p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); + if (!can_paste_from_clipboard()) + return; + + this->take_snapshot(_(L("Paste From Clipboard"))); + p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); } void Plater::msw_rescale() diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 2986d97dd1..ffb4237580 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -311,7 +311,7 @@ void Selection::add_all() this->set_bounding_boxes_dirty(); } -void Selection::set_deserialized(EMode mode, const std::vector> &volumes_and_instances) +void Selection::set_deserialized(EMode mode, const std::vector> &volumes_and_instances) { if (! m_valid) return; @@ -320,11 +320,9 @@ void Selection::set_deserialized(EMode mode, const std::vectorselected = false; m_list.clear(); - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) { - const GLVolume::CompositeID &id = (*m_volumes)[i]->composite_id; - if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), std::make_pair(id.volume_id, id.instance_id))) + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) + if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), (*m_volumes)[i]->geometry_id)) this->do_add_volume(i); - } update_type(); this->set_bounding_boxes_dirty(); } diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 8168e5e88c..35336c2b34 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -238,7 +238,7 @@ public: void add_all(); // To be called after Undo or Redo once the volumes are updated. - void set_deserialized(EMode mode, const std::vector> &volumes_and_instances); + void set_deserialized(EMode mode, const std::vector> &volumes_and_instances); // Update the selection based on the new instance IDs. void instances_changed(const std::vector &instance_ids_selected); diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 34d83c4450..8c0e065266 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -566,10 +566,8 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo m_selection.volumes_and_instances.clear(); m_selection.volumes_and_instances.reserve(selection.get_volume_idxs().size()); m_selection.mode = selection.get_mode(); - for (unsigned int volume_idx : selection.get_volume_idxs()) { - const Slic3r::GLVolume::CompositeID &id = selection.get_volume(volume_idx)->composite_id; - m_selection.volumes_and_instances.emplace_back(id.volume_id, id.instance_id); - } + for (unsigned int volume_idx : selection.get_volume_idxs()) + m_selection.volumes_and_instances.emplace_back(selection.get_volume(volume_idx)->geometry_id); this->save_mutable_object(m_selection); // Save the snapshot info. m_active_snapshot_time = m_current_time ++; diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 0c97a0307f..9e808c31fd 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -31,8 +31,8 @@ struct Snapshot // Excerpt of Slic3r::GUI::Selection for serialization onto the Undo / Redo stack. struct Selection : public Slic3r::ObjectBase { - unsigned char mode; - std::vector> volumes_and_instances; + unsigned char mode; + std::vector> volumes_and_instances; template void serialize(Archive &ar) { ar(mode, volumes_and_instances); } }; From 70c6558a4c89e00ff806c602f32dc5f0cb2c58cd Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 19:48:00 +0200 Subject: [PATCH 267/627] Fix of compilation on Linux --- src/libslic3r/Geometry.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index f1987734f9..bd248d1b67 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -270,17 +270,17 @@ public: private: template void load(Archive& archive, Slic3r::Geometry::Transformation &t) { - archive.loadBinary((char*)m.data(), sizeof(float) * 4); + archive.loadBinary((char*)t.data(), sizeof(float) * 4); } template void save(Archive& archive, const Slic3r::Geometry::Transformation &t) const { - archive.saveBinary((char*)m.data(), sizeof(float) * 4); + archive.saveBinary((char*)t.data(), sizeof(float) * 4); } private: friend class cereal::access; template void serialize(Archive & ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); } explicit Transformation(int) : m_dirty(true) {} - template static void load_and_construct(Archive & ar, cereal::construct &construct) + template static void load_and_construct(Archive &ar, cereal::construct &construct) { // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. construct(1); From 3e5f9b5a224a921297a5de0c7407e4e7862ec98b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 19:59:45 +0200 Subject: [PATCH 268/627] Removed some junk templates, which pass compilation on Windows even if they are invalid. --- src/libslic3r/Geometry.hpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index bd248d1b67..d8eefbed08 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -268,14 +268,6 @@ public: // Bounding box is expected to be centered around zero in all axes. static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox); -private: - template void load(Archive& archive, Slic3r::Geometry::Transformation &t) { - archive.loadBinary((char*)t.data(), sizeof(float) * 4); - } - template void save(Archive& archive, const Slic3r::Geometry::Transformation &t) const { - archive.saveBinary((char*)t.data(), sizeof(float) * 4); - } - private: friend class cereal::access; template void serialize(Archive & ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); } From b5b7463dc5290b963cfe4b14d536eb4d77e020dc Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 20:14:38 +0200 Subject: [PATCH 269/627] Testing code for serialization of DynamicPrintConfig --- src/slic3r/Utils/UndoRedo.cpp | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 4a9af3b43e..528c1c135c 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -760,3 +760,43 @@ const std::vector& Stack::snapshots() const { return pimpl->snapshots( } // namespace UndoRedo } // namespace Slic3r + + +//FIXME we should have unit tests for testing serialization of basic types as DynamicPrintConfig. +#if 0 +#include "libslic3r/Config.hpp" +#include "libslic3r/PrintConfig.hpp" +namespace Slic3r { + bool test_dynamic_print_config_serialization() { + FullPrintConfig full_print_config; + DynamicPrintConfig cfg; + cfg.apply(full_print_config, false); + + std::string serialized; + try { + std::ostringstream ss; + cereal::BinaryOutputArchive oarchive(ss); + oarchive(cfg); + serialized = ss.str(); + } catch (std::runtime_error e) { + e.what(); + } + + DynamicPrintConfig cfg2; + try { + std::stringstream ss(serialized); + cereal::BinaryInputArchive iarchive(ss); + iarchive(cfg2); + } catch (std::runtime_error e) { + e.what(); + } + + if (cfg == cfg2) { + printf("Yes!\n"); + return true; + } + printf("No!\n"); + return false; + } +} // namespace Slic3r +#endif From 3a24fb2f4725d79b181fcf5d80646711be5c888c Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 20:25:52 +0200 Subject: [PATCH 270/627] Yet another compilation fix. --- src/libslic3r/Geometry.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index d8eefbed08..585dc4b0bc 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -276,7 +276,7 @@ private: { // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. construct(1); - ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, m_mirror); + ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror); } }; From b1420283b6a548b3a64a7d4129e37651f655562f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 20:49:46 +0200 Subject: [PATCH 271/627] Fixed merge issues. --- src/libslic3r/Format/3mf.cpp | 2 +- src/libslic3r/Format/AMF.cpp | 6 ++---- src/libslic3r/Model.cpp | 4 ++-- src/libslic3r/Model.hpp | 2 +- src/libslic3r/Print.cpp | 8 ++++---- src/slic3r/GUI/GUI_ObjectList.cpp | 8 ++++++++ 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index e9f0689745..21c680d2dd 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -2134,7 +2134,7 @@ namespace Slic3r { const DynamicPrintConfig& config = range.second; for (const std::string& opt_key : config.keys()) { - pt::ptree& opt_tree = range_tree.add("option", config.serialize(opt_key)); + pt::ptree& opt_tree = range_tree.add("option", config.opt_serialize(opt_key)); opt_tree.put(".opt_key", opt_key); } } diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 074b7e9338..e964d3b9d0 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -933,10 +933,8 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) stream << ";" << layer_height_profile[i]; stream << "\n \n"; } - //FIXME Store the layer height ranges (ModelObject::layer_height_ranges) - - // #ys_FIXME_experiment : Try to export layer config range + // Export layer height ranges including the layer range specific config overrides. const t_layer_config_ranges& config_ranges = object->layer_config_ranges; if (!config_ranges.empty()) { @@ -950,7 +948,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) stream << range.first.first << ";" << range.first.second << "\n"; for (const std::string& key : range.second.keys()) - stream << " " << range.second.serialize(key) << "\n"; + stream << " " << range.second.opt_serialize(key) << "\n"; stream << " \n"; layer_counter++; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 0c29aa7214..949f82c0a5 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1579,9 +1579,9 @@ void ModelVolume::center_geometry_after_creation() if (!shift.isApprox(Vec3d::Zero())) { if (m_mesh) - m_mesh->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + const_cast(m_mesh.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); if (m_convex_hull) - m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + const_cast(m_convex_hull.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); translate(shift); } } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 0a50315ced..f8ebe31343 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -342,7 +342,7 @@ private: } template void serialize(Archive &ar) { ar(cereal::base_class(this)); - ar(name, input_file, instances, volumes, config, layer_height_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation, + ar(name, input_file, instances, volumes, config, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation, m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); } }; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 122034c535..f34a667600 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -587,10 +587,10 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co Moved, Deleted, }; - ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {} - ModelID id; - Status status; - LayerRanges layer_ranges; + ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} + ObjectID id; + Status status; + LayerRanges layer_ranges; // Search by id. bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } }; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d47c84c37a..4ef1cb0d2e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3400,5 +3400,13 @@ void ObjectList::recreate_object_list() m_prevent_list_events = false; } +ModelObject* ObjectList::object(const int obj_idx) const +{ + if (obj_idx < 0) + return nullptr; + + return (*m_objects)[obj_idx]; +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file From 497b01f24a50b7f61210722acd546ee18bc72afa Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 21:02:08 +0200 Subject: [PATCH 272/627] Trying to fix some template resolution on Linux --- src/libslic3r/Config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index c5754fb839..aea3f215ce 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -674,7 +674,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre // Let the parent decide what to do if the opt_key is not defined by this->def(). return nullptr; ConfigOption *opt = optdef->create_default_option(); - this->options.insert(it, std::make_pair(opt_key, opt)); + this->options.emplace_hint(it, opt_key, std::unique_ptr(opt)); return opt; } From 3d420db531afc88a41cbcfff1b0902c15108e9d8 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 21:28:57 +0200 Subject: [PATCH 273/627] Fix of perl bindings --- xs/src/xsinit.h | 1 + 1 file changed, 1 insertion(+) diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index f14e1262dc..8745abdc7c 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -87,6 +87,7 @@ extern "C" { #endif /* _MSC_VER */ #undef Zero #undef Packet +#undef ST #undef _ } #endif From 9fd0c55eb86110182b4cf53ac93e3fb802cbd35b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 22:09:14 +0200 Subject: [PATCH 274/627] Simplified the "cereal" includes to not clash with Perl includes --- src/libslic3r/BoundingBox.hpp | 3 --- src/libslic3r/Config.cpp | 1 + src/libslic3r/Config.hpp | 7 ++----- src/libslic3r/Geometry.hpp | 3 +-- src/libslic3r/ObjectID.hpp | 6 +----- src/libslic3r/Point.hpp | 3 --- src/libslic3r/PrintConfig.cpp | 1 + src/libslic3r/pchheader.hpp | 8 ++------ xs/src/xsinit.h | 1 - 9 files changed, 8 insertions(+), 25 deletions(-) diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index b12ed55517..b1ebdcfbc7 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -161,9 +161,6 @@ inline bool empty(const BoundingBox3Base &bb) } // namespace Slic3r -#include -#include - // Serialization through the Cereal library namespace cereal { template void serialize(Archive& archive, Slic3r::BoundingBox &bb) { archive(bb.min, bb.max, bb.defined); } diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index aea3f215ce..9d0649a1fe 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -822,6 +822,7 @@ t_config_option_keys StaticConfig::keys() const } +#include CEREAL_REGISTER_TYPE(Slic3r::ConfigOption) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 1511c4b289..844287efbe 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -18,11 +18,8 @@ #include #include -#include -#include -#include -#include -#include +#include +#include namespace Slic3r { diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 585dc4b0bc..dca1872d82 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -8,8 +8,7 @@ #include "Polyline.hpp" // Serialization through the Cereal library -#include -#include +#include #include "boost/polygon/voronoi.hpp" using boost::polygon::voronoi_builder; diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index c708e5687e..fe0bf465f8 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -1,11 +1,7 @@ #ifndef slic3r_ObjectID_hpp_ #define slic3r_ObjectID_hpp_ -#include -#include -#include -#include -#include +#include namespace Slic3r { diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 8979523b7e..994f45e59d 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -291,9 +291,6 @@ namespace boost { namespace polygon { } } // end Boost -#include -#include - // Serialization through the Cereal library namespace cereal { // template void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 97787fff6a..638ce38f19 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3198,5 +3198,6 @@ void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std:: } +#include CEREAL_REGISTER_TYPE(Slic3r::DynamicPrintConfig) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::DynamicConfig, Slic3r::DynamicPrintConfig) diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index 54b563defb..c0ffe21083 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -102,12 +102,8 @@ #include #include -#include -#include -#include -#include -#include -#include +#include +#include #include "BoundingBox.hpp" #include "ClipperUtils.hpp" diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index 8745abdc7c..f14e1262dc 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -87,7 +87,6 @@ extern "C" { #endif /* _MSC_VER */ #undef Zero #undef Packet -#undef ST #undef _ } #endif From 211d1ee1e32f8eb2d68173323208a9dd660689bc Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 22:52:33 +0200 Subject: [PATCH 275/627] Trying to make all C++ of the platforms happy. --- src/CMakeLists.txt | 2 +- src/libslic3r/ObjectID.hpp | 3 +-- src/slic3r/Utils/UndoRedo.cpp | 29 ++++++++++++++++------------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 61faa05710..3ee46289ad 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -75,7 +75,7 @@ if (NOT MSVC) set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") endif () -target_link_libraries(PrusaSlicer libslic3r) +target_link_libraries(PrusaSlicer libslic3r cereal) if (APPLE) # add_compile_options(-stdlib=libc++) # add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE) diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index fe0bf465f8..df16f9202c 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -69,7 +69,6 @@ protected: void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } private: - friend class UndoRedo::StackImpl; ObjectID m_id; static inline ObjectID generate_new_id() { return ObjectID(++ s_last_id); } @@ -77,9 +76,9 @@ private: friend ObjectID wipe_tower_object_id(); friend ObjectID wipe_tower_instance_id(); - friend class Slic3r::UndoRedo::StackImpl; friend class cereal::access; + friend class Slic3r::UndoRedo::StackImpl; template void serialize(Archive &ar) { ar(m_id); } ObjectBase(const ObjectID id) : m_id(id) {} template static void load_and_construct(Archive & ar, cereal::construct &construct) { ObjectID id; ar(id); construct(id); } diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 11fceee0a8..d5da0c618a 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -153,19 +153,7 @@ public: bool is_serialized() const { return m_shared_object.get() == nullptr; } const std::string& serialized_data() const { return m_serialized; } - std::shared_ptr& shared_ptr(StackImpl &stack) { - if (m_shared_object.get() == nullptr && ! this->m_serialized.empty()) { - // Deserialize the object. - std::istringstream iss(m_serialized); - { - Slic3r::UndoRedo::InputArchive archive(stack, iss); - std::unique_ptr::type> mesh(new std::remove_const::type()); - archive(*mesh.get()); - m_shared_object = std::move(mesh); - } - } - return m_shared_object; - } + std::shared_ptr& shared_ptr(StackImpl &stack); #ifdef SLIC3R_UNDOREDO_DEBUG std::string format() override { @@ -560,6 +548,21 @@ namespace cereal namespace Slic3r { namespace UndoRedo { +template std::shared_ptr& ImmutableObjectHistory::shared_ptr(StackImpl &stack) +{ + if (m_shared_object.get() == nullptr && ! this->m_serialized.empty()) { + // Deserialize the object. + std::istringstream iss(m_serialized); + { + Slic3r::UndoRedo::InputArchive archive(stack, iss); + std::unique_ptr::type> mesh(new std::remove_const::type()); + archive(*mesh.get()); + m_shared_object = std::move(mesh); + } + } + return m_shared_object; +} + template ObjectID StackImpl::save_mutable_object(const T &object) { // First find or allocate a history stack for the ObjectID of this object instance. From 7c732c7482c415dbd4726612bee4f93a2e72c9f9 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 4 Jul 2019 23:34:18 +0200 Subject: [PATCH 276/627] Trying to fix some Linux & OSX compilation issues. --- src/libslic3r/TriangleMesh.hpp | 1 + src/slic3r/Utils/UndoRedo.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 1fc5128932..5dd2597a5c 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -196,6 +196,7 @@ TriangleMesh make_sphere(double rho, double fa=(2*PI/360)); } // Serialization through the Cereal library +#include namespace cereal { template struct specialize {}; template void load(Archive &archive, Slic3r::TriangleMesh &mesh) { diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index d5da0c618a..ca40c6939a 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -555,7 +555,8 @@ template std::shared_ptr& ImmutableObjectHistory::share std::istringstream iss(m_serialized); { Slic3r::UndoRedo::InputArchive archive(stack, iss); - std::unique_ptr::type> mesh(new std::remove_const::type()); + typedef typename std::remove_const::type Type; + std::unique_ptr mesh(new Type()); archive(*mesh.get()); m_shared_object = std::move(mesh); } From 357e578a840cc07a5ae277c96534b866de2d5c79 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 5 Jul 2019 10:46:42 +0200 Subject: [PATCH 277/627] Fixed includes on OSX --- src/slic3r/Utils/UndoRedo.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 92946d761f..b08e410ff4 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -1,8 +1,10 @@ #ifndef slic3r_Utils_UndoRedo_hpp_ #define slic3r_Utils_UndoRedo_hpp_ +#include #include #include +#include #include From 6a3fc5bde3eb1c6fca1ce8944d6d2dc0fef3d0fa Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 5 Jul 2019 11:42:36 +0200 Subject: [PATCH 278/627] Documented the cereal library manual patching (FIXME!) --- doc/How to build - Mac OS.md | 3 +++ src/libslic3r/Utils.hpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/How to build - Mac OS.md b/doc/How to build - Mac OS.md index 42a71e10d7..b4196909d3 100644 --- a/doc/How to build - Mac OS.md +++ b/doc/How to build - Mac OS.md @@ -20,6 +20,9 @@ You can also customize the bundle output path using the `-DDESTDIR=` **Warning**: Once the dependency bundle is installed in a destdir, the destdir cannot be moved elsewhere. (This is because wxWidgets hardcodes the installation path.) +FIXME The Cereal serialization library needs a tiny patch on some old OSX clang installations +https://github.com/USCiLab/cereal/issues/339#issuecomment-246166717 + ### Building PrusaSlicer diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index adf7f57a71..3b30e981cb 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -182,7 +182,7 @@ class ScopeGuard public: typedef std::function Closure; private: - bool committed; +// bool committed; Closure closure; public: From 4e2fda3315dcfcc9272b71e7c286585a71dca0a5 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 5 Jul 2019 19:06:19 +0200 Subject: [PATCH 279/627] Undo / Redo fixes --- src/libslic3r/Model.hpp | 10 +- src/libslic3r/ObjectID.hpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 3 +- src/slic3r/GUI/GUI_ObjectList.cpp | 28 +++-- src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 1 + src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 1 + src/slic3r/GUI/Plater.cpp | 10 +- src/slic3r/GUI/Selection.cpp | 24 ++++ src/slic3r/Utils/UndoRedo.cpp | 129 ++++++++++++---------- src/slic3r/Utils/UndoRedo.hpp | 30 +++-- 11 files changed, 151 insertions(+), 89 deletions(-) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index f8ebe31343..7551bd8cb8 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -283,7 +283,7 @@ private: explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) { assert(this->id().invalid()); assert(this->config.id().invalid()); } ~ModelObject(); - void assign_new_unique_ids_recursive(); + void assign_new_unique_ids_recursive() override; // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). @@ -463,6 +463,7 @@ protected: // Copies IDs of both the ModelVolume and its config. explicit ModelVolume(const ModelVolume &rhs) = default; void set_model_object(ModelObject *model_object) { object = model_object; } + void assign_new_unique_ids_recursive() override { ObjectBase::set_new_unique_id(); config.set_new_unique_id(); } void transform_this_mesh(const Transform3d& t, bool fix_left_handed); void transform_this_mesh(const Matrix3d& m, bool fix_left_handed); @@ -508,14 +509,13 @@ private: ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : name(other.name), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation) { -// assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); -// assert(this->id() == other.id() && this->config.id() == other.config.id()); + assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); + assert(this->id() != other.id() && this->config.id() == other.config.id()); this->set_material_id(other.material_id()); this->config.set_new_unique_id(); if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); - assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id()); - assert(this->id() != other.id() && this->config.id() != other.config.id()); + assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id()); } ModelVolume& operator=(ModelVolume &rhs) = delete; diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index df16f9202c..484d1173ba 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -66,7 +66,7 @@ protected: void copy_id(const ObjectBase &rhs) { m_id = rhs.id(); } // Override this method if a ObjectBase derived class owns other ObjectBase derived instances. - void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } + virtual void assign_new_unique_ids_recursive() { this->set_new_unique_id(); } private: ObjectID m_id; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 43d419eead..1d5cec04ae 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2947,9 +2947,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging) { m_regenerate_volumes = false; + wxGetApp().plater()->take_snapshot(_(L("Move Object"))); do_move(); wxGetApp().obj_manipul()->set_dirty(); - // Let the platter know that the dragging finished, so a delayed refresh + // Let the plater know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 4ef1cb0d2e..582fe10076 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2116,7 +2116,7 @@ void ObjectList::part_selection_changed() panel.Thaw(); } -void ObjectList::add_object_to_list(size_t obj_idx) +void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) { auto model_object = (*m_objects)[obj_idx]; const wxString& item_name = from_u8(model_object->name); @@ -2137,7 +2137,9 @@ void ObjectList::add_object_to_list(size_t obj_idx) false); auto opt_keys = volume->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { - select_item(m_objects_model->AddSettingsChild(vol_item)); + const wxDataViewItem &settings_item = m_objects_model->AddSettingsChild(vol_item); + if (call_selection_changed) + select_item(settings_item); Expand(vol_item); } } @@ -2151,7 +2153,9 @@ void ObjectList::add_object_to_list(size_t obj_idx) // add settings to the object, if it has those auto opt_keys = model_object->config.keys(); if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { - select_item(m_objects_model->AddSettingsChild(item)); + const wxDataViewItem &settings_item = m_objects_model->AddSettingsChild(item); + if (call_selection_changed) + select_item(settings_item); Expand(item); } @@ -2159,7 +2163,8 @@ void ObjectList::add_object_to_list(size_t obj_idx) add_layer_root_item(item); #ifndef __WXOSX__ - selection_changed(); + if (call_selection_changed) + selection_changed(); #endif //__WXMSW__ } @@ -2963,11 +2968,10 @@ void ObjectList::change_part_type() void ObjectList::last_volume_is_deleted(const int obj_idx) { - if (obj_idx < 0 || m_objects->empty() || - obj_idx <= m_objects->size() || - (*m_objects)[obj_idx]->volumes.empty()) + if (obj_idx < 0 || obj_idx >= m_objects->size() || (*m_objects)[obj_idx]->volumes.empty()) return; - auto volume = (*m_objects)[obj_idx]->volumes[0]; + + auto volume = (*m_objects)[obj_idx]->volumes.front(); // clear volume's config values volume->config.clear(); @@ -3388,14 +3392,20 @@ void ObjectList::recreate_object_list() m_prevent_list_events = true; m_prevent_canvas_selection_update = true; + // Unselect all objects before deleting them, so that no change of selection is emitted during deletion. + this->UnselectAll(); m_objects_model->DeleteAll(); size_t obj_idx = 0; while (obj_idx < m_objects->size()) { - add_object_to_list(obj_idx); + add_object_to_list(obj_idx, false); ++obj_idx; } +#ifndef __WXOSX__ + selection_changed(); +#endif /* __WXOSX__ */ + m_prevent_canvas_selection_update = false; m_prevent_list_events = false; } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 354f6c0194..2a92ecbe43 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -261,7 +261,7 @@ public: void part_selection_changed(); // Add object to the list - void add_object_to_list(size_t obj_idx); + void add_object_to_list(size_t obj_idx, bool call_selection_changed = true); // Delete object from the list void delete_object_from_list(); void delete_object_from_list(const size_t obj_idx); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 372cd79efb..79e45facd9 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -655,6 +655,7 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.start_dragging(); selection.translate(position - m_cache.position, selection.requires_local_axes()); + wxGetApp().plater()->take_snapshot(_(L("Set Position"))); canvas->do_move(); m_cache.position = position; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index c7435636d3..c8900d8da0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -673,6 +673,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) { case Move: { + wxGetApp().plater()->take_snapshot(_(L("Move Object"))); canvas.disable_regenerate_volumes(); canvas.do_move(); break; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0865ab713b..bb394ffcfb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1773,9 +1773,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event &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&) { - this->take_snapshot(_(L("Instance Moved"))); - update(); }); + view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); }); @@ -1826,7 +1824,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // updates camera type from .ini file camera.set_type(get_config("use_perspective_camera")); - this->undo_redo_stack.initialize(model, view3D->get_canvas3d()->get_selection()); + // Initialize the Undo / Redo stack with a first snapshot. this->take_snapshot(_(L("New Project"))); } @@ -3196,8 +3194,6 @@ void Plater::priv::on_right_click(Vec2dEvent& evt) void Plater::priv::on_wipetower_moved(Vec3dEvent &evt) { - this->take_snapshot(_(L("Wipe Tower Moved"))); - DynamicPrintConfig cfg; cfg.opt("wipe_tower_x", true)->value = evt.data(0); cfg.opt("wipe_tower_y", true)->value = evt.data(1); @@ -3206,8 +3202,6 @@ void Plater::priv::on_wipetower_moved(Vec3dEvent &evt) void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt) { - this->take_snapshot(_(L("Wipe Tower Rotated"))); - DynamicPrintConfig cfg; cfg.opt("wipe_tower_x", true)->value = evt.data(0); cfg.opt("wipe_tower_y", true)->value = evt.data(1); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 2d5be01ff1..a71005cf2c 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -806,6 +806,8 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) double s = std::min(sx, std::min(sy, sz)); if (s != 1.0) { + wxGetApp().plater()->take_snapshot(_(L("Scale To Fit"))); + TransformationType type; type.set_world(); type.set_relative(); @@ -2032,6 +2034,10 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const void Selection::paste_volumes_from_clipboard() { +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + int dst_obj_idx = get_object_idx(); if ((dst_obj_idx < 0) || ((int)m_model->objects.size() <= dst_obj_idx)) return; @@ -2073,6 +2079,9 @@ void Selection::paste_volumes_from_clipboard() } volumes.push_back(dst_volume); +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } // keeps relative position of multivolume selections @@ -2086,10 +2095,18 @@ void Selection::paste_volumes_from_clipboard() wxGetApp().obj_list()->paste_volumes_into_list(dst_obj_idx, volumes); } + +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } void Selection::paste_objects_from_clipboard() { +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + std::vector object_idxs; const ModelObjectPtrs& src_objects = m_clipboard.get_objects(); for (const ModelObject* src_object : src_objects) @@ -2103,9 +2120,16 @@ void Selection::paste_objects_from_clipboard() } object_idxs.push_back(m_model->objects.size() - 1); +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } wxGetApp().obj_list()->paste_objects_into_list(object_idxs); + +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ } } // namespace GUI diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index ca40c6939a..058062502d 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -28,6 +28,13 @@ namespace Slic3r { namespace UndoRedo { +static std::string topmost_snapsnot_name = "@@@ Topmost @@@"; + +bool Snapshot::is_topmost() const +{ + return this->name == topmost_snapsnot_name; +} + // Time interval, start is closed, end is open. struct Interval { @@ -63,12 +70,13 @@ public: // Is the object captured by this history mutable or immutable? virtual bool is_mutable() const = 0; virtual bool is_immutable() const = 0; + virtual const void* immutable_object_ptr() const { return nullptr; } // If the history is empty, the ObjectHistory object could be released. virtual bool empty() = 0; // Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. - virtual void relese_after_timestamp(size_t timestamp) = 0; + virtual void release_after_timestamp(size_t timestamp) = 0; #ifdef SLIC3R_UNDOREDO_DEBUG // Human readable debug information. @@ -89,12 +97,12 @@ public: bool empty() override { return m_history.empty(); } // Release all data after the given timestamp. The shared pointer is NOT released. - void relese_after_timestamp(size_t timestamp) override { + void release_after_timestamp(size_t timestamp) override { assert(! m_history.empty()); assert(this->valid()); // it points to an interval which either starts with timestamp, or follows the timestamp. auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp)); - if (it == m_history.end()) { + if (it != m_history.begin()) { auto it_prev = it; -- it_prev; assert(it_prev->begin() < timestamp); @@ -128,6 +136,7 @@ public: bool is_mutable() const override { return false; } bool is_immutable() const override { return true; } + const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); } void save(size_t active_snapshot_time, size_t current_time) { assert(m_history.empty() || m_history.back().end() <= active_snapshot_time || @@ -160,9 +169,9 @@ public: std::string out = typeid(T).name(); out += this->is_serialized() ? std::string(" len:") + std::to_string(m_serialized.size()) : - std::string(" ptr:") + ptr_to_string(m_shared_object.get()); + std::string(" shared_ptr:") + ptr_to_string(m_shared_object.get()); for (const Interval &interval : m_history) - out += std::string(",<") + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; + out += std::string(", <") + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; return out; } #endif /* SLIC3R_UNDOREDO_DEBUG */ @@ -296,13 +305,8 @@ public: #ifdef SLIC3R_UNDOREDO_DEBUG std::string format() override { std::string out = typeid(T).name(); - bool first = true; - for (const MutableHistoryInterval &interval : m_history) { - if (! first) - out += ","; - out += std::string("ptr:") + ptr_to_string(interval.data()) + " len:" + std::to_string(interval.size()) + " <" + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; - first = false; - } + for (const MutableHistoryInterval &interval : m_history) + out += std::string(", ptr:") + ptr_to_string(interval.data()) + " len:" + std::to_string(interval.size()) + " <" + std::to_string(interval.begin()) + "," + std::to_string(interval.end()) + ")"; return out; } #endif /* SLIC3R_UNDOREDO_DEBUG */ @@ -364,11 +368,13 @@ public: bool has_undo_snapshot() const; bool has_redo_snapshot() const; - bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection); - bool redo(Slic3r::Model &model); + bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t jump_to_time); + bool redo(Slic3r::Model &model, size_t jump_to_time); // Snapshot history (names with timestamps). - const std::vector& snapshots() const { return m_snapshots; } + const std::vector& snapshots() const { return m_snapshots; } + // Timestamp of the active snapshot. + size_t active_snapshot_time() const { return m_active_snapshot_time; } const Selection& selection_deserialized() const { return m_selection; } @@ -388,7 +394,8 @@ public: for (const Snapshot &snapshot : m_snapshots) { if (snapshot.timestamp == m_active_snapshot_time) out += ">>> "; - out += std::string("Name:") + snapshot.name + ", timestamp: " + std::to_string(snapshot.timestamp) + ", Model ID:" + std::to_string(snapshot.model_id) + "\n"; + out += std::string("Name: \"") + snapshot.name + "\", timestamp: " + std::to_string(snapshot.timestamp) + + ", Model ID:" + ((snapshot.model_id == 0) ? "Invalid" : std::to_string(snapshot.model_id)) + "\n"; } if (m_active_snapshot_time > m_snapshots.back().timestamp) out += ">>>\n"; @@ -406,9 +413,9 @@ public: bool valid() const { assert(! m_snapshots.empty()); auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); - assert(it == m_snapshots.end() || (it != m_snapshots.begin() && it->timestamp == m_active_snapshot_time)); - assert(it != m_snapshots.end() || m_active_snapshot_time > m_snapshots.back().timestamp); - for (auto it = m_objects.begin(); it != m_objects.end(); ++ it) + assert(it != m_snapshots.begin() && it != m_snapshots.end() && it->timestamp == m_active_snapshot_time); + assert(m_active_snapshot_time < m_snapshots.back().timestamp || m_snapshots.back().is_topmost()); + for (auto it = m_objects.begin(); it != m_objects.end(); ++ it) assert(it->second->valid()); return true; } @@ -624,29 +631,13 @@ template void StackImpl::load_mutable_object(const Sl archive(static_cast(target)); } -// The Undo / Redo stack is being initialized with an empty model and an empty selection. -// The first snapshot cannot be removed. -void StackImpl::initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) -{ - assert(m_active_snapshot_time == 0); - assert(m_current_time == 0); - // The initial time interval will be <0, 1) - m_active_snapshot_time = SIZE_MAX; // let it overflow to zero in take_snapshot - m_current_time = 0; - this->take_snapshot("Internal - Initialized", model, selection); -} - // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { // Release old snapshot data. - // The active snapshot may be above the last snapshot if there is no redo data available. - if (! m_snapshots.empty() && m_active_snapshot_time > m_snapshots.back().timestamp) - m_active_snapshot_time = m_snapshots.back().timestamp + 1; - else - ++ m_active_snapshot_time; + assert(m_active_snapshot_time <= m_current_time); for (auto &kvp : m_objects) - kvp.second->relese_after_timestamp(m_active_snapshot_time); + kvp.second->release_after_timestamp(m_active_snapshot_time); { auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); m_snapshots.erase(it, m_snapshots.end()); @@ -662,6 +653,9 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo // Save the snapshot info. m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id); m_active_snapshot_time = m_current_time; + // Save snapshot info of the last "current" aka "top most" state, that is only being serialized + // if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet. + m_snapshots.emplace_back(topmost_snapsnot_name, m_active_snapshot_time, 0); // Release empty objects from the history. this->collect_garbage(); assert(this->valid()); @@ -675,7 +669,7 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model) { // Find the snapshot by time. It must exist. const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp)); - if (it_snapshot == m_snapshots.begin() || it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) + if (it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); m_active_snapshot_time = timestamp; @@ -699,18 +693,37 @@ bool StackImpl::has_undo_snapshot() const } bool StackImpl::has_redo_snapshot() const -{ +{ + assert(this->valid()); auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); - return it != m_snapshots.end() && ++ it != m_snapshots.end(); + return ++ it != m_snapshots.end(); } -bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection) +bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load) { assert(this->valid()); - auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); - if (-- it_current == m_snapshots.begin()) - return false; - this->load_snapshot(it_current->timestamp, model); + if (time_to_load == SIZE_MAX) { + auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + if (-- it_current == m_snapshots.begin()) + return false; + time_to_load = it_current->timestamp; + } + assert(time_to_load < m_active_snapshot_time); + assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); + if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { + // The current state is temporary. The current state needs to be captured to be redoable. + this->take_snapshot(topmost_snapsnot_name, model, selection); + // The line above entered another topmost_snapshot_name. + assert(m_snapshots.back().is_topmost()); + assert(! m_snapshots.back().is_topmost_captured()); + // Pop it back, it is not needed as there is now a captured topmost state. + m_snapshots.pop_back(); + // current_time was extended, but it should not cause any harm. Resetting it back may complicate the logic unnecessarily. + //-- m_current_time; + assert(m_snapshots.back().is_topmost()); + assert(m_snapshots.back().is_topmost_captured()); + } + this->load_snapshot(time_to_load, model); #ifdef SLIC3R_UNDOREDO_DEBUG std::cout << "After undo" << std::endl; this->print(); @@ -718,13 +731,18 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti return true; } -bool StackImpl::redo(Slic3r::Model &model) +bool StackImpl::redo(Slic3r::Model &model, size_t time_to_load) { assert(this->valid()); - auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); - if (it_current == m_snapshots.end() || ++ it_current == m_snapshots.end()) - return false; - this->load_snapshot(it_current->timestamp, model); + if (time_to_load == SIZE_MAX) { + auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); + if (++ it_current == m_snapshots.end()) + return false; + time_to_load = it_current->timestamp; + } + assert(time_to_load > m_active_snapshot_time); + assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); + this->load_snapshot(time_to_load, model); #ifdef SLIC3R_UNDOREDO_DEBUG std::cout << "After redo" << std::endl; this->print(); @@ -737,9 +755,9 @@ void StackImpl::collect_garbage() // Purge objects with empty histories. for (auto it = m_objects.begin(); it != m_objects.end();) { if (it->second->empty()) { - if (it->second->is_immutable()) + if (it->second->immutable_object_ptr() != nullptr) // Release the immutable object from the ptr to ObjectID map. - this->m_objects.erase(it->first); + m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr()); it = m_objects.erase(it); } else ++ it; @@ -749,16 +767,15 @@ void StackImpl::collect_garbage() // Wrappers of the private implementation. Stack::Stack() : pimpl(new StackImpl()) {} Stack::~Stack() {} -void Stack::initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->initialize(model, selection); } void Stack::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->take_snapshot(snapshot_name, model, selection); } -void Stack::load_snapshot(size_t timestamp, Slic3r::Model &model) { pimpl->load_snapshot(timestamp, model); } bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); } bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); } -bool Stack::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { return pimpl->undo(model, selection); } -bool Stack::redo(Slic3r::Model &model) { return pimpl->redo(model); } +bool Stack::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load) { return pimpl->undo(model, selection, time_to_load); } +bool Stack::redo(Slic3r::Model &model, size_t time_to_load) { return pimpl->redo(model, time_to_load); } const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); } const std::vector& Stack::snapshots() const { return pimpl->snapshots(); } +size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); } } // namespace UndoRedo } // namespace Slic3r diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index b08e410ff4..178e5a11a0 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -29,6 +29,12 @@ struct Snapshot bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; } bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; } + + // The topmost snapshot represents the current state when going forward. + bool is_topmost() const; + // The topmost snapshot is not being serialized to the Undo / Redo stack until going back in time, + // when the top most state is being serialized, so we can redo back to the top most state. + bool is_topmost_captured() const { assert(this->is_topmost()); return model_id > 0; } }; // Excerpt of Slic3r::GUI::Selection for serialization onto the Undo / Redo stack. @@ -44,25 +50,33 @@ class Stack { public: // Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning. + // The first "New Project" snapshot shall not be removed. Stack(); ~Stack(); - // The Undo / Redo stack is being initialized with an empty model and an empty selection. - // The first snapshot cannot be removed. - void initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); - // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); - void load_snapshot(size_t timestamp, Slic3r::Model &model); + // To be queried to enable / disable the Undo / Redo buttons at the UI. bool has_undo_snapshot() const; bool has_redo_snapshot() const; - // Undoing an action may need to take a snapshot of the current application state. - bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection); - bool redo(Slic3r::Model &model); + + // Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated. + // Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible. + bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load = SIZE_MAX); + + // Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated. + bool redo(Slic3r::Model &model, size_t time_to_load = SIZE_MAX); // Snapshot history (names with timestamps). + // Each snapshot indicates start of an interval in which this operation is performed. + // There is one additional snapshot taken at the very end, which indicates the current unnamed state. + const std::vector& snapshots() const; + // Timestamp of the active snapshot. One of the snapshots of this->snapshots() shall have Snapshot::timestamp equal to this->active_snapshot_time(). + // The snapshot time indicates start of an operation, which is finished at the time of the following snapshot, therefore + // the active snapshot is the successive snapshot. The same logic applies to the time_to_load parameter of undo() and redo() operations. + size_t active_snapshot_time() const; // After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted // into the list of GLVolume pointers once the 3D scene is updated. From 270fec84d330a931a523081a460369d9871dbd2b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 5 Jul 2019 19:46:48 +0200 Subject: [PATCH 280/627] Fix of the Undo / Redo for Cut. Added some more operations (for example Rotation) to the Undo / Redo. --- src/admesh/connect.cpp | 2 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 2 ++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 ++ src/slic3r/GUI/Plater.cpp | 5 +++-- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index e729c89229..b86ec50555 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -132,7 +132,7 @@ struct HashTableEdges { ~HashTableEdges() { #ifndef NDEBUG for (int i = 0; i < this->M; ++ i) - for (HashEdge *temp = this->heads[i]; this->heads[i] != this->tail; temp = this->heads[i]) + for (HashEdge *temp = this->heads[i]; temp != this->tail; temp = temp->next) ++ this->freed; this->tail = nullptr; #endif /* NDEBUG */ diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 79e45facd9..ec5272a44e 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -302,6 +302,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. + wxGetApp().plater()->take_snapshot(_(L("Set Rotation"))); canvas->do_rotate(); UpdateAndShow(true); @@ -687,6 +688,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value) selection.rotate( (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), transformation_type); + wxGetApp().plater()->take_snapshot(_(L("Set Orientation"))); canvas->do_rotate(); m_cache.rotation = rotation; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index c8900d8da0..438cd1a103 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -600,6 +600,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) if (m_current == Flatten) { // Rotate the object so the normal points downward: + wxGetApp().plater()->take_snapshot(_(L("Place on Face"))); selection.flattening_rotate(get_flattening_normal()); canvas.do_flatten(); wxGetApp().obj_manipul()->set_dirty(); @@ -685,6 +686,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) } case Rotate: { + wxGetApp().plater()->take_snapshot(_(L("Rotate Object"))); canvas.do_rotate(); break; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bb394ffcfb..aa7a54286e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2389,7 +2389,6 @@ void Plater::priv::deselect_all() void Plater::priv::remove(size_t obj_idx) { - this->take_snapshot(_(L("Remove Object"))); // Prevent toolpaths preview from rendering while we modify the Print object preview->set_enabled(false); @@ -2406,7 +2405,7 @@ void Plater::priv::remove(size_t obj_idx) void Plater::priv::delete_object_from_model(size_t obj_idx) { -// this->take_snapshot(_(L("Delete Object"))); // ys_FIXME What is the difference with "Remove Object"? + this->take_snapshot(_(L("Delete Object"))); model.delete_object(obj_idx); update(); object_list_changed(); @@ -2850,6 +2849,8 @@ void Plater::priv::update_sla_scene() void Plater::priv::reload_from_disk() { + this->take_snapshot(_(L("Reload from Disk"))); + const auto &selection = get_selection(); const auto obj_orig_idx = selection.get_object_idx(); if (selection.is_wipe_tower() || obj_orig_idx == -1) { return; } From 45a5487e51e7ee451cba7140524a0d4a659c559c Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 5 Jul 2019 20:09:30 +0200 Subject: [PATCH 281/627] Fix of compilation on clang --- src/slic3r/Utils/UndoRedo.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 178e5a11a0..f8bfda08cf 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include From fb725502b7a6f7fd645d83ab640e22e0581d413d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 5 Jul 2019 20:27:44 +0200 Subject: [PATCH 282/627] Undo / Redo: Bound Ctrl-V/Ctrl-Z to the side panel. --- src/slic3r/GUI/GUI_ObjectList.cpp | 26 ++++++++++++++++++++++---- src/slic3r/GUI/GUI_ObjectList.hpp | 2 ++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 582fe10076..90c3f2665d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -143,20 +143,24 @@ ObjectList::ObjectList(wxWindow* parent) : // Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this); { // Accelerators - wxAcceleratorEntry entries[6]; + wxAcceleratorEntry entries[8]; entries[0].Set(wxACCEL_CTRL, (int) 'C', wxID_COPY); entries[1].Set(wxACCEL_CTRL, (int) 'X', wxID_CUT); entries[2].Set(wxACCEL_CTRL, (int) 'V', wxID_PASTE); entries[3].Set(wxACCEL_CTRL, (int) 'A', wxID_SELECTALL); - entries[4].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE); - entries[5].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE); - wxAcceleratorTable accel(6, entries); + entries[4].Set(wxACCEL_CTRL, (int) 'Z', wxID_UNDO); + entries[5].Set(wxACCEL_CTRL, (int) 'Y', wxID_REDO); + entries[6].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE); + entries[7].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE); + wxAcceleratorTable accel(8, entries); SetAcceleratorTable(accel); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy(); }, wxID_COPY); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->paste(); }, wxID_PASTE); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->select_item_all_children(); }, wxID_SELECTALL); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->remove(); }, wxID_DELETE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->undo(); }, wxID_UNDO); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo(); }, wxID_REDO); } #else __WXOSX__ Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX @@ -809,6 +813,16 @@ void ObjectList::paste() wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); } +void ObjectList::undo() +{ + wxGetApp().plater()->undo(); +} + +void ObjectList::redo() +{ + wxGetApp().plater()->redo(); +} + #ifndef __WXOSX__ void ObjectList::key_event(wxKeyEvent& event) { @@ -827,6 +841,10 @@ void ObjectList::key_event(wxKeyEvent& event) copy(); else if (wxGetKeyState(wxKeyCode('V')) && wxGetKeyState(WXK_CONTROL)) paste(); + else if (wxGetKeyState(wxKeyCode('Y')) && wxGetKeyState(WXK_CONTROL)) + redo(); + else if (wxGetKeyState(wxKeyCode('Z')) && wxGetKeyState(WXK_CONTROL)) + undo(); else event.Skip(); } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 2a92ecbe43..a5a9c21388 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -204,6 +204,8 @@ public: void copy(); void paste(); + void undo(); + void redo(); void get_settings_choice(const wxString& category_name); void get_freq_settings_choice(const wxString& bundle_name); From 25d916f144a868c28dc9c433b281d53f84afdd6b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 8 Jul 2019 08:40:20 +0200 Subject: [PATCH 283/627] Color change time estimates --- src/libslic3r/GCode.cpp | 4 + src/libslic3r/GCodeTimeEstimator.cpp | 323 ++++++++++++++++----------- src/libslic3r/GCodeTimeEstimator.hpp | 35 ++- src/libslic3r/Print.hpp | 4 + src/slic3r/GUI/Plater.cpp | 10 + 5 files changed, 233 insertions(+), 143 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c03431a308..d2e5814935 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1057,6 +1057,10 @@ void GCode::_do_export(Print &print, FILE *file) print.m_print_statistics.clear(); print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A"; + print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(); + if (m_silent_time_estimator_enabled) + print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(); + std::vector extruders = m_writer.extruders(); if (! extruders.empty()) { std::pair out_filament_used_mm ("; filament used [mm] = ", 0); diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index b87305da87..33dc9f4b7d 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -174,7 +174,7 @@ namespace Slic3r { const std::string GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag = "; SILENT_LAST_M73_OUTPUT_PLACEHOLDER"; GCodeTimeEstimator::GCodeTimeEstimator(EMode mode) - : _mode(mode) + : m_mode(mode) { reset(); set_default(); @@ -183,7 +183,7 @@ namespace Slic3r { void GCodeTimeEstimator::add_gcode_line(const std::string& gcode_line) { PROFILE_FUNC(); - _parser.parse_line(gcode_line, + m_parser.parse_line(gcode_line, [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }); } @@ -196,7 +196,7 @@ namespace Slic3r { { this->_process_gcode_line(reader, line); }; for (; *ptr != 0;) { gline.reset(); - ptr = _parser.parse_line(ptr, gline, action); + ptr = m_parser.parse_line(ptr, gline, action); } } @@ -206,10 +206,13 @@ namespace Slic3r { if (start_from_beginning) { _reset_time(); - _last_st_synchronized_block_id = -1; + m_last_st_synchronized_block_id = -1; } _calculate_time(); + if (m_needs_color_times && (m_color_time_cache != 0.0f)) + m_color_times.push_back(m_color_time_cache); + #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS @@ -219,12 +222,15 @@ namespace Slic3r { { reset(); - _parser.parse_buffer(gcode, + m_parser.parse_buffer(gcode, [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }); _calculate_time(); + if (m_needs_color_times && (m_color_time_cache != 0.0f)) + m_color_times.push_back(m_color_time_cache); + #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS @@ -234,9 +240,12 @@ namespace Slic3r { { reset(); - _parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); + m_parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); _calculate_time(); + if (m_needs_color_times && (m_color_time_cache != 0.0f)) + m_color_times.push_back(m_color_time_cache); + #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS @@ -249,9 +258,12 @@ namespace Slic3r { auto action = [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) { this->_process_gcode_line(reader, line); }; for (const std::string& line : gcode_lines) - _parser.parse_line(line, action); + m_parser.parse_line(line, action); _calculate_time(); + if (m_needs_color_times && (m_color_time_cache != 0.0f)) + m_color_times.push_back(m_color_time_cache); + #if ENABLE_MOVE_STATS _log_moves_stats(); #endif // ENABLE_MOVE_STATS @@ -270,7 +282,7 @@ namespace Slic3r { throw std::runtime_error(std::string("Remaining times export failed.\nCannot open file for writing.\n")); std::string time_mask; - switch (_mode) + switch (m_mode) { default: case Normal: @@ -291,7 +303,7 @@ namespace Slic3r { // buffer line to export only when greater than 64K to reduce writing calls std::string export_line; char time_line[64]; - G1LineIdToBlockIdMap::const_iterator it_line_id = _g1_line_ids.begin(); + G1LineIdToBlockIdMap::const_iterator it_line_id = m_g1_line_ids.begin(); while (std::getline(in, gcode_line)) { if (!in.good()) @@ -301,15 +313,15 @@ namespace Slic3r { } // replaces placeholders for initial line M73 with the real lines - if (((_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || - ((_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) + if (((m_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || + ((m_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) { - sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(_time).c_str()); + sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(m_time).c_str()); gcode_line = time_line; } // replaces placeholders for final line M73 with the real lines - else if (((_mode == Normal) && (gcode_line == Normal_Last_M73_Output_Placeholder_Tag)) || - ((_mode == Silent) && (gcode_line == Silent_Last_M73_Output_Placeholder_Tag))) + else if (((m_mode == Normal) && (gcode_line == Normal_Last_M73_Output_Placeholder_Tag)) || + ((m_mode == Silent) && (gcode_line == Silent_Last_M73_Output_Placeholder_Tag))) { sprintf(time_line, time_mask.c_str(), "100", "0"); gcode_line = time_line; @@ -319,27 +331,27 @@ namespace Slic3r { // add remaining time lines where needed - _parser.parse_line(gcode_line, + m_parser.parse_line(gcode_line, [this, &it_line_id, &g1_lines_count, &last_recorded_time, &time_line, &gcode_line, time_mask, interval](GCodeReader& reader, const GCodeReader::GCodeLine& line) { if (line.cmd_is("G1")) { ++g1_lines_count; - assert(it_line_id == _g1_line_ids.end() || it_line_id->first >= g1_lines_count); + assert(it_line_id == m_g1_line_ids.end() || it_line_id->first >= g1_lines_count); const Block *block = nullptr; - if (it_line_id != _g1_line_ids.end() && it_line_id->first == g1_lines_count) { - if (line.has_e() && it_line_id->second < (unsigned int)_blocks.size()) - block = &_blocks[it_line_id->second]; + if (it_line_id != m_g1_line_ids.end() && it_line_id->first == g1_lines_count) { + if (line.has_e() && it_line_id->second < (unsigned int)m_blocks.size()) + block = &m_blocks[it_line_id->second]; ++it_line_id; } if (block != nullptr && block->elapsed_time != -1.0f) { - float block_remaining_time = _time - block->elapsed_time; + float block_remaining_time = m_time - block->elapsed_time; if (std::abs(last_recorded_time - block_remaining_time) > interval) { - sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / _time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); + sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / m_time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); gcode_line += time_line; last_recorded_time = block_remaining_time; @@ -387,240 +399,240 @@ namespace Slic3r { void GCodeTimeEstimator::set_axis_position(EAxis axis, float position) { - _state.axis[axis].position = position; + m_state.axis[axis].position = position; } void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec) { - _state.axis[axis].max_feedrate = feedrate_mm_sec; + m_state.axis[axis].max_feedrate = feedrate_mm_sec; } void GCodeTimeEstimator::set_axis_max_acceleration(EAxis axis, float acceleration) { - _state.axis[axis].max_acceleration = acceleration; + m_state.axis[axis].max_acceleration = acceleration; } void GCodeTimeEstimator::set_axis_max_jerk(EAxis axis, float jerk) { - _state.axis[axis].max_jerk = jerk; + m_state.axis[axis].max_jerk = jerk; } float GCodeTimeEstimator::get_axis_position(EAxis axis) const { - return _state.axis[axis].position; + return m_state.axis[axis].position; } float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const { - return _state.axis[axis].max_feedrate; + return m_state.axis[axis].max_feedrate; } float GCodeTimeEstimator::get_axis_max_acceleration(EAxis axis) const { - return _state.axis[axis].max_acceleration; + return m_state.axis[axis].max_acceleration; } float GCodeTimeEstimator::get_axis_max_jerk(EAxis axis) const { - return _state.axis[axis].max_jerk; + return m_state.axis[axis].max_jerk; } void GCodeTimeEstimator::set_feedrate(float feedrate_mm_sec) { - _state.feedrate = feedrate_mm_sec; + m_state.feedrate = feedrate_mm_sec; } float GCodeTimeEstimator::get_feedrate() const { - return _state.feedrate; + return m_state.feedrate; } void GCodeTimeEstimator::set_acceleration(float acceleration_mm_sec2) { - _state.acceleration = (_state.max_acceleration == 0) ? + m_state.acceleration = (m_state.max_acceleration == 0) ? acceleration_mm_sec2 : // Clamp the acceleration with the maximum. - std::min(_state.max_acceleration, acceleration_mm_sec2); + std::min(m_state.max_acceleration, acceleration_mm_sec2); } float GCodeTimeEstimator::get_acceleration() const { - return _state.acceleration; + return m_state.acceleration; } void GCodeTimeEstimator::set_max_acceleration(float acceleration_mm_sec2) { - _state.max_acceleration = acceleration_mm_sec2; + m_state.max_acceleration = acceleration_mm_sec2; if (acceleration_mm_sec2 > 0) - _state.acceleration = acceleration_mm_sec2; + m_state.acceleration = acceleration_mm_sec2; } float GCodeTimeEstimator::get_max_acceleration() const { - return _state.max_acceleration; + return m_state.max_acceleration; } void GCodeTimeEstimator::set_retract_acceleration(float acceleration_mm_sec2) { - _state.retract_acceleration = acceleration_mm_sec2; + m_state.retract_acceleration = acceleration_mm_sec2; } float GCodeTimeEstimator::get_retract_acceleration() const { - return _state.retract_acceleration; + return m_state.retract_acceleration; } void GCodeTimeEstimator::set_minimum_feedrate(float feedrate_mm_sec) { - _state.minimum_feedrate = feedrate_mm_sec; + m_state.minimum_feedrate = feedrate_mm_sec; } float GCodeTimeEstimator::get_minimum_feedrate() const { - return _state.minimum_feedrate; + return m_state.minimum_feedrate; } void GCodeTimeEstimator::set_minimum_travel_feedrate(float feedrate_mm_sec) { - _state.minimum_travel_feedrate = feedrate_mm_sec; + m_state.minimum_travel_feedrate = feedrate_mm_sec; } float GCodeTimeEstimator::get_minimum_travel_feedrate() const { - return _state.minimum_travel_feedrate; + return m_state.minimum_travel_feedrate; } void GCodeTimeEstimator::set_filament_load_times(const std::vector &filament_load_times) { - _state.filament_load_times.clear(); + m_state.filament_load_times.clear(); for (double t : filament_load_times) - _state.filament_load_times.push_back((float)t); + m_state.filament_load_times.push_back((float)t); } void GCodeTimeEstimator::set_filament_unload_times(const std::vector &filament_unload_times) { - _state.filament_unload_times.clear(); + m_state.filament_unload_times.clear(); for (double t : filament_unload_times) - _state.filament_unload_times.push_back((float)t); + m_state.filament_unload_times.push_back((float)t); } float GCodeTimeEstimator::get_filament_load_time(unsigned int id_extruder) { return - (_state.filament_load_times.empty() || id_extruder == _state.extruder_id_unloaded) ? + (m_state.filament_load_times.empty() || id_extruder == m_state.extruder_id_unloaded) ? 0 : - (_state.filament_load_times.size() <= id_extruder) ? - _state.filament_load_times.front() : - _state.filament_load_times[id_extruder]; + (m_state.filament_load_times.size() <= id_extruder) ? + m_state.filament_load_times.front() : + m_state.filament_load_times[id_extruder]; } float GCodeTimeEstimator::get_filament_unload_time(unsigned int id_extruder) { return - (_state.filament_unload_times.empty() || id_extruder == _state.extruder_id_unloaded) ? + (m_state.filament_unload_times.empty() || id_extruder == m_state.extruder_id_unloaded) ? 0 : - (_state.filament_unload_times.size() <= id_extruder) ? - _state.filament_unload_times.front() : - _state.filament_unload_times[id_extruder]; + (m_state.filament_unload_times.size() <= id_extruder) ? + m_state.filament_unload_times.front() : + m_state.filament_unload_times[id_extruder]; } void GCodeTimeEstimator::set_extrude_factor_override_percentage(float percentage) { - _state.extrude_factor_override_percentage = percentage; + m_state.extrude_factor_override_percentage = percentage; } float GCodeTimeEstimator::get_extrude_factor_override_percentage() const { - return _state.extrude_factor_override_percentage; + return m_state.extrude_factor_override_percentage; } void GCodeTimeEstimator::set_dialect(GCodeFlavor dialect) { - _state.dialect = dialect; + m_state.dialect = dialect; } GCodeFlavor GCodeTimeEstimator::get_dialect() const { PROFILE_FUNC(); - return _state.dialect; + return m_state.dialect; } void GCodeTimeEstimator::set_units(GCodeTimeEstimator::EUnits units) { - _state.units = units; + m_state.units = units; } GCodeTimeEstimator::EUnits GCodeTimeEstimator::get_units() const { - return _state.units; + return m_state.units; } void GCodeTimeEstimator::set_global_positioning_type(GCodeTimeEstimator::EPositioningType type) { - _state.global_positioning_type = type; + m_state.global_positioning_type = type; } GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_global_positioning_type() const { - return _state.global_positioning_type; + return m_state.global_positioning_type; } void GCodeTimeEstimator::set_e_local_positioning_type(GCodeTimeEstimator::EPositioningType type) { - _state.e_local_positioning_type = type; + m_state.e_local_positioning_type = type; } GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_e_local_positioning_type() const { - return _state.e_local_positioning_type; + return m_state.e_local_positioning_type; } int GCodeTimeEstimator::get_g1_line_id() const { - return _state.g1_line_id; + return m_state.g1_line_id; } void GCodeTimeEstimator::increment_g1_line_id() { - ++_state.g1_line_id; + ++m_state.g1_line_id; } void GCodeTimeEstimator::reset_g1_line_id() { - _state.g1_line_id = 0; + m_state.g1_line_id = 0; } void GCodeTimeEstimator::set_extruder_id(unsigned int id) { - _state.extruder_id = id; + m_state.extruder_id = id; } unsigned int GCodeTimeEstimator::get_extruder_id() const { - return _state.extruder_id; + return m_state.extruder_id; } void GCodeTimeEstimator::reset_extruder_id() { // Set the initial extruder ID to unknown. For the multi-material setup it means // that all the filaments are parked in the MMU and no filament is loaded yet. - _state.extruder_id = _state.extruder_id_unloaded; + m_state.extruder_id = m_state.extruder_id_unloaded; } void GCodeTimeEstimator::add_additional_time(float timeSec) { PROFILE_FUNC(); - _state.additional_time += timeSec; + m_state.additional_time += timeSec; } void GCodeTimeEstimator::set_additional_time(float timeSec) { - _state.additional_time = timeSec; + m_state.additional_time = timeSec; } float GCodeTimeEstimator::get_additional_time() const { - return _state.additional_time; + return m_state.additional_time; } void GCodeTimeEstimator::set_default() @@ -648,8 +660,8 @@ namespace Slic3r { set_axis_max_jerk(axis, DEFAULT_AXIS_MAX_JERK[a]); } - _state.filament_load_times.clear(); - _state.filament_unload_times.clear(); + m_state.filament_load_times.clear(); + m_state.filament_unload_times.clear(); } void GCodeTimeEstimator::reset() @@ -664,7 +676,7 @@ namespace Slic3r { float GCodeTimeEstimator::get_time() const { - return _time; + return m_time; } std::string GCodeTimeEstimator::get_time_dhms() const @@ -677,19 +689,44 @@ namespace Slic3r { return _get_time_minutes(get_time()); } + std::vector GCodeTimeEstimator::get_color_times() const + { + return m_color_times; + } + + std::vector GCodeTimeEstimator::get_color_times_dhms() const + { + std::vector ret; + for (float t : m_color_times) + { + ret.push_back(_get_time_dhms(t)); + } + return ret; + } + + std::vector GCodeTimeEstimator::get_color_times_minutes() const + { + std::vector ret; + for (float t : m_color_times) + { + ret.push_back(_get_time_minutes(t)); + } + return ret; + } + // Return an estimate of the memory consumed by the time estimator. size_t GCodeTimeEstimator::memory_used() const { size_t out = sizeof(*this); - out += SLIC3R_STDVEC_MEMSIZE(this->_blocks, Block); - out += SLIC3R_STDVEC_MEMSIZE(this->_g1_line_ids, G1LineIdToBlockId); + out += SLIC3R_STDVEC_MEMSIZE(this->m_blocks, Block); + out += SLIC3R_STDVEC_MEMSIZE(this->m_g1_line_ids, G1LineIdToBlockId); return out; } void GCodeTimeEstimator::_reset() { - _curr.reset(); - _prev.reset(); + m_curr.reset(); + m_prev.reset(); set_axis_position(X, 0.0f); set_axis_position(Y, 0.0f); @@ -701,19 +738,23 @@ namespace Slic3r { reset_extruder_id(); reset_g1_line_id(); - _g1_line_ids.clear(); + m_g1_line_ids.clear(); - _last_st_synchronized_block_id = -1; + m_last_st_synchronized_block_id = -1; + + m_needs_color_times = false; + m_color_times.clear(); + m_color_time_cache = 0.0f; } void GCodeTimeEstimator::_reset_time() { - _time = 0.0f; + m_time = 0.0f; } void GCodeTimeEstimator::_reset_blocks() { - _blocks.clear(); + m_blocks.clear(); } void GCodeTimeEstimator::_calculate_time() @@ -723,35 +764,32 @@ namespace Slic3r { _reverse_pass(); _recalculate_trapezoids(); - _time += get_additional_time(); + m_time += get_additional_time(); + m_color_time_cache += get_additional_time(); - for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) + for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size(); ++i) { - Block& block = _blocks[i]; - -#if ENABLE_MOVE_STATS + Block& block = m_blocks[i]; float block_time = 0.0f; block_time += block.acceleration_time(); block_time += block.cruise_time(); block_time += block.deceleration_time(); - _time += block_time; - block.elapsed_time = _time; + m_time += block_time; + block.elapsed_time = m_time; +#if ENABLE_MOVE_STATS MovesStatsMap::iterator it = _moves_stats.find(block.move_type); if (it == _moves_stats.end()) it = _moves_stats.insert(MovesStatsMap::value_type(block.move_type, MoveStats())).first; it->second.count += 1; it->second.time += block_time; -#else - _time += block.acceleration_time(); - _time += block.cruise_time(); - _time += block.deceleration_time(); - block.elapsed_time = _time; #endif // ENABLE_MOVE_STATS + + m_color_time_cache += block_time; } - _last_st_synchronized_block_id = (int)_blocks.size() - 1; + m_last_st_synchronized_block_id = (int)m_blocks.size() - 1; // The additional time has been consumed (added to the total time), reset it to zero. set_additional_time(0.); } @@ -866,6 +904,11 @@ namespace Slic3r { _processM566(line); break; } + case 600: // Set color change + { + _processM600(line); + break; + } case 702: // MK3 MMU2: Process the final filament unload. { _processM702(line); @@ -934,7 +977,7 @@ namespace Slic3r { return; // calculates block feedrate - _curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate()); + m_curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate()); float distance = block.move_length(); float invDistance = 1.0f / distance; @@ -942,23 +985,23 @@ namespace Slic3r { float min_feedrate_factor = 1.0f; for (unsigned char a = X; a < Num_Axis; ++a) { - _curr.axis_feedrate[a] = _curr.feedrate * block.delta_pos[a] * invDistance; + m_curr.axis_feedrate[a] = m_curr.feedrate * block.delta_pos[a] * invDistance; if (a == E) - _curr.axis_feedrate[a] *= get_extrude_factor_override_percentage(); + m_curr.axis_feedrate[a] *= get_extrude_factor_override_percentage(); - _curr.abs_axis_feedrate[a] = std::abs(_curr.axis_feedrate[a]); - if (_curr.abs_axis_feedrate[a] > 0.0f) - min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / _curr.abs_axis_feedrate[a]); + m_curr.abs_axis_feedrate[a] = std::abs(m_curr.axis_feedrate[a]); + if (m_curr.abs_axis_feedrate[a] > 0.0f) + min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / m_curr.abs_axis_feedrate[a]); } - block.feedrate.cruise = min_feedrate_factor * _curr.feedrate; + block.feedrate.cruise = min_feedrate_factor * m_curr.feedrate; if (min_feedrate_factor < 1.0f) { for (unsigned char a = X; a < Num_Axis; ++a) { - _curr.axis_feedrate[a] *= min_feedrate_factor; - _curr.abs_axis_feedrate[a] *= min_feedrate_factor; + m_curr.axis_feedrate[a] *= min_feedrate_factor; + m_curr.abs_axis_feedrate[a] *= min_feedrate_factor; } } @@ -975,25 +1018,25 @@ namespace Slic3r { block.acceleration = acceleration; // calculates block exit feedrate - _curr.safe_feedrate = block.feedrate.cruise; + m_curr.safe_feedrate = block.feedrate.cruise; for (unsigned char a = X; a < Num_Axis; ++a) { float axis_max_jerk = get_axis_max_jerk((EAxis)a); - if (_curr.abs_axis_feedrate[a] > axis_max_jerk) - _curr.safe_feedrate = std::min(_curr.safe_feedrate, axis_max_jerk); + if (m_curr.abs_axis_feedrate[a] > axis_max_jerk) + m_curr.safe_feedrate = std::min(m_curr.safe_feedrate, axis_max_jerk); } - block.feedrate.exit = _curr.safe_feedrate; + block.feedrate.exit = m_curr.safe_feedrate; // calculates block entry feedrate - float vmax_junction = _curr.safe_feedrate; - if (!_blocks.empty() && (_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) + float vmax_junction = m_curr.safe_feedrate; + if (!m_blocks.empty() && (m_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) { - bool prev_speed_larger = _prev.feedrate > block.feedrate.cruise; - float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / _prev.feedrate) : (_prev.feedrate / block.feedrate.cruise); + bool prev_speed_larger = m_prev.feedrate > block.feedrate.cruise; + float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / m_prev.feedrate) : (m_prev.feedrate / block.feedrate.cruise); // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting. - vmax_junction = prev_speed_larger ? block.feedrate.cruise : _prev.feedrate; + vmax_junction = prev_speed_larger ? block.feedrate.cruise : m_prev.feedrate; float v_factor = 1.0f; bool limited = false; @@ -1001,8 +1044,8 @@ namespace Slic3r { for (unsigned char a = X; a < Num_Axis; ++a) { // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop. - float v_exit = _prev.axis_feedrate[a]; - float v_entry = _curr.axis_feedrate[a]; + float v_exit = m_prev.axis_feedrate[a]; + float v_entry = m_curr.axis_feedrate[a]; if (prev_speed_larger) v_exit *= smaller_speed_factor; @@ -1044,23 +1087,23 @@ namespace Slic3r { float vmax_junction_threshold = vmax_junction * 0.99f; // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start. - if ((_prev.safe_feedrate > vmax_junction_threshold) && (_curr.safe_feedrate > vmax_junction_threshold)) - vmax_junction = _curr.safe_feedrate; + if ((m_prev.safe_feedrate > vmax_junction_threshold) && (m_curr.safe_feedrate > vmax_junction_threshold)) + vmax_junction = m_curr.safe_feedrate; } - float v_allowable = Block::max_allowable_speed(-acceleration, _curr.safe_feedrate, distance); + float v_allowable = Block::max_allowable_speed(-acceleration, m_curr.safe_feedrate, distance); block.feedrate.entry = std::min(vmax_junction, v_allowable); block.max_entry_speed = vmax_junction; block.flags.nominal_length = (block.feedrate.cruise <= v_allowable); block.flags.recalculate = true; - block.safe_feedrate = _curr.safe_feedrate; + block.safe_feedrate = m_curr.safe_feedrate; // calculates block trapezoid block.calculate_trapezoid(); // updates previous - _prev = _curr; + m_prev = m_curr; // updates axis positions for (unsigned char a = X; a < Num_Axis; ++a) @@ -1091,8 +1134,8 @@ namespace Slic3r { #endif // ENABLE_MOVE_STATS // adds block to blocks list - _blocks.emplace_back(block); - _g1_line_ids.emplace_back(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)_blocks.size() - 1)); + m_blocks.emplace_back(block); + m_g1_line_ids.emplace_back(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)m_blocks.size() - 1)); } void GCodeTimeEstimator::_processG4(const GCodeReader::GCodeLine& line) @@ -1336,6 +1379,18 @@ namespace Slic3r { set_axis_max_jerk(E, line.e() * MMMIN_TO_MMSEC); } + void GCodeTimeEstimator::_processM600(const GCodeReader::GCodeLine& line) + { + PROFILE_FUNC(); + m_needs_color_times = true; + _calculate_time(); + if (m_color_time_cache != 0.0f) + { + m_color_times.push_back(m_color_time_cache); + m_color_time_cache = 0.0f; + } + } + void GCodeTimeEstimator::_processM702(const GCodeReader::GCodeLine& line) { PROFILE_FUNC(); @@ -1376,11 +1431,11 @@ namespace Slic3r { void GCodeTimeEstimator::_forward_pass() { PROFILE_FUNC(); - if (_blocks.size() > 1) + if (m_blocks.size() > 1) { - for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size() - 1; ++i) + for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size() - 1; ++i) { - _planner_forward_pass_kernel(_blocks[i], _blocks[i + 1]); + _planner_forward_pass_kernel(m_blocks[i], m_blocks[i + 1]); } } } @@ -1388,11 +1443,11 @@ namespace Slic3r { void GCodeTimeEstimator::_reverse_pass() { PROFILE_FUNC(); - if (_blocks.size() > 1) + if (m_blocks.size() > 1) { - for (int i = (int)_blocks.size() - 1; i >= _last_st_synchronized_block_id + 2; --i) + for (int i = (int)m_blocks.size() - 1; i >= m_last_st_synchronized_block_id + 2; --i) { - _planner_reverse_pass_kernel(_blocks[i - 1], _blocks[i]); + _planner_reverse_pass_kernel(m_blocks[i - 1], m_blocks[i]); } } } @@ -1444,9 +1499,9 @@ namespace Slic3r { Block* curr = nullptr; Block* next = nullptr; - for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) + for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size(); ++i) { - Block& b = _blocks[i]; + Block& b = m_blocks[i]; curr = next; next = &b; @@ -1517,7 +1572,7 @@ namespace Slic3r { { std::cout << MOVE_TYPE_STR[move.first]; std::cout << ": count " << move.second.count << " (" << 100.0f * (float)move.second.count / moves_count << "%)"; - std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / _time << "%)"; + std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / m_time << "%)"; std::cout << std::endl; } std::cout << std::endl; diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index 1fbc1c14bf..840d587784 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -215,17 +215,22 @@ namespace Slic3r { typedef std::vector G1LineIdToBlockIdMap; private: - EMode _mode; - GCodeReader _parser; - State _state; - Feedrates _curr; - Feedrates _prev; - BlocksList _blocks; + EMode m_mode; + GCodeReader m_parser; + State m_state; + Feedrates m_curr; + Feedrates m_prev; + BlocksList m_blocks; // Map between g1 line id and blocks id, used to speed up export of remaining times - G1LineIdToBlockIdMap _g1_line_ids; + G1LineIdToBlockIdMap m_g1_line_ids; // Index of the last block already st_synchronized - int _last_st_synchronized_block_id; - float _time; // s + int m_last_st_synchronized_block_id; + float m_time; // s + + // data to calculate color print times + bool m_needs_color_times; + std::vector m_color_times; + float m_color_time_cache; #if ENABLE_MOVE_STATS MovesStatsMap _moves_stats; @@ -341,6 +346,15 @@ namespace Slic3r { // Returns the estimated time, in minutes (integer) std::string get_time_minutes() const; + // Returns the estimated time, in seconds, for each color + std::vector get_color_times() const; + + // Returns the estimated time, in format DDd HHh MMm SSs, for each color + std::vector get_color_times_dhms() const; + + // Returns the estimated time, in minutes (integer), for each color + std::vector get_color_times_minutes() const; + // Return an estimate of the memory consumed by the time estimator. size_t memory_used() const; @@ -409,6 +423,9 @@ namespace Slic3r { // Set allowable instantaneous speed change void _processM566(const GCodeReader::GCodeLine& line); + // Set color change + void _processM600(const GCodeReader::GCodeLine& line); + // Unload the current filament into the MK3 MMU2 unit at the end of print. void _processM702(const GCodeReader::GCodeLine& line); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 53d6d692db..7fe0eb7a3c 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -238,6 +238,8 @@ struct PrintStatistics PrintStatistics() { clear(); } std::string estimated_normal_print_time; std::string estimated_silent_print_time; + std::vector estimated_normal_color_print_times; + std::vector estimated_silent_color_print_times; double total_used_filament; double total_extruded_volume; double total_cost; @@ -256,6 +258,8 @@ struct PrintStatistics void clear() { estimated_normal_print_time.clear(); estimated_silent_print_time.clear(); + estimated_normal_color_print_times.clear(); + estimated_silent_color_print_times.clear(); total_used_filament = 0.; total_extruded_volume = 0.; total_cost = 0.; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 09ab91ef10..217937123a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1116,10 +1116,20 @@ void Sidebar::show_sliced_info_sizer(const bool show) if (ps.estimated_normal_print_time != "N/A") { new_label += wxString::Format("\n - %s", _(L("normal mode"))); info_text += wxString::Format("\n%s", ps.estimated_normal_print_time); + for (unsigned int i = 0; i < (unsigned int)ps.estimated_normal_color_print_times.size(); ++i) + { + new_label += wxString::Format("\n - %s%d", _(L("Color ")), i + 1); + info_text += wxString::Format("\n%s", ps.estimated_normal_color_print_times[i]); + } } if (ps.estimated_silent_print_time != "N/A") { new_label += wxString::Format("\n - %s", _(L("stealth mode"))); info_text += wxString::Format("\n%s", ps.estimated_silent_print_time); + for (unsigned int i = 0; i < (unsigned int)ps.estimated_normal_color_print_times.size(); ++i) + { + new_label += wxString::Format("\n - %s%d", _(L("Color ")), i + 1); + info_text += wxString::Format("\n%s", ps.estimated_normal_color_print_times[i]); + } } p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); } From 7b6229289d144d1f24feb53cda31973a64e34ff8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 8 Jul 2019 10:57:35 +0200 Subject: [PATCH 284/627] Added undo/redo to the "Edit" menu --- src/slic3r/GUI/MainFrame.cpp | 8 ++++++++ src/slic3r/GUI/Plater.cpp | 10 ++++++++++ src/slic3r/GUI/Plater.hpp | 2 ++ 3 files changed, 20 insertions(+) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d800f6f380..67b336a0e0 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -502,6 +502,14 @@ void MainFrame::init_menubar() _(L("Deletes all objects")), [this](wxCommandEvent&) { m_plater->reset_with_confirm(); }, menu_icon("delete_all_menu"), nullptr, [this](){return can_delete_all(); }, this); + editMenu->AppendSeparator(); + append_menu_item(editMenu, wxID_ANY, _(L("&Undo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Z", + _(L("Undo")), [this](wxCommandEvent&) { m_plater->undo(); }, + "undo", nullptr, [this](){return m_plater->can_undo(); }, this); + append_menu_item(editMenu, wxID_ANY, _(L("&Redo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Y", + _(L("Redo")), [this](wxCommandEvent&) { m_plater->redo(); }, + "undo", nullptr, [this](){return m_plater->can_redo(); }, this); + editMenu->AppendSeparator(); append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C", _(L("Copy selection to clipboard")), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index aa7a54286e..6ac58eb841 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4405,6 +4405,16 @@ bool Plater::can_copy_to_clipboard() const return true; } +bool Plater::can_undo() const +{ + return p->undo_redo_stack.has_undo_snapshot(); +} + +bool Plater::can_redo() const +{ + return p->undo_redo_stack.has_redo_snapshot(); +} + SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() : m_was_running(wxGetApp().plater()->is_background_process_running()) { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index d38957b3ae..0be465f53c 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -222,6 +222,8 @@ public: bool can_layers_editing() const; bool can_paste_from_clipboard() const; bool can_copy_to_clipboard() const; + bool can_undo() const; + bool can_redo() const; void msw_rescale(); From 4ba7dfb6ddb6aa92456088184862a8ad1e2c7222 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 8 Jul 2019 15:30:59 +0200 Subject: [PATCH 285/627] Refactoring of functions thick_lines_to_indexed_vertex_array() to reduce the amount of produced vertices while reducing visual artifacts in gcode toolpaths due to averaged normals --- src/slic3r/GUI/3DScene.cpp | 27 ++++++++++++++++++++++----- src/slic3r/GUI/3DScene.hpp | 2 -- src/slic3r/GUI/GLCanvas3D.cpp | 6 ------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index fe0cd9ffbd..c49c7c9cac 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -849,6 +849,7 @@ static void thick_lines_to_indexed_vertex_array( int idx_initial[4] = { -1, -1, -1, -1 }; double width_initial = 0.; double bottom_z_initial = 0.0; + double len_prev = 0.0; // loop once more in case of closed loops size_t lines_end = closed ? (lines.size() + 1) : lines.size(); @@ -864,6 +865,7 @@ static void thick_lines_to_indexed_vertex_array( bool is_closing = closed && is_last; Vec2d v = unscale(line.vector()).normalized(); + double len = unscale(line.length()); Vec2d a = unscale(line.a); Vec2d b = unscale(line.b); @@ -927,7 +929,14 @@ static void thick_lines_to_indexed_vertex_array( // Continuing a previous segment. // Share left / right vertices if possible. double v_dot = v_prev.dot(v); - bool sharp = v_dot < 0.9999; // v_dot < 0.9999; // cos(1 degree) + // To reduce gpu memory usage, we try to reuse vertices + // To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges + // is longer than a fixed threshold. + // The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts + double len_threshold = 2.5; + + // Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met + bool sharp = (v_dot < 0.707) || (len_prev > len_threshold) || (len > len_threshold); if (sharp) { if (!bottom_z_different) { @@ -1003,6 +1012,7 @@ static void thick_lines_to_indexed_vertex_array( bottom_z_prev = bottom_z; b1_prev = b1; v_prev = v; + len_prev = len; if (bottom_z_different && (closed || (!is_first && !is_last))) { @@ -1056,6 +1066,7 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, int idx_initial[4] = { -1, -1, -1, -1 }; int idx_prev[4] = { -1, -1, -1, -1 }; double z_prev = 0.0; + double len_prev = 0.0; Vec3d n_right_prev = Vec3d::Zero(); Vec3d n_top_prev = Vec3d::Zero(); Vec3d unit_v_prev = Vec3d::Zero(); @@ -1077,6 +1088,7 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, double width = widths[i]; Vec3d unit_v = unscale(line.vector()).normalized(); + double len = unscale(line.length()); Vec3d n_top = Vec3d::Zero(); Vec3d n_right = Vec3d::Zero(); @@ -1153,9 +1165,16 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, // Continuing a previous segment. // Share left / right vertices if possible. double v_dot = unit_v_prev.dot(unit_v); - bool is_sharp = v_dot < 0.9999; // v_dot < 0.9999; // cos(1 degree) bool is_right_turn = n_top_prev.dot(unit_v_prev.cross(unit_v)) > 0.0; + // To reduce gpu memory usage, we try to reuse vertices + // To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges + // is longer than a fixed threshold. + // The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts + double len_threshold = 2.5; + + // Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met + bool is_sharp = (v_dot < 0.707) || (len_prev > len_threshold) || (len > len_threshold); if (is_sharp) { // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. @@ -1234,6 +1253,7 @@ static void thick_lines_to_indexed_vertex_array(const Lines3& lines, n_right_prev = n_right; n_top_prev = n_top; unit_v_prev = unit_v; + len_prev = len; if (!closed) { @@ -1495,9 +1515,6 @@ void GLModel::set_scale(const Vec3d& scale) void GLModel::reset() { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// m_volume.release_geometry(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_filename = ""; } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index bb629a1d8b..50821a6caa 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -74,9 +74,7 @@ public: quad_indices_VBO_id(0) {} -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ~GLIndexedVertexArray() { release_geometry(); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs) { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 77c7555382..d15d9aa4d3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1356,9 +1356,6 @@ void GLCanvas3D::reset_volumes() if (!m_volumes.empty()) { m_selection.clear(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// m_volumes.release_geometry(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_volumes.clear(); m_dirty = true; } @@ -1920,9 +1917,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(volume_idx_wipe_tower_old == -1); volume_idx_wipe_tower_old = (int)volume_id; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// volume->release_geometry(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (! m_reload_delayed) delete volume; } else { From fbf14b42e9831b5fc99be555668e126df37f9d58 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 8 Jul 2019 18:01:14 +0200 Subject: [PATCH 286/627] Added undo/redo icons. Fist step to implementation Undo/Redo list for toolbar --- resources/icons/redo.svg | 12 +++++++ resources/icons/redo_toolbar.svg | 12 +++++++ resources/icons/undo_toolbar.svg | 12 +++++++ src/slic3r/GUI/GLCanvas3D.cpp | 58 ++++++++++++++++++++++++++++++++ src/slic3r/GUI/GLToolbar.cpp | 4 +++ src/slic3r/GUI/GLToolbar.hpp | 7 ++++ src/slic3r/GUI/ImGuiWrapper.cpp | 21 ++++++++++++ src/slic3r/GUI/ImGuiWrapper.hpp | 1 + src/slic3r/GUI/MainFrame.cpp | 2 +- 9 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 resources/icons/redo.svg create mode 100644 resources/icons/redo_toolbar.svg create mode 100644 resources/icons/undo_toolbar.svg diff --git a/resources/icons/redo.svg b/resources/icons/redo.svg new file mode 100644 index 0000000000..9109779bbc --- /dev/null +++ b/resources/icons/redo.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/resources/icons/redo_toolbar.svg b/resources/icons/redo_toolbar.svg new file mode 100644 index 0000000000..ad073244f8 --- /dev/null +++ b/resources/icons/redo_toolbar.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/resources/icons/undo_toolbar.svg b/resources/icons/undo_toolbar.svg new file mode 100644 index 0000000000..699ccd807f --- /dev/null +++ b/resources/icons/undo_toolbar.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 1d5cec04ae..970b349cbd 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3618,6 +3618,64 @@ bool GLCanvas3D::_init_toolbar() if (!m_toolbar.add_item(item)) return false; + if (!m_toolbar.add_separator()) + return false; + + item.name = "undo"; +#if ENABLE_SVG_ICONS + item.icon_filename = "undo_toolbar.svg"; +#endif // ENABLE_SVG_ICONS + item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]"; + item.sprite_id = 11; + item.action_callback = [this]() + { + if (m_canvas != nullptr) { + wxPostEvent(m_canvas, SimpleEvent(EVT_GLCANVAS_UNDO)); + m_toolbar.set_imgui_visible(); + } + }; + item.visibility_callback = []()->bool { return true; }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_undo(); }; + item.render_callback = [this]() + { + if (m_canvas != nullptr && m_toolbar.get_imgui_visible()) { + ImGuiWrapper* imgui = wxGetApp().imgui(); + + const float approx_height = m_toolbar.get_height(); + imgui->set_next_window_pos(600, approx_height, ImGuiCond_Always); + + imgui->set_next_window_bg_alpha(0.5f); + imgui->begin(_(L("Undo Stack")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + std::vector undo_stack = {"A", "B", "C", "D","A", "B", "C", "D","A", "B", "C", "D",}; + int sel = 4; + imgui->multi_sel_list("", undo_stack, sel); + + const bool undo_clicked = imgui->button(_(L("Undo N Action"))); + + imgui->end(); + if (undo_clicked) + m_toolbar.set_imgui_visible(false); + } + }; + if (!m_toolbar.add_item(item)) + return false; + + item.name = "redo"; +#if ENABLE_SVG_ICONS + item.icon_filename = "redo_toolbar.svg"; +#endif // ENABLE_SVG_ICONS + item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]"; + item.sprite_id = 12; + item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLCANVAS_REDO)); }; + item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_redo(); }; + item.render_callback = []() {}; + if (!m_toolbar.add_item(item)) + return false; + + if (!m_toolbar.add_separator()) + return false; + return true; } diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index f8082ad7e0..a851e3a4ce 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -35,6 +35,7 @@ wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent); const GLToolbarItem::ActionCallback GLToolbarItem::Default_Action_Callback = [](){}; const GLToolbarItem::VisibilityCallback GLToolbarItem::Default_Visibility_Callback = []()->bool { return true; }; const GLToolbarItem::EnabledStateCallback GLToolbarItem::Default_Enabled_State_Callback = []()->bool { return true; }; +const GLToolbarItem::RenderCallback GLToolbarItem::Default_Render_Callback = [](){}; GLToolbarItem::Data::Data() : name("") @@ -48,6 +49,7 @@ GLToolbarItem::Data::Data() , action_callback(Default_Action_Callback) , visibility_callback(Default_Visibility_Callback) , enabled_state_callback(Default_Enabled_State_Callback) + , render_callback(Default_Render_Callback) { } @@ -81,6 +83,8 @@ bool GLToolbarItem::update_enabled_state() void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const { GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(tex_width, tex_height, icon_size)); + + m_data.render_callback(); } GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 24314d60ff..aa68fae38e 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -37,6 +37,7 @@ public: typedef std::function ActionCallback; typedef std::function VisibilityCallback; typedef std::function EnabledStateCallback; + typedef std::function RenderCallback; enum EType : unsigned char { @@ -68,6 +69,7 @@ public: ActionCallback action_callback; VisibilityCallback visibility_callback; EnabledStateCallback enabled_state_callback; + RenderCallback render_callback; Data(); }; @@ -75,6 +77,7 @@ public: static const ActionCallback Default_Action_Callback; static const VisibilityCallback Default_Visibility_Callback; static const EnabledStateCallback Default_Enabled_State_Callback; + static const RenderCallback Default_Render_Callback; private: EType m_type; @@ -249,6 +252,7 @@ private: MouseCapture m_mouse_capture; std::string m_tooltip; + bool m_imgui_visible {false}; public: #if ENABLE_SVG_ICONS @@ -305,6 +309,9 @@ public: bool on_mouse(wxMouseEvent& evt, GLCanvas3D& parent); + void set_imgui_visible(bool visible = true) { m_imgui_visible = visible; } + bool get_imgui_visible() { return m_imgui_visible; } + private: void calc_layout() const; float get_width_horizontal() const; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index b6abf641ab..cbcf33f77d 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -342,6 +342,27 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector& return res; } +// Getter for the const char*[] +static bool StringGetter(void* data, int i, const char** out_text) +{ + const std::vector* v = (std::vector*)data; + if (out_text) + *out_text = (*v)[i].c_str(); + return true; +} + +bool ImGuiWrapper::multi_sel_list(const wxString& label, const std::vector& options, int& selection) +{ + // this is to force the label to the left of the widget: + if (!label.IsEmpty()) + text(label); + + bool res = false; + ImGui::ListBox("", &selection, StringGetter, (void*)&options, (int)options.size()); + + return res; +} + void ImGuiWrapper::disabled_begin(bool disabled) { wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call"); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 0479e47434..42c562b720 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -67,6 +67,7 @@ public: void text(const std::string &label); void text(const wxString &label); bool combo(const wxString& label, const std::vector& options, int& selection); // Use -1 to not mark any option as selected + bool multi_sel_list(const wxString& label, const std::vector& options, int& selection); // Use -1 to not mark any option as selected void disabled_begin(bool disabled); void disabled_end(); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 67b336a0e0..cca6e02e67 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -508,7 +508,7 @@ void MainFrame::init_menubar() "undo", nullptr, [this](){return m_plater->can_undo(); }, this); append_menu_item(editMenu, wxID_ANY, _(L("&Redo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Y", _(L("Redo")), [this](wxCommandEvent&) { m_plater->redo(); }, - "undo", nullptr, [this](){return m_plater->can_redo(); }, this); + "redo", nullptr, [this](){return m_plater->can_redo(); }, this); editMenu->AppendSeparator(); append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C", From 36049788acc8ae888d4e8df65f89b6708c2455bb Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 9 Jul 2019 08:24:23 +0200 Subject: [PATCH 287/627] Fixed reset of bed 3d model --- src/slic3r/GUI/3DScene.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index c49c7c9cac..9e3eaf41d3 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1515,6 +1515,7 @@ void GLModel::set_scale(const Vec3d& scale) void GLModel::reset() { + m_volume.indexed_vertex_array.release_geometry(); m_filename = ""; } From 386a42b4c5aea6d7beeb55831c706d3680a77eb6 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 9 Jul 2019 10:15:40 +0200 Subject: [PATCH 288/627] Disable dep_libigl in deps. Include dir collisions... --- deps/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index d8e72370b5..8b41c853d6 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -105,7 +105,7 @@ else() dep_gtest dep_nlopt dep_qhull - dep_libigl + # dep_libigl # Not working, static build has different Eigen ) endif() From d4914441f3dc5038c1599fb92561502d260d05ab Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 9 Jul 2019 10:18:57 +0200 Subject: [PATCH 289/627] Modified logic to add snapshots to undo/redo stack using GLCanvas::do_xxxxxx() methods --- src/slic3r/GUI/GLCanvas3D.cpp | 41 ++++++++++++++++++----- src/slic3r/GUI/GLCanvas3D.hpp | 11 +++--- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 14 +++----- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 12 +++---- src/slic3r/GUI/Selection.cpp | 6 ++-- 5 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 1d5cec04ae..7a2e5345de 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1799,7 +1799,7 @@ std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) void GLCanvas3D::mirror_selection(Axis axis) { m_selection.mirror(axis); - do_mirror(); + do_mirror("Mirror Object"); wxGetApp().obj_manipul()->set_dirty(); } @@ -2947,8 +2947,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging) { m_regenerate_volumes = false; - wxGetApp().plater()->take_snapshot(_(L("Move Object"))); - do_move(); + do_move("Move Object"); wxGetApp().obj_manipul()->set_dirty(); // Let the plater know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. @@ -3107,11 +3106,14 @@ void GLCanvas3D::set_tooltip(const std::string& tooltip) const } -void GLCanvas3D::do_move() +void GLCanvas3D::do_move(const std::string& snapshot_type) { if (m_model == nullptr) return; + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(L(snapshot_type))); + std::set> done; // keeps track of modified instances bool object_moved = false; Vec3d wipe_tower_origin = Vec3d::Zero(); @@ -3162,13 +3164,18 @@ void GLCanvas3D::do_move() if (wipe_tower_origin != Vec3d::Zero()) post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_MOVED, std::move(wipe_tower_origin))); + + m_dirty = true; } -void GLCanvas3D::do_rotate() +void GLCanvas3D::do_rotate(const std::string& snapshot_type) { if (m_model == nullptr) return; + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(L(snapshot_type))); + std::set> done; // keeps track of modified instances Selection::EMode selection_mode = m_selection.get_mode(); @@ -3217,13 +3224,18 @@ void GLCanvas3D::do_rotate() if (!done.empty()) post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); + + m_dirty = true; } -void GLCanvas3D::do_scale() +void GLCanvas3D::do_scale(const std::string& snapshot_type) { if (m_model == nullptr) return; + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(L(snapshot_type))); + std::set> done; // keeps track of modified instances Selection::EMode selection_mode = m_selection.get_mode(); @@ -3269,18 +3281,27 @@ void GLCanvas3D::do_scale() if (!done.empty()) post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); + + m_dirty = true; } -void GLCanvas3D::do_flatten() +void GLCanvas3D::do_flatten(const Vec3d& normal, const std::string& snapshot_type) { - do_rotate(); + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(L(snapshot_type))); + + m_selection.flattening_rotate(normal); + do_rotate(""); // avoid taking another snapshot } -void GLCanvas3D::do_mirror() +void GLCanvas3D::do_mirror(const std::string& snapshot_type) { if (m_model == nullptr) return; + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(L(snapshot_type))); + std::set> done; // keeps track of modified instances Selection::EMode selection_mode = m_selection.get_mode(); @@ -3319,6 +3340,8 @@ void GLCanvas3D::do_mirror() } post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + + m_dirty = true; } void GLCanvas3D::set_camera_zoom(double zoom) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 47b1c5ec25..c1b6dce149 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -599,11 +599,12 @@ public: void set_tooltip(const std::string& tooltip) const; - void do_move(); - void do_rotate(); - void do_scale(); - void do_flatten(); - void do_mirror(); + // the following methods add a snapshot to the undo/redo stack, unless the given string is empty + void do_move(const std::string& snapshot_type); + void do_rotate(const std::string& snapshot_type); + void do_scale(const std::string& snapshot_type); + void do_flatten(const Vec3d& normal, const std::string& snapshot_type); + void do_mirror(const std::string& snapshot_type); void set_camera_zoom(double zoom); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index ec5272a44e..787c924511 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -220,8 +220,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - canvas->do_mirror(); - canvas->set_as_dirty(); + canvas->do_mirror("Set Mirror"); UpdateAndShow(true); }); return sizer; @@ -302,8 +301,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - wxGetApp().plater()->take_snapshot(_(L("Set Rotation"))); - canvas->do_rotate(); + canvas->do_rotate("Set Rotation"); UpdateAndShow(true); }); @@ -656,8 +654,7 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.start_dragging(); selection.translate(position - m_cache.position, selection.requires_local_axes()); - wxGetApp().plater()->take_snapshot(_(L("Set Position"))); - canvas->do_move(); + canvas->do_move("Set Position"); m_cache.position = position; m_cache.position_rounded(axis) = DBL_MAX; @@ -688,8 +685,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value) selection.rotate( (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), transformation_type); - wxGetApp().plater()->take_snapshot(_(L("Set Orientation"))); - canvas->do_rotate(); + canvas->do_rotate("Set Orientation"); m_cache.rotation = rotation; m_cache.rotation_rounded(axis) = DBL_MAX; @@ -754,7 +750,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const selection.start_dragging(); selection.scale(scaling_factor * 0.01, transformation_type); - wxGetApp().plater()->canvas3D()->do_scale(); + wxGetApp().plater()->canvas3D()->do_scale("Set Scale"); } void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any& value) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 438cd1a103..23f3cc6c3e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -600,9 +600,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) if (m_current == Flatten) { // Rotate the object so the normal points downward: - wxGetApp().plater()->take_snapshot(_(L("Place on Face"))); - selection.flattening_rotate(get_flattening_normal()); - canvas.do_flatten(); + canvas.do_flatten(get_flattening_normal(), "Place on Face"); wxGetApp().obj_manipul()->set_dirty(); } @@ -674,20 +672,18 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) { case Move: { - wxGetApp().plater()->take_snapshot(_(L("Move Object"))); canvas.disable_regenerate_volumes(); - canvas.do_move(); + canvas.do_move("Gizmo-Move Object"); break; } case Scale: { - canvas.do_scale(); + canvas.do_scale("Gizmo-Scale Object"); break; } case Rotate: { - wxGetApp().plater()->take_snapshot(_(L("Rotate Object"))); - canvas.do_rotate(); + canvas.do_rotate("Gizmo-Rotate Object"); break; } default: diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index a71005cf2c..9971465464 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -806,7 +806,7 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) double s = std::min(sx, std::min(sy, sz)); if (s != 1.0) { - wxGetApp().plater()->take_snapshot(_(L("Scale To Fit"))); + wxGetApp().plater()->take_snapshot(_(L("Scale To Fit"))); TransformationType type; type.set_world(); @@ -816,12 +816,12 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) // apply scale start_dragging(); scale(s * Vec3d::Ones(), type); - wxGetApp().plater()->canvas3D()->do_scale(); + wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot // center selection on print bed start_dragging(); translate(print_volume.center() - get_bounding_box().center()); - wxGetApp().plater()->canvas3D()->do_move(); + wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot wxGetApp().obj_manipul()->set_dirty(); } From 18fcb6468120d76e524ea54e4a0cf569bf19292c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 9 Jul 2019 13:12:55 +0200 Subject: [PATCH 290/627] Fixing broken SLA slicing: SPE-984 --- src/libslic3r/SLA/SLABasePool.cpp | 6 +-- src/libslic3r/SLA/SLASupportTree.cpp | 7 ++-- src/libslic3r/SLAPrint.cpp | 57 +++++++++++++++++----------- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 04cbd78243..b1fbec8395 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -510,9 +510,9 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, float layerh, ThrowOnCancel thrfn) { - TriangleMesh m = mesh; - m.require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer slicer(&m); + if (mesh.empty()) return; + + TriangleMeshSlicer slicer(&mesh); auto bb = mesh.bounding_box(); float gnd = float(bb.min(Z)); diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index ae033c62fc..1f7ef07cb8 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -808,7 +808,6 @@ public: merged.merge(bs.mesh); } - if(m_ctl.stopcondition()) { // In case of failure we have to return an empty mesh meshcache = TriangleMesh(); @@ -819,7 +818,7 @@ public: // The mesh will be passed by const-pointer to TriangleMeshSlicer, // which will need this. - meshcache.require_shared_vertices(); + if (!meshcache.empty()) meshcache.require_shared_vertices(); // TODO: Is this necessary? //meshcache.repair(); @@ -2245,7 +2244,7 @@ SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const TriangleMesh fullmesh = m_impl->merged_mesh(); fullmesh.merge(get_pad()); - fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this + if (!fullmesh.empty()) fullmesh.require_shared_vertices(); TriangleMeshSlicer slicer(&fullmesh); SlicedSupports ret; slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn); @@ -2258,7 +2257,7 @@ SlicedSupports SLASupportTree::slice(const std::vector &heights, { TriangleMesh fullmesh = m_impl->merged_mesh(); fullmesh.merge(get_pad()); - fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this + if (!fullmesh.empty()) fullmesh.require_shared_vertices(); TriangleMeshSlicer slicer(&fullmesh); SlicedSupports ret; slicer.slice(heights, cr, &ret, get().ctl().cancelfn); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 1902e74ae6..8d7534df0d 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -751,18 +751,27 @@ void SLAPrint::process() mit->set_model_slice_idx(po, id); ++mit; } + + if(po.m_config.supports_enable.getBool() || + po.m_config.pad_enable.getBool()) + { + po.m_supportdata.reset( + new SLAPrintObject::SupportData(po.transformed_mesh()) ); + } }; // In this step we check the slices, identify island and cover them with // support points. Then we sprinkle the rest of the mesh. auto support_points = [this, ostepd](SLAPrintObject& po) { - const ModelObject& mo = *po.m_model_object; - po.m_supportdata.reset( - new SLAPrintObject::SupportData(po.transformed_mesh()) ); - // If supports are disabled, we can skip the model scan. if(!po.m_config.supports_enable.getBool()) return; + if (!po.m_supportdata) + po.m_supportdata.reset( + new SLAPrintObject::SupportData(po.transformed_mesh())); + + const ModelObject& mo = *po.m_model_object; + BOOST_LOG_TRIVIAL(debug) << "Support point count " << mo.sla_support_points.size(); @@ -771,7 +780,7 @@ void SLAPrint::process() // into the backend cache. if (mo.sla_points_status != sla::PointsStatus::UserModified) { - // Hypotetical use of the slice index: + // Hypothetical use of the slice index: // auto bb = po.transformed_mesh().bounding_box(); // auto range = po.get_slice_records(bb.min(Z)); // std::vector heights; heights.reserve(range.size()); @@ -888,12 +897,6 @@ void SLAPrint::process() // and before the supports had been sliced. (or the slicing has to be // repeated) - if(!po.m_supportdata || !po.m_supportdata->support_tree_ptr) { - BOOST_LOG_TRIVIAL(error) << "Uninitialized support data at " - << "pad creation."; - return; - } - if(po.m_config.pad_enable.getBool()) { double wt = po.m_config.pad_wall_thickness.getFloat(); @@ -921,7 +924,7 @@ void SLAPrint::process() pcfg.throw_on_cancel = thrfn; po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); - } else { + } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { po.m_supportdata->support_tree_ptr->remove_pad(); } @@ -938,6 +941,11 @@ void SLAPrint::process() if(sd) sd->support_slices.clear(); + // Don't bother if no supports and no pad is present. + if (!po.m_config.supports_enable.getBool() && + !po.m_config.pad_enable.getBool()) + return; + if(sd && sd->support_tree_ptr) { std::vector heights; heights.reserve(po.m_slice_index.size()); @@ -964,7 +972,8 @@ void SLAPrint::process() po.m_slice_index[i].set_support_slice_idx(po, i); } - // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update status to the 3D preview to load the SLA slices. + // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update + // status to the 3D preview to load the SLA slices. m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW); }; @@ -1536,14 +1545,17 @@ bool SLAPrint::is_step_done(SLAPrintObjectStep step) const return true; } -SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object): - Inherited(print, model_object), - m_stepmask(slaposCount, true), - m_transformed_rmesh( [this](TriangleMesh& obj){ - obj = m_model_object->raw_mesh(); obj.transform(m_trafo); obj.require_shared_vertices(); - }) -{ -} +SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object) + : Inherited(print, model_object) + , m_stepmask(slaposCount, true) + , m_transformed_rmesh([this](TriangleMesh &obj) { + obj = m_model_object->raw_mesh(); + if (!obj.empty()) { + obj.transform(m_trafo); + obj.require_shared_vertices(); + } + }) +{} SLAPrintObject::~SLAPrintObject() {} @@ -1682,13 +1694,14 @@ namespace { // dummy empty static containers for return values in some methods const std::vector EMPTY_SLICES; const TriangleMesh EMPTY_MESH; const ExPolygons EMPTY_SLICE; +const std::vector EMPTY_SUPPORT_POINTS; } const SliceRecord SliceRecord::EMPTY(0, std::nanf(""), 0.f); const std::vector& SLAPrintObject::get_support_points() const { - return m_supportdata->support_points; + return m_supportdata? m_supportdata->support_points : EMPTY_SUPPORT_POINTS; } const std::vector &SLAPrintObject::get_support_slices() const From e8461f65dfba5741932c1ae0cc0a6e6946ec01cd Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 9 Jul 2019 13:33:15 +0200 Subject: [PATCH 291/627] Refactoring in class BedShapePanel --- src/slic3r/GUI/BedShapeDialog.cpp | 119 +++++++++++++++--------------- src/slic3r/GUI/BedShapeDialog.hpp | 8 +- 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index d52204d4a7..166127b683 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -54,6 +54,7 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) { auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); + sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font()); // shape options m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, @@ -61,49 +62,49 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) sbsizer->Add(m_shape_options_book); auto optgroup = init_shape_options_page(_(L("Rectangular"))); - ConfigOptionDef def; - def.type = coPoints; - def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); - def.label = L("Size"); - def.tooltip = L("Size in X and Y of the rectangular plate."); - Option option(def, "rect_size"); - optgroup->append_single_option_line(option); + ConfigOptionDef def; + def.type = coPoints; + def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); + def.label = L("Size"); + def.tooltip = L("Size in X and Y of the rectangular plate."); + Option option(def, "rect_size"); + optgroup->append_single_option_line(option); - def.type = coPoints; - def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); - def.label = L("Origin"); - def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); - option = Option(def, "rect_origin"); - optgroup->append_single_option_line(option); + def.type = coPoints; + def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); + def.label = L("Origin"); + def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); + option = Option(def, "rect_origin"); + optgroup->append_single_option_line(option); - optgroup = init_shape_options_page(_(L("Circular"))); - def.type = coFloat; - def.set_default_value(new ConfigOptionFloat(200)); - def.sidetext = L("mm"); - def.label = L("Diameter"); - def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); - option = Option(def, "diameter"); - optgroup->append_single_option_line(option); + optgroup = init_shape_options_page(_(L("Circular"))); + def.type = coFloat; + def.set_default_value(new ConfigOptionFloat(200)); + def.sidetext = L("mm"); + def.label = L("Diameter"); + def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); + option = Option(def, "diameter"); + optgroup->append_single_option_line(option); - optgroup = init_shape_options_page(_(L("Custom"))); - Line line{ "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - auto shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL..."))); - wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL); - shape_sizer->Add(shape_btn, 1, wxEXPAND); + optgroup = init_shape_options_page(_(L("Custom"))); + Line line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + wxButton* shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL..."))); + wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL); + shape_sizer->Add(shape_btn, 1, wxEXPAND); - wxSizer* sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(shape_sizer, 1, wxEXPAND); + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(shape_sizer, 1, wxEXPAND); - shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { - load_stl(); - })); + shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + load_stl(); + })); - return sizer; - }; - optgroup->append_line(line); + return sizer; + }; + optgroup->append_line(line); Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) { @@ -134,20 +135,19 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) // Create a panel for a rectangular / circular / custom bed shape. ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) { - auto panel = new wxPanel(m_shape_options_book); - ConfigOptionsGroupShp optgroup; - optgroup = std::make_shared(panel, _(L("Settings"))); + wxPanel* panel = new wxPanel(m_shape_options_book); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Settings"))); optgroup->label_width = 10; - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - update_shape(); - }; - - m_optgroups.push_back(optgroup); - panel->SetSizerAndFit(optgroup->sizer); - m_shape_options_book->AddPage(panel, title); + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + update_shape(); + }; + + m_optgroups.push_back(optgroup); + panel->SetSizerAndFit(optgroup->sizer); + m_shape_options_book->AddPage(panel, title); - return optgroup; + return optgroup; } // Called from the constructor. @@ -310,15 +310,13 @@ void BedShapePanel::update_shape() // Loads an stl file, projects it to the XY plane and calculates a polygon. void BedShapePanel::load_stl() { - auto dialog = new wxFileDialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "", - file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dialog->ShowModal() != wxID_OK) { - dialog->Destroy(); - return; - } - wxArrayString input_file; - dialog->GetPaths(input_file); - dialog->Destroy(); + wxFileDialog dialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "", + file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() != wxID_OK) + return; + + wxArrayString input_file; + dialog.GetPaths(input_file); std::string file_name = input_file[0].ToUTF8().data(); @@ -327,10 +325,9 @@ void BedShapePanel::load_stl() model = Model::read_from_file(file_name); } catch (std::exception &e) { - auto msg = _(L("Error!")) + " " + file_name + " : " + e.what() + "."; - show_error(this, msg); - exit(1); - } + show_error(this, _(L("Error! Invalid model"))); + return; + } auto mesh = model.mesh(); auto expolygons = mesh.horizontal_projection(); diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 6600a1c84d..d2c67a7c72 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -25,18 +25,20 @@ public: void build_panel(ConfigOptionPoints* default_pt); + // Returns the resulting bed shape polygon. This value will be stored to the ini file. + std::vector GetValue() { return m_canvas->m_bed_shape; } + +private: ConfigOptionsGroupShp init_shape_options_page(const wxString& title); void set_shape(ConfigOptionPoints* points); void update_preview(); void update_shape(); void load_stl(); - // Returns the resulting bed shape polygon. This value will be stored to the ini file. - std::vector GetValue() { return m_canvas->m_bed_shape; } - wxChoicebook* m_shape_options_book; std::vector m_optgroups; + friend class BedShapeDialog; }; class BedShapeDialog : public DPIDialog From 52dc8547aca48764b4c40bbb020dcfaafaedaa72 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 9 Jul 2019 15:08:34 +0200 Subject: [PATCH 292/627] Reverder order of rendering of color print time estimates --- src/slic3r/GUI/Plater.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7ba9e585ad..dee78aef74 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1132,7 +1132,7 @@ void Sidebar::show_sliced_info_sizer(const bool show) if (ps.estimated_normal_print_time != "N/A") { new_label += wxString::Format("\n - %s", _(L("normal mode"))); info_text += wxString::Format("\n%s", ps.estimated_normal_print_time); - for (unsigned int i = 0; i < (unsigned int)ps.estimated_normal_color_print_times.size(); ++i) + for (int i = (int)ps.estimated_normal_color_print_times.size() - 1; i >= 0; --i) { new_label += wxString::Format("\n - %s%d", _(L("Color ")), i + 1); info_text += wxString::Format("\n%s", ps.estimated_normal_color_print_times[i]); @@ -1141,7 +1141,7 @@ void Sidebar::show_sliced_info_sizer(const bool show) if (ps.estimated_silent_print_time != "N/A") { new_label += wxString::Format("\n - %s", _(L("stealth mode"))); info_text += wxString::Format("\n%s", ps.estimated_silent_print_time); - for (unsigned int i = 0; i < (unsigned int)ps.estimated_normal_color_print_times.size(); ++i) + for (int i = (int)ps.estimated_normal_color_print_times.size() - 1; i >= 0; --i) { new_label += wxString::Format("\n - %s%d", _(L("Color ")), i + 1); info_text += wxString::Format("\n%s", ps.estimated_normal_color_print_times[i]); From 1f74d7fdfc548fc168d2a71e1a341108639516e1 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 9 Jul 2019 15:47:34 +0200 Subject: [PATCH 293/627] Added color number in legend texture for color prints --- src/libslic3r/GCode/PreviewData.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp index 8eba6801e4..8ea52714b2 100644 --- a/src/libslic3r/GCode/PreviewData.cpp +++ b/src/libslic3r/GCode/PreviewData.cpp @@ -466,7 +466,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: } case Extrusion::Tool: { - unsigned int tools_colors_count = tool_colors.size() / 4; + unsigned int tools_colors_count = (unsigned int)tool_colors.size() / 4; items.reserve(tools_colors_count); for (unsigned int i = 0; i < tools_colors_count; ++i) { @@ -491,17 +491,20 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: items.emplace_back(Slic3r::I18N::translate(L("Default print color")), color); break; } + + std::string id_str = std::to_string(i + 1) + ": "; + if (i == 0) { - items.emplace_back((boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color); + items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color); break; } if (i == color_print_cnt) { - items.emplace_back((boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i-1].second).str(), color); + items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i - 1].second).str(), color); continue; } // items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i-1] % cp_values[i]).str(), color); - items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i-1].second % cp_values[i].first).str(), color); + items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i - 1].second% cp_values[i].first).str(), color); } break; } From 1347e655c21ec665621e0929373e5e211b5610a4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 9 Jul 2019 15:27:28 +0200 Subject: [PATCH 294/627] Next improvements of an undo/redo from a toolbar --- src/slic3r/GUI/GLCanvas3D.cpp | 84 +++++++++++++++++++++------------ src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/GLToolbar.cpp | 4 +- src/slic3r/GUI/GLToolbar.hpp | 24 ++++++++-- src/slic3r/GUI/ImGuiWrapper.cpp | 35 ++++++++------ src/slic3r/GUI/ImGuiWrapper.hpp | 2 +- src/slic3r/GUI/Plater.cpp | 15 ++++++ src/slic3r/GUI/Plater.hpp | 1 + 8 files changed, 113 insertions(+), 53 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 970b349cbd..e362cf74db 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3442,6 +3442,41 @@ bool GLCanvas3D::_is_shown_on_screen() const return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; } +// Getter for the const char*[] +static bool string_getter(const bool is_undo, int idx, const char** out_text) +{ + return wxGetApp().plater()->undo_redo_string_getter(is_undo, idx, out_text); +} + +void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) +{ + if (m_canvas != nullptr && m_toolbar.get_imgui_visible(is_undo)) + { + const wxString& stack_name = _(is_undo ? L("Undo") : L("Redo")); + ImGuiWrapper* imgui = wxGetApp().imgui(); + + const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); + imgui->set_next_window_pos(x, m_toolbar.get_height(), ImGuiCond_Always); + + imgui->set_next_window_bg_alpha(0.5f); + imgui->begin(wxString::Format(_(L("%s Stack")), stack_name), + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + int hovered = m_toolbar.get_imgui_hovered_pos(); + int selected = -1; + const float em = static_cast(wxGetApp().em_unit()); + + if (imgui->undo_redo_list(ImVec2(12 * em, 20 * em), is_undo, &string_getter, hovered, selected)) + m_toolbar.set_imgui_hovered_pos(hovered); + if (selected >= 0) + m_toolbar.hide_imgui(is_undo); + + imgui->text(wxString::Format(_(L("%s %d Action")), stack_name, hovered + 1)); + + imgui->end(); + } +} + bool GLCanvas3D::_init_toolbar() { if (!m_toolbar.is_enabled()) @@ -3627,37 +3662,19 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]"; item.sprite_id = 11; - item.action_callback = [this]() - { + item.is_toggable = false; + item.action_callback = [this]() { if (m_canvas != nullptr) { wxPostEvent(m_canvas, SimpleEvent(EVT_GLCANVAS_UNDO)); - m_toolbar.set_imgui_visible(); + m_toolbar.activate_imgui(true); } }; item.visibility_callback = []()->bool { return true; }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_undo(); }; - item.render_callback = [this]() - { - if (m_canvas != nullptr && m_toolbar.get_imgui_visible()) { - ImGuiWrapper* imgui = wxGetApp().imgui(); - - const float approx_height = m_toolbar.get_height(); - imgui->set_next_window_pos(600, approx_height, ImGuiCond_Always); - - imgui->set_next_window_bg_alpha(0.5f); - imgui->begin(_(L("Undo Stack")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - std::vector undo_stack = {"A", "B", "C", "D","A", "B", "C", "D","A", "B", "C", "D",}; - int sel = 4; - imgui->multi_sel_list("", undo_stack, sel); - - const bool undo_clicked = imgui->button(_(L("Undo N Action"))); - - imgui->end(); - if (undo_clicked) - m_toolbar.set_imgui_visible(false); - } + item.enabled_state_callback = [this]()->bool { + if (!wxGetApp().plater()->can_undo()) { m_toolbar.hide_imgui(true); return false; } + return true; }; + item.render_callback = [this](float pos_x, float, float, float) { _render_undo_redo_stack(true, pos_x); }; if (!m_toolbar.add_item(item)) return false; @@ -3667,15 +3684,20 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]"; item.sprite_id = 12; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLCANVAS_REDO)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_redo(); }; - item.render_callback = []() {}; + item.action_callback = [this]() { + if (m_canvas != nullptr) { + wxPostEvent(m_canvas, SimpleEvent(EVT_GLCANVAS_REDO)); + m_toolbar.activate_imgui(false); + } + }; + item.enabled_state_callback = [this]()->bool { + if (!wxGetApp().plater()->can_redo()) { m_toolbar.hide_imgui(false); return false; } + return true; + }; + item.render_callback = [this](float pos_x, float, float, float) { _render_undo_redo_stack(false, pos_x); }; if (!m_toolbar.add_item(item)) return false; - if (!m_toolbar.add_separator()) - return false; - return true; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 47b1c5ec25..4867f94ce9 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -676,6 +676,7 @@ private: #endif // ENABLE_SHOW_CAMERA_TARGET void _render_sla_slices() const; void _render_selection_sidebar_hints() const; + void _render_undo_redo_stack(const bool is_undo, float pos_x); void _update_volumes_hover_state() const; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index a851e3a4ce..f6140464b6 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -35,7 +35,7 @@ wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent); const GLToolbarItem::ActionCallback GLToolbarItem::Default_Action_Callback = [](){}; const GLToolbarItem::VisibilityCallback GLToolbarItem::Default_Visibility_Callback = []()->bool { return true; }; const GLToolbarItem::EnabledStateCallback GLToolbarItem::Default_Enabled_State_Callback = []()->bool { return true; }; -const GLToolbarItem::RenderCallback GLToolbarItem::Default_Render_Callback = [](){}; +const GLToolbarItem::RenderCallback GLToolbarItem::Default_Render_Callback = [](float, float, float, float){}; GLToolbarItem::Data::Data() : name("") @@ -84,7 +84,7 @@ void GLToolbarItem::render(unsigned int tex_id, float left, float right, float b { GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(tex_width, tex_height, icon_size)); - m_data.render_callback(); + m_data.render_callback(left, right, bottom, top); } GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index aa68fae38e..e32e4a41ed 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -37,7 +37,7 @@ public: typedef std::function ActionCallback; typedef std::function VisibilityCallback; typedef std::function EnabledStateCallback; - typedef std::function RenderCallback; + typedef std::function RenderCallback; enum EType : unsigned char { @@ -252,7 +252,10 @@ private: MouseCapture m_mouse_capture; std::string m_tooltip; - bool m_imgui_visible {false}; + bool m_undo_imgui_visible {false}; + bool m_redo_imgui_visible {false}; + int m_imgui_hovered_pos { -1 }; + int m_imgui_selected_pos { -1 }; public: #if ENABLE_SVG_ICONS @@ -309,8 +312,21 @@ public: bool on_mouse(wxMouseEvent& evt, GLCanvas3D& parent); - void set_imgui_visible(bool visible = true) { m_imgui_visible = visible; } - bool get_imgui_visible() { return m_imgui_visible; } + // undo == true => "undo" imgui is activated + // undo == false => "redo" imgui is activated + bool get_imgui_visible(const bool undo) const { return undo ? m_undo_imgui_visible : m_redo_imgui_visible; } + void hide_imgui(const bool undo) { undo ? m_undo_imgui_visible = false : m_redo_imgui_visible = false; } + void activate_imgui(const bool undo) { + m_undo_imgui_visible = undo; + m_redo_imgui_visible = !undo; + m_imgui_hovered_pos = m_imgui_selected_pos = -1; + } + + void set_imgui_hovered_pos(int pos = -1) { m_imgui_hovered_pos = pos; } + int get_imgui_hovered_pos() const { return m_imgui_hovered_pos; } + + void set_imgui_selected_pos(int pos = -1) { m_imgui_selected_pos = pos; } + int get_imgui_selected_pos() const { return m_imgui_selected_pos; } private: void calc_layout() const; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index cbcf33f77d..7f4a6c10c4 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -342,25 +342,30 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector& return res; } -// Getter for the const char*[] -static bool StringGetter(void* data, int i, const char** out_text) +bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool , int , const char**), int& hovered, int& selected) { - const std::vector* v = (std::vector*)data; - if (out_text) - *out_text = (*v)[i].c_str(); - return true; -} + bool is_hovered = false; + ImGui::ListBoxHeader("", size); -bool ImGuiWrapper::multi_sel_list(const wxString& label, const std::vector& options, int& selection) -{ - // this is to force the label to the left of the widget: - if (!label.IsEmpty()) - text(label); + int i=0; + const char* item_text; + while (items_getter(is_undo, i, &item_text)) + { + ImGui::Selectable(item_text, i < hovered); - bool res = false; - ImGui::ListBox("", &selection, StringGetter, (void*)&options, (int)options.size()); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(item_text); + hovered = i; + is_hovered = true; + } - return res; + if (ImGui::IsItemClicked()) + selected = i; + i++; + } + + ImGui::ListBoxFooter(); + return is_hovered; } void ImGuiWrapper::disabled_begin(bool disabled) diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 42c562b720..a18b151845 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -67,7 +67,7 @@ public: void text(const std::string &label); void text(const wxString &label); bool combo(const wxString& label, const std::vector& options, int& selection); // Use -1 to not mark any option as selected - bool multi_sel_list(const wxString& label, const std::vector& options, int& selection); // Use -1 to not mark any option as selected + bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected); void disabled_begin(bool disabled); void disabled_end(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6ac58eb841..3dfc1d1b88 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4110,6 +4110,21 @@ void Plater::take_snapshot(const std::string &snapshot_name) { p->take_snapshot( void Plater::take_snapshot(const wxString &snapshot_name) { p->take_snapshot(snapshot_name); } void Plater::undo() { p->undo(); } void Plater::redo() { p->redo(); } +bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text) +{ + const size_t& active_snapshot_time = p->undo_redo_stack.active_snapshot_time(); + const std::vector& ss_stack = p->undo_redo_stack.snapshots(); + const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time)); + + const int idx_in_ss_stack = it - ss_stack.begin() + (is_undo ? -(++idx) : idx); + + if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) { + *out_text = ss_stack[idx_in_ss_stack].name.c_str(); + return true; + } + + return false; +} void Plater::on_extruders_change(int num_extruders) { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 0be465f53c..7f379c6381 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -187,6 +187,7 @@ public: void take_snapshot(const wxString &snapshot_name); void undo(); void redo(); + bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text); void on_extruders_change(int extruders_count); void on_config_change(const DynamicPrintConfig &config); From f985f5190c8260b28b1a3d06d20138ee16113dad Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 9 Jul 2019 20:45:00 +0200 Subject: [PATCH 295/627] Completed undo/redo from a toolbar --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 48 ++++++++++++++++++++++++++++++++--- src/slic3r/GUI/Plater.hpp | 2 ++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index dc59f4e5ba..d87b97f530 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3492,7 +3492,7 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) if (imgui->undo_redo_list(ImVec2(12 * em, 20 * em), is_undo, &string_getter, hovered, selected)) m_toolbar.set_imgui_hovered_pos(hovered); if (selected >= 0) - m_toolbar.hide_imgui(is_undo); + is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); imgui->text(wxString::Format(_(L("%s %d Action")), stack_name, hovered + 1)); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3dfc1d1b88..a450ac0e5a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1583,8 +1583,11 @@ struct Plater::priv void take_snapshot(const std::string& snapshot_name) { this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection()); } void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } + int get_active_snapshot_index(); void undo(); void redo(); + void undo_to(size_t time_to_load); + void redo_to(size_t time_to_load); bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; } void update_print_volume_state(); @@ -3560,6 +3563,14 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const } } +int Plater::priv::get_active_snapshot_index() +{ + const size_t& active_snapshot_time = this->undo_redo_stack.active_snapshot_time(); + const std::vector& ss_stack = this->undo_redo_stack.snapshots(); + const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time)); + return it - ss_stack.begin(); +} + void Plater::priv::undo() { if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection())) @@ -3572,6 +3583,18 @@ void Plater::priv::redo() this->update_after_undo_redo(); } +void Plater::priv::undo_to(size_t time_to_load) +{ + if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), time_to_load)) + this->update_after_undo_redo(); +} + +void Plater::priv::redo_to(size_t time_to_load) +{ + if (this->undo_redo_stack.redo(model, time_to_load)) + this->update_after_undo_redo(); +} + void Plater::priv::update_after_undo_redo() { this->view3D->get_canvas3d()->get_selection().clear(); @@ -4110,13 +4133,30 @@ void Plater::take_snapshot(const std::string &snapshot_name) { p->take_snapshot( void Plater::take_snapshot(const wxString &snapshot_name) { p->take_snapshot(snapshot_name); } void Plater::undo() { p->undo(); } void Plater::redo() { p->redo(); } +void Plater::undo_to(int selection) +{ + if (selection == 0) { + p->undo(); + return; + } + + const int idx = p->get_active_snapshot_index() - selection - 1; + p->undo_to(p->undo_redo_stack.snapshots()[idx].timestamp); +} +void Plater::redo_to(int selection) +{ + if (selection == 0) { + p->redo(); + return; + } + + const int idx = selection + p->get_active_snapshot_index(); + p->redo_to(p->undo_redo_stack.snapshots()[idx].timestamp); +} bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text) { - const size_t& active_snapshot_time = p->undo_redo_stack.active_snapshot_time(); const std::vector& ss_stack = p->undo_redo_stack.snapshots(); - const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time)); - - const int idx_in_ss_stack = it - ss_stack.begin() + (is_undo ? -(++idx) : idx); + const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -(++idx) : idx); if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) { *out_text = ss_stack[idx_in_ss_stack].name.c_str(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 7f379c6381..ca3d592247 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -187,6 +187,8 @@ public: void take_snapshot(const wxString &snapshot_name); void undo(); void redo(); + void undo_to(int selection); + void redo_to(int selection); bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text); void on_extruders_change(int extruders_count); From 3720e6a3a3ec59fca0f8781139e5d329156f81e6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 10 Jul 2019 10:15:07 +0200 Subject: [PATCH 296/627] Fixed redo_to() function and code cleaning from redundant options --- src/slic3r/GUI/GLCanvas3D.cpp | 66 +++++++++++++---------------------- src/slic3r/GUI/GLToolbar.cpp | 3 +- src/slic3r/GUI/GLToolbar.hpp | 16 --------- src/slic3r/GUI/Plater.cpp | 2 +- 4 files changed, 27 insertions(+), 60 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d87b97f530..4fa5b8b276 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3473,31 +3473,31 @@ static bool string_getter(const bool is_undo, int idx, const char** out_text) void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) { - if (m_canvas != nullptr && m_toolbar.get_imgui_visible(is_undo)) - { - const wxString& stack_name = _(is_undo ? L("Undo") : L("Redo")); - ImGuiWrapper* imgui = wxGetApp().imgui(); + const wxString& stack_name = _(is_undo ? L("Undo") : L("Redo")); + ImGuiWrapper* imgui = wxGetApp().imgui(); - const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); - imgui->set_next_window_pos(x, m_toolbar.get_height(), ImGuiCond_Always); + const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); + imgui->set_next_window_pos(x, m_toolbar.get_height(), ImGuiCond_Always); - imgui->set_next_window_bg_alpha(0.5f); - imgui->begin(wxString::Format(_(L("%s Stack")), stack_name), - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + imgui->set_next_window_bg_alpha(0.5f); + imgui->begin(wxString::Format(_(L("%s Stack")), stack_name), + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - int hovered = m_toolbar.get_imgui_hovered_pos(); - int selected = -1; - const float em = static_cast(wxGetApp().em_unit()); + int hovered = m_toolbar.get_imgui_hovered_pos(); + int selected = -1; + const float em = static_cast(wxGetApp().em_unit()); - if (imgui->undo_redo_list(ImVec2(12 * em, 20 * em), is_undo, &string_getter, hovered, selected)) - m_toolbar.set_imgui_hovered_pos(hovered); - if (selected >= 0) - is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); + if (imgui->undo_redo_list(ImVec2(12 * em, 20 * em), is_undo, &string_getter, hovered, selected)) + m_toolbar.set_imgui_hovered_pos(hovered); + else + m_toolbar.set_imgui_hovered_pos(-1); - imgui->text(wxString::Format(_(L("%s %d Action")), stack_name, hovered + 1)); + if (selected >= 0) + is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); - imgui->end(); - } + imgui->text(wxString::Format(_(L("%s %d Action")), stack_name, hovered + 1)); + + imgui->end(); } bool GLCanvas3D::_init_toolbar() @@ -3685,19 +3685,10 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]"; item.sprite_id = 11; - item.is_toggable = false; - item.action_callback = [this]() { - if (m_canvas != nullptr) { - wxPostEvent(m_canvas, SimpleEvent(EVT_GLCANVAS_UNDO)); - m_toolbar.activate_imgui(true); - } - }; + item.action_callback = [this]() { if (m_canvas != nullptr) m_toolbar.set_imgui_hovered_pos(-1); }; item.visibility_callback = []()->bool { return true; }; - item.enabled_state_callback = [this]()->bool { - if (!wxGetApp().plater()->can_undo()) { m_toolbar.hide_imgui(true); return false; } - return true; - }; - item.render_callback = [this](float pos_x, float, float, float) { _render_undo_redo_stack(true, pos_x); }; + item.enabled_state_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); } ; + item.render_callback = [this](float pos_x, float, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, pos_x); }; if (!m_toolbar.add_item(item)) return false; @@ -3707,17 +3698,8 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]"; item.sprite_id = 12; - item.action_callback = [this]() { - if (m_canvas != nullptr) { - wxPostEvent(m_canvas, SimpleEvent(EVT_GLCANVAS_REDO)); - m_toolbar.activate_imgui(false); - } - }; - item.enabled_state_callback = [this]()->bool { - if (!wxGetApp().plater()->can_redo()) { m_toolbar.hide_imgui(false); return false; } - return true; - }; - item.render_callback = [this](float pos_x, float, float, float) { _render_undo_redo_stack(false, pos_x); }; + item.enabled_state_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); }; + item.render_callback = [this](float pos_x, float, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, pos_x); }; if (!m_toolbar.add_item(item)) return false; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index f6140464b6..e786053330 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -84,7 +84,8 @@ void GLToolbarItem::render(unsigned int tex_id, float left, float right, float b { GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(tex_width, tex_height, icon_size)); - m_data.render_callback(left, right, bottom, top); + if (is_pressed()) + m_data.render_callback(left, right, bottom, top); } GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index e32e4a41ed..78dd56081f 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -252,10 +252,7 @@ private: MouseCapture m_mouse_capture; std::string m_tooltip; - bool m_undo_imgui_visible {false}; - bool m_redo_imgui_visible {false}; int m_imgui_hovered_pos { -1 }; - int m_imgui_selected_pos { -1 }; public: #if ENABLE_SVG_ICONS @@ -312,22 +309,9 @@ public: bool on_mouse(wxMouseEvent& evt, GLCanvas3D& parent); - // undo == true => "undo" imgui is activated - // undo == false => "redo" imgui is activated - bool get_imgui_visible(const bool undo) const { return undo ? m_undo_imgui_visible : m_redo_imgui_visible; } - void hide_imgui(const bool undo) { undo ? m_undo_imgui_visible = false : m_redo_imgui_visible = false; } - void activate_imgui(const bool undo) { - m_undo_imgui_visible = undo; - m_redo_imgui_visible = !undo; - m_imgui_hovered_pos = m_imgui_selected_pos = -1; - } - void set_imgui_hovered_pos(int pos = -1) { m_imgui_hovered_pos = pos; } int get_imgui_hovered_pos() const { return m_imgui_hovered_pos; } - void set_imgui_selected_pos(int pos = -1) { m_imgui_selected_pos = pos; } - int get_imgui_selected_pos() const { return m_imgui_selected_pos; } - private: void calc_layout() const; float get_width_horizontal() const; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a450ac0e5a..d8825ab3a3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4150,7 +4150,7 @@ void Plater::redo_to(int selection) return; } - const int idx = selection + p->get_active_snapshot_index(); + const int idx = p->get_active_snapshot_index() + selection + 1; p->redo_to(p->undo_redo_stack.snapshots()[idx].timestamp); } bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text) From 46e295407b3c568e39cebb79dc8b3c3dbca386c3 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 10 Jul 2019 10:52:12 +0200 Subject: [PATCH 297/627] Modified toolbar to call RenderCallback only when the item is toggable and pressed --- src/slic3r/GUI/GLCanvas3D.cpp | 40 ++++++------------ src/slic3r/GUI/GLToolbar.cpp | 79 ++++++++++++++++++++++------------- src/slic3r/GUI/GLToolbar.hpp | 23 ++-------- 3 files changed, 65 insertions(+), 77 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d87b97f530..4c9f700197 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3473,9 +3473,9 @@ static bool string_getter(const bool is_undo, int idx, const char** out_text) void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) { - if (m_canvas != nullptr && m_toolbar.get_imgui_visible(is_undo)) + if (m_canvas != nullptr) { - const wxString& stack_name = _(is_undo ? L("Undo") : L("Redo")); + const wxString stack_name = _(is_undo ? L("Undo") : L("Redo")); ImGuiWrapper* imgui = wxGetApp().imgui(); const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); @@ -3483,16 +3483,17 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) imgui->set_next_window_bg_alpha(0.5f); imgui->begin(wxString::Format(_(L("%s Stack")), stack_name), - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - int hovered = m_toolbar.get_imgui_hovered_pos(); + int hovered = -1; int selected = -1; const float em = static_cast(wxGetApp().em_unit()); if (imgui->undo_redo_list(ImVec2(12 * em, 20 * em), is_undo, &string_getter, hovered, selected)) - m_toolbar.set_imgui_hovered_pos(hovered); - if (selected >= 0) - is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); + { + if (selected >= 0) + is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); + } imgui->text(wxString::Format(_(L("%s %d Action")), stack_name, hovered + 1)); @@ -3685,18 +3686,9 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]"; item.sprite_id = 11; - item.is_toggable = false; - item.action_callback = [this]() { - if (m_canvas != nullptr) { - wxPostEvent(m_canvas, SimpleEvent(EVT_GLCANVAS_UNDO)); - m_toolbar.activate_imgui(true); - } - }; + item.action_callback = [this]() { if (m_canvas != nullptr) { wxPostEvent(m_canvas, SimpleEvent(EVT_GLCANVAS_UNDO)); } }; item.visibility_callback = []()->bool { return true; }; - item.enabled_state_callback = [this]()->bool { - if (!wxGetApp().plater()->can_undo()) { m_toolbar.hide_imgui(true); return false; } - return true; - }; + item.enabled_state_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); }; item.render_callback = [this](float pos_x, float, float, float) { _render_undo_redo_stack(true, pos_x); }; if (!m_toolbar.add_item(item)) return false; @@ -3707,16 +3699,8 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]"; item.sprite_id = 12; - item.action_callback = [this]() { - if (m_canvas != nullptr) { - wxPostEvent(m_canvas, SimpleEvent(EVT_GLCANVAS_REDO)); - m_toolbar.activate_imgui(false); - } - }; - item.enabled_state_callback = [this]()->bool { - if (!wxGetApp().plater()->can_redo()) { m_toolbar.hide_imgui(false); return false; } - return true; - }; + item.action_callback = [this]() { if (m_canvas != nullptr) { wxPostEvent(m_canvas, SimpleEvent(EVT_GLCANVAS_REDO)); } }; + item.enabled_state_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); }; item.render_callback = [this](float pos_x, float, float, float) { _render_undo_redo_stack(false, pos_x); }; if (!m_toolbar.add_item(item)) return false; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index f6140464b6..78e10c4f1e 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -84,7 +84,8 @@ void GLToolbarItem::render(unsigned int tex_id, float left, float right, float b { GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(tex_width, tex_height, icon_size)); - m_data.render_callback(left, right, bottom, top); + if (is_toggable() && is_pressed()) + m_data.render_callback(left, right, bottom, top); } GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const @@ -164,6 +165,7 @@ GLToolbar::GLToolbar(GLToolbar::EType type) , m_icons_texture_dirty(true) #endif // ENABLE_SVG_ICONS , m_tooltip("") + , m_pressed_toggable_id(-1) { } @@ -345,7 +347,7 @@ void GLToolbar::select_item(const std::string& name) bool GLToolbar::is_item_pressed(const std::string& name) const { - for (GLToolbarItem* item : m_items) + for (const GLToolbarItem* item : m_items) { if (item->get_name() == name) return item->is_pressed(); @@ -356,7 +358,7 @@ bool GLToolbar::is_item_pressed(const std::string& name) const bool GLToolbar::is_item_disabled(const std::string& name) const { - for (GLToolbarItem* item : m_items) + for (const GLToolbarItem* item : m_items) { if (item->get_name() == name) return item->is_disabled(); @@ -367,7 +369,7 @@ bool GLToolbar::is_item_disabled(const std::string& name) const bool GLToolbar::is_item_visible(const std::string& name) const { - for (GLToolbarItem* item : m_items) + for (const GLToolbarItem* item : m_items) { if (item->get_name() == name) return item->is_visible(); @@ -376,11 +378,25 @@ bool GLToolbar::is_item_visible(const std::string& name) const return false; } +bool GLToolbar::is_any_item_pressed() const +{ + for (const GLToolbarItem* item : m_items) + { + if (item->is_pressed()) + return true; + } + + return false; +} + bool GLToolbar::update_items_state() { bool ret = false; ret |= update_items_visibility(); ret |= update_items_enabled_state(); + if (!is_any_item_pressed()) + m_pressed_toggable_id = -1; + return ret; } @@ -558,36 +574,41 @@ float GLToolbar::get_main_size() const void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) { - if (item_id < (unsigned int)m_items.size()) + if ((m_pressed_toggable_id == -1) || (m_pressed_toggable_id == item_id)) { - GLToolbarItem* item = m_items[item_id]; - if ((item != nullptr) && !item->is_separator() && item->is_hovered()) + if (item_id < (unsigned int)m_items.size()) { - if (item->is_toggable()) + GLToolbarItem* item = m_items[item_id]; + if ((item != nullptr) && !item->is_separator() && item->is_hovered()) { - GLToolbarItem::EState state = item->get_state(); - if (state == GLToolbarItem::Hover) - item->set_state(GLToolbarItem::HoverPressed); - else if (state == GLToolbarItem::HoverPressed) - item->set_state(GLToolbarItem::Hover); - - parent.render(); - item->do_action(); - } - else - { - if (m_type == Radio) - select_item(item->get_name()); - else - item->set_state(GLToolbarItem::HoverPressed); - - parent.render(); - item->do_action(); - if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) + if (item->is_toggable()) { - // the item may get disabled during the action, if not, set it back to hover state - item->set_state(GLToolbarItem::Hover); + GLToolbarItem::EState state = item->get_state(); + if (state == GLToolbarItem::Hover) + item->set_state(GLToolbarItem::HoverPressed); + else if (state == GLToolbarItem::HoverPressed) + item->set_state(GLToolbarItem::Hover); + + m_pressed_toggable_id = item->is_pressed() ? item_id : -1; + parent.render(); + item->do_action(); + } + else + { + if (m_type == Radio) + select_item(item->get_name()); + else + item->set_state(GLToolbarItem::HoverPressed); + + parent.render(); + item->do_action(); + if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) + { + // the item may get disabled during the action, if not, set it back to hover state + item->set_state(GLToolbarItem::Hover); + parent.render(); + } } } } diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index e32e4a41ed..de19296a5d 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -252,10 +252,7 @@ private: MouseCapture m_mouse_capture; std::string m_tooltip; - bool m_undo_imgui_visible {false}; - bool m_redo_imgui_visible {false}; - int m_imgui_hovered_pos { -1 }; - int m_imgui_selected_pos { -1 }; + unsigned int m_pressed_toggable_id; public: #if ENABLE_SVG_ICONS @@ -302,6 +299,8 @@ public: bool is_item_disabled(const std::string& name) const; bool is_item_visible(const std::string& name) const; + bool is_any_item_pressed() const; + const std::string& get_tooltip() const { return m_tooltip; } @@ -312,22 +311,6 @@ public: bool on_mouse(wxMouseEvent& evt, GLCanvas3D& parent); - // undo == true => "undo" imgui is activated - // undo == false => "redo" imgui is activated - bool get_imgui_visible(const bool undo) const { return undo ? m_undo_imgui_visible : m_redo_imgui_visible; } - void hide_imgui(const bool undo) { undo ? m_undo_imgui_visible = false : m_redo_imgui_visible = false; } - void activate_imgui(const bool undo) { - m_undo_imgui_visible = undo; - m_redo_imgui_visible = !undo; - m_imgui_hovered_pos = m_imgui_selected_pos = -1; - } - - void set_imgui_hovered_pos(int pos = -1) { m_imgui_hovered_pos = pos; } - int get_imgui_hovered_pos() const { return m_imgui_hovered_pos; } - - void set_imgui_selected_pos(int pos = -1) { m_imgui_selected_pos = pos; } - int get_imgui_selected_pos() const { return m_imgui_selected_pos; } - private: void calc_layout() const; float get_width_horizontal() const; From 99df9f56c4a68660d819180caa15f538b7341c5d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 10 Jul 2019 11:28:11 +0200 Subject: [PATCH 298/627] Added take_snapshot() for adding of settings --- src/slic3r/GUI/GUI_ObjectList.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 90c3f2665d..c11865cac1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1115,6 +1115,8 @@ void ObjectList::get_settings_choice(const wxString& category_name) } #endif + take_snapshot(wxString::Format(_(L("Add Settings for %s")), is_part ? _(L("Sub-object")) : _(L("Object")))); + std::vector selected_options; selected_options.reserve(selection_cnt); for (auto sel : selections) @@ -1165,6 +1167,8 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) assert(m_config); auto opt_keys = m_config->keys(); + take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(GetSelection()) & itObject ? _(L("Object")) : _(L("Sub-object")))); + const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; for (auto& opt_key : options) { From 40a1f31e847e8613c7b588e6a026b1dcdec317ad Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 10 Jul 2019 11:59:25 +0200 Subject: [PATCH 299/627] Disable remaining toolbar items when one of them is toggable and pressed --- src/slic3r/GUI/GLToolbar.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 78e10c4f1e..8c9751086d 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -1396,9 +1396,15 @@ bool GLToolbar::update_items_enabled_state() { bool ret = false; - for (GLToolbarItem* item : m_items) + for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) { + GLToolbarItem* item = m_items[i]; ret |= item->update_enabled_state(); + if (item->is_enabled() && (m_pressed_toggable_id != -1) && (m_pressed_toggable_id != i)) + { + ret = true; + item->set_state(GLToolbarItem::Disabled); + } } if (ret) From 14dad5039a316b9a9224cc638f3bd0b783a1293b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 10 Jul 2019 13:45:25 +0200 Subject: [PATCH 300/627] Imgui dialogs for undo/redo centered on their toolbar item icon --- src/slic3r/GUI/GLCanvas3D.cpp | 7 +++---- src/slic3r/GUI/ImGuiWrapper.cpp | 4 ++-- src/slic3r/GUI/ImGuiWrapper.hpp | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 4fa5b8b276..d14739c94d 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3477,8 +3477,7 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) ImGuiWrapper* imgui = wxGetApp().imgui(); const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); - imgui->set_next_window_pos(x, m_toolbar.get_height(), ImGuiCond_Always); - + imgui->set_next_window_pos(x, m_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); imgui->set_next_window_bg_alpha(0.5f); imgui->begin(wxString::Format(_(L("%s Stack")), stack_name), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); @@ -3688,7 +3687,7 @@ bool GLCanvas3D::_init_toolbar() item.action_callback = [this]() { if (m_canvas != nullptr) m_toolbar.set_imgui_hovered_pos(-1); }; item.visibility_callback = []()->bool { return true; }; item.enabled_state_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); } ; - item.render_callback = [this](float pos_x, float, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, pos_x); }; + item.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); }; if (!m_toolbar.add_item(item)) return false; @@ -3699,7 +3698,7 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]"; item.sprite_id = 12; item.enabled_state_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); }; - item.render_callback = [this](float pos_x, float, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, pos_x); }; + item.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, 0.5f * (left + right)); }; if (!m_toolbar.add_item(item)) return false; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 7f4a6c10c4..f58266a5df 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -233,9 +233,9 @@ ImVec2 ImGuiWrapper::calc_text_size(const wxString &text) return size; } -void ImGuiWrapper::set_next_window_pos(float x, float y, int flag) +void ImGuiWrapper::set_next_window_pos(float x, float y, int flag, float pivot_x, float pivot_y) { - ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag); + ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag, ImVec2(pivot_x, pivot_y)); ImGui::SetNextWindowSize(ImVec2(0.0, 0.0)); } diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index a18b151845..c6550351e7 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -51,7 +51,7 @@ public: ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); } ImVec2 calc_text_size(const wxString &text); - void set_next_window_pos(float x, float y, int flag); + void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); void set_next_window_bg_alpha(float alpha); bool begin(const std::string &name, int flags = 0); From 1b5ab100bd685d5cf4e77d0b653b864562f9932b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 10 Jul 2019 14:08:14 +0200 Subject: [PATCH 301/627] GLToolbar::m_imgui_hovered_pos replaced with GLCanvas3D::m_imgui_undo_redo_hovered_pos --- src/slic3r/GUI/GLCanvas3D.cpp | 8 ++++---- src/slic3r/GUI/GLCanvas3D.hpp | 2 ++ src/slic3r/GUI/GLToolbar.hpp | 4 ---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d14739c94d..3dccf45512 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3482,14 +3482,14 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) imgui->begin(wxString::Format(_(L("%s Stack")), stack_name), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - int hovered = m_toolbar.get_imgui_hovered_pos(); + int hovered = m_imgui_undo_redo_hovered_pos; int selected = -1; const float em = static_cast(wxGetApp().em_unit()); if (imgui->undo_redo_list(ImVec2(12 * em, 20 * em), is_undo, &string_getter, hovered, selected)) - m_toolbar.set_imgui_hovered_pos(hovered); + m_imgui_undo_redo_hovered_pos = hovered; else - m_toolbar.set_imgui_hovered_pos(-1); + m_imgui_undo_redo_hovered_pos = -1; if (selected >= 0) is_undo ? wxGetApp().plater()->undo_to(selected) : wxGetApp().plater()->redo_to(selected); @@ -3684,7 +3684,7 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]"; item.sprite_id = 11; - item.action_callback = [this]() { if (m_canvas != nullptr) m_toolbar.set_imgui_hovered_pos(-1); }; + item.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; item.visibility_callback = []()->bool { return true; }; item.enabled_state_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); } ; item.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); }; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index f9efdf37a8..1949c864b9 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -488,6 +488,8 @@ private: RenderStats m_render_stats; #endif // ENABLE_RENDER_STATISTICS + int m_imgui_undo_redo_hovered_pos{ -1 }; + public: GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); ~GLCanvas3D(); diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 2ba1bb31b7..de19296a5d 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -252,7 +252,6 @@ private: MouseCapture m_mouse_capture; std::string m_tooltip; - int m_imgui_hovered_pos { -1 }; unsigned int m_pressed_toggable_id; public: @@ -312,9 +311,6 @@ public: bool on_mouse(wxMouseEvent& evt, GLCanvas3D& parent); - void set_imgui_hovered_pos(int pos = -1) { m_imgui_hovered_pos = pos; } - int get_imgui_hovered_pos() const { return m_imgui_hovered_pos; } - private: void calc_layout() const; float get_width_horizontal() const; From 1dc0439a31562d901db266a039ca166ba3f7d08e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 10 Jul 2019 15:55:53 +0200 Subject: [PATCH 302/627] Fixed margins for FreqChangedParams --- src/slic3r/GUI/OptionsGroup.hpp | 6 ++++++ src/slic3r/GUI/Plater.cpp | 15 ++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 422a5c2a28..d720787b6c 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -162,6 +162,12 @@ public: void clear_fields_except_of(const std::vector left_fields); + void hide_labels() { + label_width = 0; + m_grid_sizer->SetCols(m_grid_sizer->GetEffectiveColsCount()-1); + static_cast(m_grid_sizer)->AddGrowableCol(!extra_column ? 0 : 1); + } + OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false, column_t extra_clmn = nullptr) : m_parent(_parent), title(title), diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index dee78aef74..3a155c9b0b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -245,6 +245,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * last_selected(wxNOT_FOUND), m_em_unit(wxGetApp().em_unit()) { + SetFont(wxGetApp().normal_font()); Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { auto selected_item = this->GetSelection(); @@ -373,7 +374,7 @@ class FreqChangedParams : public OG_Settings std::shared_ptr m_og_sla; public: - FreqChangedParams(wxWindow* parent, const int label_width); + FreqChangedParams(wxWindow* parent); ~FreqChangedParams() {} wxButton* get_wiping_dialog_button() { return m_wiping_dialog_button; } @@ -382,14 +383,14 @@ public: void Show(const bool is_fff); }; -FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : +FreqChangedParams::FreqChangedParams(wxWindow* parent) : OG_Settings(parent, false) { DynamicPrintConfig* config = &wxGetApp().preset_bundle->prints.get_edited_preset().config; // Frequently changed parameters for FFF_technology m_og->set_config(config); - m_og->label_width = label_width == 0 ? 1 : label_width; + m_og->hide_labels(); m_og->m_on_change = [config, this](t_config_option_key opt_key, boost::any value) { Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); @@ -485,6 +486,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : auto wiping_dialog_btn = [config, this](wxWindow* parent) { m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + m_wiping_dialog_button->SetFont(wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_wiping_dialog_button); m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) @@ -512,9 +514,9 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : // Frequently changed parameters for SLA_technology m_og_sla = std::make_shared(parent, ""); + m_og_sla->hide_labels(); DynamicPrintConfig* config_sla = &wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; m_og_sla->set_config(config_sla); - m_og_sla->label_width = label_width == 0 ? 1 : label_width; m_og_sla->m_on_change = [config_sla, this](t_config_option_key opt_key, boost::any value) { Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT); @@ -733,13 +735,12 @@ Sidebar::Sidebar(Plater *parent) init_combo(&p->combo_printer, _(L("Printer")), Preset::TYPE_PRINTER, false); const int margin_5 = int(0.5*wxGetApp().em_unit());// 5; - const int margin_10 = 10;//int(1.5*wxGetApp().em_unit());// 15; p->sizer_params = new wxBoxSizer(wxVERTICAL); // Frequently changed parameters - p->frequently_changed_parameters = new FreqChangedParams(p->scrolled, 0/*label_width*/); - p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, margin_10); + p->frequently_changed_parameters = new FreqChangedParams(p->scrolled); + p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, wxOSX ? 1 : margin_5); // Object List p->object_list = new ObjectList(p->scrolled); From 5cbaa7b08129899ff207d660aed42e22531ef9b4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 10 Jul 2019 17:50:24 +0200 Subject: [PATCH 303/627] FreqChangedParams : workaround for right border alignment --- src/slic3r/GUI/OptionsGroup.cpp | 4 ++-- src/slic3r/GUI/Plater.cpp | 42 +++++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 0ed23889e6..9feca2f3d7 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -266,7 +266,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n is_sizer_field(field) ? v_sizer->Add(field->getSizer(), 0, wxEXPAND) : v_sizer->Add(field->getWindow(), 0, wxEXPAND); - return; + break;//return; } is_sizer_field(field) ? @@ -300,7 +300,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n { // extra widget for non-staticbox option group (like for the frequently used parameters on the sidebar) should be wxALIGN_RIGHT const auto v_sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(v_sizer, 1, wxEXPAND); + sizer->Add(v_sizer, option_set.size() == 1 ? 0 : 1, wxEXPAND); v_sizer->Add(extra_widget(this->ctrl_parent()), 0, wxALIGN_RIGHT); return; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3a155c9b0b..2988278d79 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -373,6 +373,7 @@ class FreqChangedParams : public OG_Settings wxSizer* m_sizer {nullptr}; std::shared_ptr m_og_sla; + std::vector m_empty_buttons; public: FreqChangedParams(wxWindow* parent); ~FreqChangedParams() {} @@ -381,8 +382,19 @@ public: wxSizer* get_sizer() override; ConfigOptionsGroup* get_og(const bool is_fff); void Show(const bool is_fff); + + void msw_rescale(); }; +void FreqChangedParams::msw_rescale() +{ + m_og->msw_rescale(); + m_og_sla->msw_rescale(); + + for (auto btn: m_empty_buttons) + btn->msw_rescale(); +} + FreqChangedParams::FreqChangedParams(wxWindow* parent) : OG_Settings(parent, false) { @@ -462,6 +474,20 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : Option option = Option(support_def, "support"); option.opt.full_width = true; line.append_option(option); + + /* Not a best solution, but + * Temporary workaround for right border alignment + */ + auto empty_widget = [this] (wxWindow* parent) { + auto sizer = new wxBoxSizer(wxHORIZONTAL); + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, + wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, int(0.3 * wxGetApp().em_unit())); + m_empty_buttons.push_back(btn); + return sizer; + }; + line.append_widget(empty_widget); + m_og->append_line(line); @@ -488,7 +514,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); m_wiping_dialog_button->SetFont(wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_wiping_dialog_button); + sizer->Add(m_wiping_dialog_button, 0, wxALIGN_CENTER_VERTICAL); m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) { auto &config = wxGetApp().preset_bundle->project_config; @@ -505,6 +531,13 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent)); } })); + + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, + wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + sizer->Add(btn , 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, + int(0.3 * wxGetApp().em_unit())); + m_empty_buttons.push_back(btn); + return sizer; }; line.append_widget(wiping_dialog_btn); @@ -554,7 +587,8 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : support_def_sla.enum_labels.erase(support_def_sla.enum_labels.begin() + 2); option = Option(support_def_sla, "support"); option.opt.full_width = true; - line.append_option(option); + line.append_option(option); + line.append_widget(empty_widget); m_og_sla->append_line(line); line = Line{ "", "" }; @@ -947,9 +981,7 @@ void Sidebar::msw_rescale() // ... then refill them and set min size to correct layout of the sidebar update_all_preset_comboboxes(); - p->frequently_changed_parameters->get_og(true)->msw_rescale(); - p->frequently_changed_parameters->get_og(false)->msw_rescale(); - + p->frequently_changed_parameters->msw_rescale(); p->object_list->msw_rescale(); p->object_manipulation->msw_rescale(); p->object_settings->msw_rescale(); From dbf0eacfa7c0ac05183dbee54e003ce0c517fbd1 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 11 Jul 2019 07:46:40 +0200 Subject: [PATCH 304/627] Deactivate undo/redo toolbar items when leaving the 3D scene or clicking into it --- src/slic3r/GUI/GLCanvas3D.cpp | 24 ++++++++++++++++++++++-- src/slic3r/GUI/GLCanvas3D.hpp | 2 ++ src/slic3r/GUI/GLToolbar.cpp | 30 ++++++++++++++++++++++++------ src/slic3r/GUI/GLToolbar.hpp | 7 +++++-- 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3dccf45512..31087d633e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2397,7 +2397,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #endif /* __APPLE__ */ post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; - case WXK_ESCAPE: { deselect_all(); break; } case '0': { select_view("iso"); break; } case '1': { select_view("top"); break; } @@ -2744,12 +2743,17 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if (evt.Leaving()) { + _deactivate_undo_redo_toolbar_items(); + // to remove hover on objects when the mouse goes out of this canvas m_mouse.position = Vec2d(-1.0, -1.0); m_dirty = true; } - else if (evt.LeftDown() || evt.RightDown()) + else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) { + if (_deactivate_undo_redo_toolbar_items()) + return; + // If user pressed left or right button we first check whether this happened // on a volume or not. m_layers_editing.state = LayersEditing::Unknown; @@ -5866,6 +5870,22 @@ void GLCanvas3D::_update_selection_from_hover() m_dirty = true; } +bool GLCanvas3D::_deactivate_undo_redo_toolbar_items() +{ + if (m_toolbar.is_item_pressed("undo")) + { + m_toolbar.force_action(m_toolbar.get_item_id("undo"), *this); + return true; + } + else if (m_toolbar.is_item_pressed("redo")) + { + m_toolbar.force_action(m_toolbar.get_item_id("redo"), *this); + return true; + } + + return false; +} + const Print* GLCanvas3D::fff_print() const { return (m_process == nullptr) ? nullptr : m_process->fff_print(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 1949c864b9..3ae420cdb1 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -742,6 +742,8 @@ private: // updates the selection from the content of m_hover_volume_idxs void _update_selection_from_hover(); + bool _deactivate_undo_redo_toolbar_items(); + static std::vector _parse_colors(const std::vector& colors); public: diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 8c9751086d..45e4275226 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -389,6 +389,22 @@ bool GLToolbar::is_any_item_pressed() const return false; } +unsigned int GLToolbar::get_item_id(const std::string& name) const +{ + for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) + { + if (m_items[i]->get_name() == name) + return i; + } + + return -1; +} + +void GLToolbar::force_action(unsigned int item_id, GLCanvas3D& parent) +{ + do_action(item_id, parent, false); +} + bool GLToolbar::update_items_state() { bool ret = false; @@ -461,10 +477,8 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) m_mouse_capture.parent = &parent; processed = true; if ((item_id != -2) && !m_items[item_id]->is_separator()) - { // mouse is inside an icon - do_action((unsigned int)item_id, parent); - } + do_action((unsigned int)item_id, parent, true); } else if (evt.MiddleDown()) { @@ -572,14 +586,14 @@ float GLToolbar::get_main_size() const return size; } -void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) +void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent, bool check_hover) { if ((m_pressed_toggable_id == -1) || (m_pressed_toggable_id == item_id)) { if (item_id < (unsigned int)m_items.size()) { GLToolbarItem* item = m_items[item_id]; - if ((item != nullptr) && !item->is_separator() && item->is_hovered()) + if ((item != nullptr) && !item->is_separator() && (!check_hover || item->is_hovered())) { if (item->is_toggable()) { @@ -588,6 +602,10 @@ void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) item->set_state(GLToolbarItem::HoverPressed); else if (state == GLToolbarItem::HoverPressed) item->set_state(GLToolbarItem::Hover); + else if (state == GLToolbarItem::Pressed) + item->set_state(GLToolbarItem::Normal); + else if (state == GLToolbarItem::Normal) + item->set_state(GLToolbarItem::Pressed); m_pressed_toggable_id = item->is_pressed() ? item_id : -1; @@ -599,7 +617,7 @@ void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) if (m_type == Radio) select_item(item->get_name()); else - item->set_state(GLToolbarItem::HoverPressed); + item->set_state(item->is_hovered() ? GLToolbarItem::HoverPressed : GLToolbarItem::Pressed); parent.render(); item->do_action(); diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index de19296a5d..33e1ca234e 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -301,8 +301,11 @@ public: bool is_any_item_pressed() const; - const std::string& get_tooltip() const { return m_tooltip; } + unsigned int get_item_id(const std::string& name) const; + void force_action(unsigned int item_id, GLCanvas3D& parent); + + const std::string& get_tooltip() const { return m_tooltip; } // returns true if any item changed its state bool update_items_state(); @@ -318,7 +321,7 @@ private: float get_height_horizontal() const; float get_height_vertical() const; float get_main_size() const; - void do_action(unsigned int item_id, GLCanvas3D& parent); + void do_action(unsigned int item_id, GLCanvas3D& parent, bool check_hover); std::string update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); std::string update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent); std::string update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent); From f964f5e99a5f432569ce3e04a26a420a1490c7ae Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 11 Jul 2019 07:54:33 +0200 Subject: [PATCH 305/627] Deactivate undo/redo toolbar items by pressing Esc key --- src/slic3r/GUI/GLCanvas3D.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 31087d633e..fe91e9a96a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2326,6 +2326,9 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) return; } + if ((keyCode == WXK_ESCAPE) && _deactivate_undo_redo_toolbar_items()) + return; + if (m_gizmos.on_char(evt, *this)) return; From 4c6c608342e92d6769205ea8b68bde7ebbef04d6 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 11 Jul 2019 15:29:46 +0200 Subject: [PATCH 306/627] GLToolbar and GLToolbarItem refactored to allow two different actions when left/right clicking on items. Stack dialog for undo and redo items is now shown on right click only --- src/slic3r/GUI/GLCanvas3D.cpp | 64 +++++++++++++++++--------------- src/slic3r/GUI/GLToolbar.cpp | 70 +++++++++++++++++++++++++++-------- src/slic3r/GUI/GLToolbar.hpp | 47 +++++++++++++++++------ src/slic3r/GUI/Plater.cpp | 6 +-- 4 files changed, 127 insertions(+), 60 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index fe91e9a96a..ceca4e0ff7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3550,7 +3550,7 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]"; item.sprite_id = 0; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; + item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; if (!m_toolbar.add_item(item)) return false; @@ -3560,8 +3560,8 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Delete")) + " [Del]"; item.sprite_id = 1; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; + item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; if (!m_toolbar.add_item(item)) return false; @@ -3571,8 +3571,8 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; item.sprite_id = 2; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; + item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; if (!m_toolbar.add_item(item)) return false; @@ -3582,8 +3582,8 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Arrange")) + " [A]"; item.sprite_id = 3; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; + item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; if (!m_toolbar.add_item(item)) return false; @@ -3596,8 +3596,8 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]"; item.sprite_id = 4; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); }; + item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); }; if (!m_toolbar.add_item(item)) return false; @@ -3607,8 +3607,8 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]"; item.sprite_id = 5; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); }; + item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); }; if (!m_toolbar.add_item(item)) return false; @@ -3621,9 +3621,10 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Add instance")) + " [+]"; item.sprite_id = 6; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; + item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; + if (!m_toolbar.add_item(item)) return false; @@ -3633,9 +3634,9 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Remove instance")) + " [-]"; item.sprite_id = 7; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; + item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; if (!m_toolbar.add_item(item)) return false; @@ -3648,9 +3649,9 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Split to objects")); item.sprite_id = 8; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; + item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; if (!m_toolbar.add_item(item)) return false; @@ -3660,9 +3661,9 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Split to parts")); item.sprite_id = 9; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; + item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; if (!m_toolbar.add_item(item)) return false; @@ -3675,10 +3676,10 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Layers editing")); item.sprite_id = 10; - item.is_toggable = true; - item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; + item.left_toggable = true; + item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; }; - item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; if (!m_toolbar.add_item(item)) return false; @@ -3691,10 +3692,13 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]"; item.sprite_id = 11; - item.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; + item.left_toggable = false; + item.right_toggable = true; + item.left_action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); }; + item.right_action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; item.visibility_callback = []()->bool { return true; }; - item.enabled_state_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); } ; - item.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); }; + item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); }; + item.right_render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); }; if (!m_toolbar.add_item(item)) return false; @@ -3704,8 +3708,10 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]"; item.sprite_id = 12; - item.enabled_state_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); }; - item.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, 0.5f * (left + right)); }; + item.left_action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); }; + item.right_action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; + item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); }; + item.right_render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, 0.5f * (left + right)); }; if (!m_toolbar.add_item(item)) return false; @@ -5877,12 +5883,12 @@ bool GLCanvas3D::_deactivate_undo_redo_toolbar_items() { if (m_toolbar.is_item_pressed("undo")) { - m_toolbar.force_action(m_toolbar.get_item_id("undo"), *this); + m_toolbar.force_right_action(m_toolbar.get_item_id("undo"), *this); return true; } else if (m_toolbar.is_item_pressed("redo")) { - m_toolbar.force_action(m_toolbar.get_item_id("redo"), *this); + m_toolbar.force_right_action(m_toolbar.get_item_id("redo"), *this); return true; } diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 45e4275226..862805f8d0 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -34,7 +34,7 @@ wxDEFINE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent); const GLToolbarItem::ActionCallback GLToolbarItem::Default_Action_Callback = [](){}; const GLToolbarItem::VisibilityCallback GLToolbarItem::Default_Visibility_Callback = []()->bool { return true; }; -const GLToolbarItem::EnabledStateCallback GLToolbarItem::Default_Enabled_State_Callback = []()->bool { return true; }; +const GLToolbarItem::EnablingCallback GLToolbarItem::Default_Enabling_Callback = []()->bool { return true; }; const GLToolbarItem::RenderCallback GLToolbarItem::Default_Render_Callback = [](float, float, float, float){}; GLToolbarItem::Data::Data() @@ -44,12 +44,15 @@ GLToolbarItem::Data::Data() #endif // ENABLE_SVG_ICONS , tooltip("") , sprite_id(-1) - , is_toggable(false) + , left_toggable(false) + , right_toggable(false) , visible(true) - , action_callback(Default_Action_Callback) + , left_action_callback(Default_Action_Callback) + , right_action_callback(Default_Action_Callback) , visibility_callback(Default_Visibility_Callback) - , enabled_state_callback(Default_Enabled_State_Callback) - , render_callback(Default_Render_Callback) + , enabling_callback(Default_Enabling_Callback) + , left_render_callback(nullptr) + , right_render_callback(nullptr) { } @@ -57,6 +60,7 @@ GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Dat : m_type(type) , m_state(Normal) , m_data(data) + , m_last_action(Undefined) { } @@ -72,7 +76,7 @@ bool GLToolbarItem::update_visibility() bool GLToolbarItem::update_enabled_state() { - bool enabled = m_data.enabled_state_callback(); + bool enabled = m_data.enabling_callback(); bool ret = (is_enabled() != enabled); if (ret) m_state = enabled ? GLToolbarItem::Normal : GLToolbarItem::Disabled; @@ -84,8 +88,13 @@ void GLToolbarItem::render(unsigned int tex_id, float left, float right, float b { GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(tex_width, tex_height, icon_size)); - if (is_toggable() && is_pressed()) - m_data.render_callback(left, right, bottom, top); + if (is_pressed()) + { + if ((m_last_action == Left) && m_data.left_toggable && (m_data.left_render_callback != nullptr)) + m_data.left_render_callback(left, right, bottom, top); + else if ((m_last_action == Right) && m_data.right_toggable && (m_data.right_render_callback != nullptr)) + m_data.right_render_callback(left, right, bottom, top); + } } GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const @@ -400,9 +409,14 @@ unsigned int GLToolbar::get_item_id(const std::string& name) const return -1; } -void GLToolbar::force_action(unsigned int item_id, GLCanvas3D& parent) +void GLToolbar::force_left_action(unsigned int item_id, GLCanvas3D& parent) { - do_action(item_id, parent, false); + do_action(GLToolbarItem::Left, item_id, parent, false); +} + +void GLToolbar::force_right_action(unsigned int item_id, GLCanvas3D& parent) +{ + do_action(GLToolbarItem::Right, item_id, parent, false); } bool GLToolbar::update_items_state() @@ -476,9 +490,12 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) m_mouse_capture.left = true; m_mouse_capture.parent = &parent; processed = true; - if ((item_id != -2) && !m_items[item_id]->is_separator()) + if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action() == GLToolbarItem::Left))) + { // mouse is inside an icon - do_action((unsigned int)item_id, parent, true); + do_action(GLToolbarItem::Left, (unsigned int)item_id, parent, true); + parent.set_as_dirty(); + } } else if (evt.MiddleDown()) { @@ -489,6 +506,13 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) { m_mouse_capture.right = true; m_mouse_capture.parent = &parent; + processed = true; + if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action() == GLToolbarItem::Right))) + { + // mouse is inside an icon + do_action(GLToolbarItem::Right, (unsigned int)item_id, parent, true); + parent.set_as_dirty(); + } } else if (evt.LeftUp()) processed = true; @@ -586,7 +610,7 @@ float GLToolbar::get_main_size() const return size; } -void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent, bool check_hover) +void GLToolbar::do_action(GLToolbarItem::EActionType type, unsigned int item_id, GLCanvas3D& parent, bool check_hover) { if ((m_pressed_toggable_id == -1) || (m_pressed_toggable_id == item_id)) { @@ -595,7 +619,8 @@ void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent, bool check_h GLToolbarItem* item = m_items[item_id]; if ((item != nullptr) && !item->is_separator() && (!check_hover || item->is_hovered())) { - if (item->is_toggable()) + if (((type == GLToolbarItem::Right) && item->is_right_toggable()) || + ((type == GLToolbarItem::Left) && item->is_left_toggable())) { GLToolbarItem::EState state = item->get_state(); if (state == GLToolbarItem::Hover) @@ -608,9 +633,15 @@ void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent, bool check_h item->set_state(GLToolbarItem::Pressed); m_pressed_toggable_id = item->is_pressed() ? item_id : -1; + item->reset_last_action(); parent.render(); - item->do_action(); + switch (type) + { + default: + case GLToolbarItem::Left: { item->do_left_action(); break; } + case GLToolbarItem::Right: { item->do_right_action(); break; } + } } else { @@ -619,8 +650,15 @@ void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent, bool check_h else item->set_state(item->is_hovered() ? GLToolbarItem::HoverPressed : GLToolbarItem::Pressed); + item->reset_last_action(); parent.render(); - item->do_action(); + switch (type) + { + default: + case GLToolbarItem::Left: { item->do_left_action(); break; } + case GLToolbarItem::Right: { item->do_right_action(); break; } + } + if ((m_type == Normal) && (item->get_state() != GLToolbarItem::Disabled)) { // the item may get disabled during the action, if not, set it back to hover state diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 33e1ca234e..86cd1a7fc2 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -36,8 +36,8 @@ class GLToolbarItem public: typedef std::function ActionCallback; typedef std::function VisibilityCallback; - typedef std::function EnabledStateCallback; - typedef std::function RenderCallback; + typedef std::function EnablingCallback; + typedef std::function RenderCallback; enum EType : unsigned char { @@ -46,6 +46,14 @@ public: Num_Types }; + enum EActionType : unsigned char + { + Undefined, + Left, + Right, + Num_Action_Types + }; + enum EState : unsigned char { Normal, @@ -64,25 +72,33 @@ public: #endif // ENABLE_SVG_ICONS std::string tooltip; unsigned int sprite_id; - bool is_toggable; + bool left_toggable; + bool right_toggable; bool visible; - ActionCallback action_callback; + // action on left click + ActionCallback left_action_callback; + // action on right click + ActionCallback right_action_callback; VisibilityCallback visibility_callback; - EnabledStateCallback enabled_state_callback; - RenderCallback render_callback; + EnablingCallback enabling_callback; + // render callback on left click + RenderCallback left_render_callback; + // render callback on right click + RenderCallback right_render_callback; Data(); }; static const ActionCallback Default_Action_Callback; static const VisibilityCallback Default_Visibility_Callback; - static const EnabledStateCallback Default_Enabled_State_Callback; + static const EnablingCallback Default_Enabling_Callback; static const RenderCallback Default_Render_Callback; private: EType m_type; EState m_state; Data m_data; + EActionType m_last_action; public: GLToolbarItem(EType type, const Data& data); @@ -96,17 +112,25 @@ public: #endif // ENABLE_SVG_ICONS const std::string& get_tooltip() const { return m_data.tooltip; } - void do_action() { m_data.action_callback(); } + void do_left_action() { m_last_action = Left; m_data.left_action_callback(); } + void do_right_action() { m_last_action = Right; m_data.right_action_callback(); } bool is_enabled() const { return m_state != Disabled; } bool is_disabled() const { return m_state == Disabled; } bool is_hovered() const { return (m_state == Hover) || (m_state == HoverPressed); } bool is_pressed() const { return (m_state == Pressed) || (m_state == HoverPressed); } - bool is_toggable() const { return m_data.is_toggable; } + bool is_left_toggable() const { return m_data.left_toggable; } + bool is_right_toggable() const { return m_data.right_toggable; } bool is_visible() const { return m_data.visible; } bool is_separator() const { return m_type == Separator; } + bool has_left_render_callback() const { return m_data.left_render_callback != nullptr; } + bool has_right_render_callback() const { return m_data.right_render_callback != nullptr; } + + EActionType get_last_action() const { return m_last_action; } + void reset_last_action() { m_last_action = Undefined; } + // returns true if the state changes bool update_visibility(); // returns true if the state changes @@ -303,7 +327,8 @@ public: unsigned int get_item_id(const std::string& name) const; - void force_action(unsigned int item_id, GLCanvas3D& parent); + void force_left_action(unsigned int item_id, GLCanvas3D& parent); + void force_right_action(unsigned int item_id, GLCanvas3D& parent); const std::string& get_tooltip() const { return m_tooltip; } @@ -321,7 +346,7 @@ private: float get_height_horizontal() const; float get_height_vertical() const; float get_main_size() const; - void do_action(unsigned int item_id, GLCanvas3D& parent, bool check_hover); + void do_action(GLToolbarItem::EActionType type, unsigned int item_id, GLCanvas3D& parent, bool check_hover); std::string update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); std::string update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent); std::string update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d8825ab3a3..ea66d5f788 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3421,8 +3421,7 @@ void Plater::priv::init_view_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("3D editor view")) + " [" + GUI::shortkey_ctrl_prefix() + "5]"; item.sprite_id = 0; - item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; - item.is_toggable = false; + item.left_action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; if (!view_toolbar.add_item(item)) return; @@ -3432,8 +3431,7 @@ void Plater::priv::init_view_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Preview")) + " [" + GUI::shortkey_ctrl_prefix() + "6]"; item.sprite_id = 1; - item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; - item.is_toggable = false; + item.left_action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; if (!view_toolbar.add_item(item)) return; From a6a5b94155b22b4434e8f4ad1dc0426da95ffa1e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 11 Jul 2019 16:00:01 +0200 Subject: [PATCH 307/627] Added suppress_snapshots() and allow_snapshots() for avoid of excess "snapshoting" --- src/slic3r/GUI/GUI_ObjectList.cpp | 10 +++++++++- src/slic3r/GUI/Plater.cpp | 22 ++++++++++++++++++++-- src/slic3r/GUI/Plater.hpp | 2 ++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c11865cac1..360318413c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -66,11 +66,14 @@ static int extruders_count() return wxGetApp().extruders_cnt(); } -static void take_snapshot(const wxString& snapshot_name) +static void take_snapshot(const wxString& snapshot_name) { wxGetApp().plater()->take_snapshot(snapshot_name); } +static void suppress_snapshots(){ wxGetApp().plater()->suppress_snapshots(); } +static void allow_snapshots() { wxGetApp().plater()->allow_snapshots(); } + ObjectList::ObjectList(wxWindow* parent) : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), m_parent(parent) @@ -2333,6 +2336,9 @@ void ObjectList::remove() wxDataViewItem parent = wxDataViewItem(0); + take_snapshot(_(L("Delete Selected"))); + suppress_snapshots(); + for (auto& item : sels) { if (m_objects_model->GetParent(item) == wxDataViewItem(0)) @@ -2353,6 +2359,8 @@ void ObjectList::remove() if (parent) select_item(parent); + + allow_snapshots(); } void ObjectList::del_layer_range(const t_layer_height_range& range) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d8825ab3a3..15dd6b6ca8 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1280,7 +1280,11 @@ struct Plater::priv PrinterTechnology printer_technology = ptFFF; Slic3r::GCodePreviewData gcode_preview_data; Slic3r::UndoRedo::Stack undo_redo_stack; - + bool m_prevent_snapshots = false; /* Used for avoid of excess "snapshoting". + * Like for "delete selected" or "set numbers of copies" + * we should call tack_snapshot just ones + * instead of calls for each action separately + * */ // GUI elements wxSizer* panel_sizer{ nullptr }; wxPanel* current_panel{ nullptr }; @@ -1581,13 +1585,20 @@ struct Plater::priv void split_volume(); void scale_selection_to_fit_print_volume(); - void take_snapshot(const std::string& snapshot_name) { this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection()); } + void take_snapshot(const std::string& snapshot_name) + { + if (this->m_prevent_snapshots) + return; + this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection()); + } void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } int get_active_snapshot_index(); void undo(); void redo(); void undo_to(size_t time_to_load); void redo_to(size_t time_to_load); + void suppress_snapshots() { this->m_prevent_snapshots = true; } + void allow_snapshots() { this->m_prevent_snapshots = false; } bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; } void update_print_volume_state(); @@ -3729,7 +3740,9 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo void Plater::remove_selected() { this->take_snapshot(_(L("Delete Selected Objects"))); + this->suppress_snapshots(); this->p->view3D->delete_selected(); + this->allow_snapshots(); } void Plater::increase_instances(size_t num) @@ -3809,12 +3822,15 @@ void Plater::set_number_of_copies(/*size_t num*/) return; this->take_snapshot(wxString::Format(_(L("Set numbers of copies to %d")), num)); + this->suppress_snapshots(); int diff = (int)num - (int)model_object->instances.size(); if (diff > 0) increase_instances(diff); else if (diff < 0) decrease_instances(-diff); + + this->allow_snapshots(); } bool Plater::is_selection_empty() const @@ -4131,6 +4147,8 @@ void Plater::send_gcode() void Plater::take_snapshot(const std::string &snapshot_name) { p->take_snapshot(snapshot_name); } void Plater::take_snapshot(const wxString &snapshot_name) { p->take_snapshot(snapshot_name); } +void Plater::suppress_snapshots() { p->suppress_snapshots(); } +void Plater::allow_snapshots() { p->allow_snapshots(); } void Plater::undo() { p->undo(); } void Plater::redo() { p->redo(); } void Plater::undo_to(int selection) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index ca3d592247..23a5f12cf8 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -185,6 +185,8 @@ public: void take_snapshot(const std::string &snapshot_name); void take_snapshot(const wxString &snapshot_name); + void suppress_snapshots(); + void allow_snapshots(); void undo(); void redo(); void undo_to(int selection); From 2f57f756e54151721e9be4b5f0bbe2788830ad20 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 12 Jul 2019 09:26:19 +0200 Subject: [PATCH 308/627] Follow-up of 4c6c608342e92d6769205ea8b68bde7ebbef04d6 -> refactoring --- src/slic3r/GUI/GLCanvas3D.cpp | 40 +++++++++++++++---------------- src/slic3r/GUI/GLToolbar.cpp | 31 ++++++++++++------------ src/slic3r/GUI/GLToolbar.hpp | 45 +++++++++++++++++++---------------- src/slic3r/GUI/Plater.cpp | 4 ++-- 4 files changed, 63 insertions(+), 57 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ceca4e0ff7..86aab33bee 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3550,7 +3550,7 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]"; item.sprite_id = 0; - item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; if (!m_toolbar.add_item(item)) return false; @@ -3560,7 +3560,7 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Delete")) + " [Del]"; item.sprite_id = 1; - item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; if (!m_toolbar.add_item(item)) return false; @@ -3571,7 +3571,7 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; item.sprite_id = 2; - item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; if (!m_toolbar.add_item(item)) return false; @@ -3582,7 +3582,7 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Arrange")) + " [A]"; item.sprite_id = 3; - item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; if (!m_toolbar.add_item(item)) return false; @@ -3596,7 +3596,7 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]"; item.sprite_id = 4; - item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); }; if (!m_toolbar.add_item(item)) return false; @@ -3607,7 +3607,7 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]"; item.sprite_id = 5; - item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); }; if (!m_toolbar.add_item(item)) return false; @@ -3621,7 +3621,7 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Add instance")) + " [+]"; item.sprite_id = 6; - item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; @@ -3634,7 +3634,7 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Remove instance")) + " [-]"; item.sprite_id = 7; - item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; if (!m_toolbar.add_item(item)) @@ -3649,7 +3649,7 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Split to objects")); item.sprite_id = 8; - item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; if (!m_toolbar.add_item(item)) @@ -3661,7 +3661,7 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Split to parts")); item.sprite_id = 9; - item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; if (!m_toolbar.add_item(item)) @@ -3676,8 +3676,8 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Layers editing")); item.sprite_id = 10; - item.left_toggable = true; - item.left_action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; + item.left.toggable = true; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; if (!m_toolbar.add_item(item)) @@ -3692,13 +3692,13 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]"; item.sprite_id = 11; - item.left_toggable = false; - item.right_toggable = true; - item.left_action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); }; - item.right_action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; + item.left.toggable = false; + item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); }; + item.right.toggable = true; + item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; + item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); }; item.visibility_callback = []()->bool { return true; }; item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); }; - item.right_render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); }; if (!m_toolbar.add_item(item)) return false; @@ -3708,10 +3708,10 @@ bool GLCanvas3D::_init_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]"; item.sprite_id = 12; - item.left_action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); }; - item.right_action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; + item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); }; + item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; + item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, 0.5f * (left + right)); }; item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); }; - item.right_render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, 0.5f * (left + right)); }; if (!m_toolbar.add_item(item)) return false; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 862805f8d0..5093eb5d43 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -37,6 +37,13 @@ const GLToolbarItem::VisibilityCallback GLToolbarItem::Default_Visibility_Callba const GLToolbarItem::EnablingCallback GLToolbarItem::Default_Enabling_Callback = []()->bool { return true; }; const GLToolbarItem::RenderCallback GLToolbarItem::Default_Render_Callback = [](float, float, float, float){}; +GLToolbarItem::Data::Option::Option() + : toggable(false) + , action_callback(Default_Action_Callback) + , render_callback(nullptr) +{ +} + GLToolbarItem::Data::Data() : name("") #if ENABLE_SVG_ICONS @@ -44,15 +51,9 @@ GLToolbarItem::Data::Data() #endif // ENABLE_SVG_ICONS , tooltip("") , sprite_id(-1) - , left_toggable(false) - , right_toggable(false) , visible(true) - , left_action_callback(Default_Action_Callback) - , right_action_callback(Default_Action_Callback) , visibility_callback(Default_Visibility_Callback) , enabling_callback(Default_Enabling_Callback) - , left_render_callback(nullptr) - , right_render_callback(nullptr) { } @@ -60,7 +61,7 @@ GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Dat : m_type(type) , m_state(Normal) , m_data(data) - , m_last_action(Undefined) + , m_last_action_type(Undefined) { } @@ -90,10 +91,10 @@ void GLToolbarItem::render(unsigned int tex_id, float left, float right, float b if (is_pressed()) { - if ((m_last_action == Left) && m_data.left_toggable && (m_data.left_render_callback != nullptr)) - m_data.left_render_callback(left, right, bottom, top); - else if ((m_last_action == Right) && m_data.right_toggable && (m_data.right_render_callback != nullptr)) - m_data.right_render_callback(left, right, bottom, top); + if ((m_last_action_type == Left) && m_data.left.can_render()) + m_data.left.render_callback(left, right, bottom, top); + else if ((m_last_action_type == Right) && m_data.right.can_render()) + m_data.right.render_callback(left, right, bottom, top); } } @@ -490,7 +491,7 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) m_mouse_capture.left = true; m_mouse_capture.parent = &parent; processed = true; - if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action() == GLToolbarItem::Left))) + if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Left))) { // mouse is inside an icon do_action(GLToolbarItem::Left, (unsigned int)item_id, parent, true); @@ -507,7 +508,7 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) m_mouse_capture.right = true; m_mouse_capture.parent = &parent; processed = true; - if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action() == GLToolbarItem::Right))) + if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Right))) { // mouse is inside an icon do_action(GLToolbarItem::Right, (unsigned int)item_id, parent, true); @@ -633,7 +634,7 @@ void GLToolbar::do_action(GLToolbarItem::EActionType type, unsigned int item_id, item->set_state(GLToolbarItem::Pressed); m_pressed_toggable_id = item->is_pressed() ? item_id : -1; - item->reset_last_action(); + item->reset_last_action_type(); parent.render(); switch (type) @@ -650,7 +651,7 @@ void GLToolbar::do_action(GLToolbarItem::EActionType type, unsigned int item_id, else item->set_state(item->is_hovered() ? GLToolbarItem::HoverPressed : GLToolbarItem::Pressed); - item->reset_last_action(); + item->reset_last_action_type(); parent.render(); switch (type) { diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 86cd1a7fc2..34a92ea348 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -66,25 +66,30 @@ public: struct Data { + struct Option + { + bool toggable; + ActionCallback action_callback; + RenderCallback render_callback; + + Option(); + + bool can_render() const { return toggable && (render_callback != nullptr); } + }; + std::string name; #if ENABLE_SVG_ICONS std::string icon_filename; #endif // ENABLE_SVG_ICONS std::string tooltip; unsigned int sprite_id; - bool left_toggable; - bool right_toggable; + // mouse left click + Option left; + // mouse right click + Option right; bool visible; - // action on left click - ActionCallback left_action_callback; - // action on right click - ActionCallback right_action_callback; VisibilityCallback visibility_callback; EnablingCallback enabling_callback; - // render callback on left click - RenderCallback left_render_callback; - // render callback on right click - RenderCallback right_render_callback; Data(); }; @@ -98,7 +103,7 @@ private: EType m_type; EState m_state; Data m_data; - EActionType m_last_action; + EActionType m_last_action_type; public: GLToolbarItem(EType type, const Data& data); @@ -112,24 +117,24 @@ public: #endif // ENABLE_SVG_ICONS const std::string& get_tooltip() const { return m_data.tooltip; } - void do_left_action() { m_last_action = Left; m_data.left_action_callback(); } - void do_right_action() { m_last_action = Right; m_data.right_action_callback(); } + void do_left_action() { m_last_action_type = Left; m_data.left.action_callback(); } + void do_right_action() { m_last_action_type = Right; m_data.right.action_callback(); } bool is_enabled() const { return m_state != Disabled; } bool is_disabled() const { return m_state == Disabled; } bool is_hovered() const { return (m_state == Hover) || (m_state == HoverPressed); } bool is_pressed() const { return (m_state == Pressed) || (m_state == HoverPressed); } - - bool is_left_toggable() const { return m_data.left_toggable; } - bool is_right_toggable() const { return m_data.right_toggable; } bool is_visible() const { return m_data.visible; } bool is_separator() const { return m_type == Separator; } - bool has_left_render_callback() const { return m_data.left_render_callback != nullptr; } - bool has_right_render_callback() const { return m_data.right_render_callback != nullptr; } + bool is_left_toggable() const { return m_data.left.toggable; } + bool is_right_toggable() const { return m_data.right.toggable; } - EActionType get_last_action() const { return m_last_action; } - void reset_last_action() { m_last_action = Undefined; } + bool has_left_render_callback() const { return m_data.left.render_callback != nullptr; } + bool has_right_render_callback() const { return m_data.right.render_callback != nullptr; } + + EActionType get_last_action_type() const { return m_last_action_type; } + void reset_last_action_type() { m_last_action_type = Undefined; } // returns true if the state changes bool update_visibility(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 06252e1e77..e826d748ec 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3432,7 +3432,7 @@ void Plater::priv::init_view_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("3D editor view")) + " [" + GUI::shortkey_ctrl_prefix() + "5]"; item.sprite_id = 0; - item.left_action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; + item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; if (!view_toolbar.add_item(item)) return; @@ -3442,7 +3442,7 @@ void Plater::priv::init_view_toolbar() #endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Preview")) + " [" + GUI::shortkey_ctrl_prefix() + "6]"; item.sprite_id = 1; - item.left_action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; + item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; if (!view_toolbar.add_item(item)) return; From cc70c8dff9c6b9395845caba0542954276081613 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 12 Jul 2019 10:13:35 +0200 Subject: [PATCH 309/627] Tech ENABLE_SVG_ICONS set as default --- src/libslic3r/Technologies.hpp | 9 -- src/slic3r/GUI/GLCanvas3D.cpp | 124 -------------- src/slic3r/GUI/GLCanvas3D.hpp | 4 - src/slic3r/GUI/GLToolbar.cpp | 161 +------------------ src/slic3r/GUI/GLToolbar.hpp | 52 ------ src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 6 - src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 8 - src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 5 - src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 4 - src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 5 - src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp | 4 - src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 5 - src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 4 - src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 13 -- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 4 - src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 5 - src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 4 - src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 5 - src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 4 - src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 152 +---------------- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 14 -- src/slic3r/GUI/Plater.cpp | 18 --- 22 files changed, 2 insertions(+), 608 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index f05bc0b577..ae43369d22 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -41,13 +41,4 @@ #define ENABLE_TEXTURES_FROM_SVG (1 && ENABLE_1_42_0_ALPHA7) -//==================== -// 1.42.0.alpha8 techs -//==================== -#define ENABLE_1_42_0_ALPHA8 1 - -// Toolbars and Gizmos use icons imported from svg files -#define ENABLE_SVG_ICONS (1 && ENABLE_1_42_0_ALPHA8 && ENABLE_TEXTURES_FROM_SVG) - - #endif // _technologies_h_ diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d15d9aa4d3..ec77460921 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1199,11 +1199,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , m_bed(bed) , m_camera(camera) , m_view_toolbar(view_toolbar) -#if ENABLE_SVG_ICONS , m_toolbar(GLToolbar::Normal, "Top") -#else - , m_toolbar(GLToolbar::Normal) -#endif // ENABLE_SVG_ICONS , m_use_clipping_planes(false) , m_sidebar_field("") , m_keep_dirty(false) @@ -3422,12 +3418,6 @@ bool GLCanvas3D::_init_toolbar() if (!m_toolbar.is_enabled()) return true; -#if !ENABLE_SVG_ICONS - ItemsIconsTexture::Metadata icons_data; - icons_data.filename = "toolbar.png"; - icons_data.icon_size = 37; -#endif // !ENABLE_SVG_ICONS - BackgroundTexture::Metadata background_data; background_data.filename = "toolbar_background.png"; background_data.left = 16; @@ -3435,11 +3425,7 @@ bool GLCanvas3D::_init_toolbar() background_data.right = 16; background_data.bottom = 16; -#if ENABLE_SVG_ICONS if (!m_toolbar.init(background_data)) -#else - if (!m_toolbar.init(icons_data, background_data)) -#endif // ENABLE_SVG_ICONS { // unable to init the toolbar texture, disable it m_toolbar.set_enabled(false); @@ -3456,9 +3442,7 @@ bool GLCanvas3D::_init_toolbar() GLToolbarItem::Data item; item.name = "add"; -#if ENABLE_SVG_ICONS item.icon_filename = "add.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]"; item.sprite_id = 0; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; @@ -3466,9 +3450,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "delete"; -#if ENABLE_SVG_ICONS item.icon_filename = "remove.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Delete")) + " [Del]"; item.sprite_id = 1; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; @@ -3477,9 +3459,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "deleteall"; -#if ENABLE_SVG_ICONS item.icon_filename = "delete_all.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; item.sprite_id = 2; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; @@ -3488,9 +3468,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "arrange"; -#if ENABLE_SVG_ICONS item.icon_filename = "arrange.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Arrange")) + " [A]"; item.sprite_id = 3; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; @@ -3502,9 +3480,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "copy"; -#if ENABLE_SVG_ICONS item.icon_filename = "copy.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]"; item.sprite_id = 4; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; @@ -3513,9 +3489,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "paste"; -#if ENABLE_SVG_ICONS item.icon_filename = "paste.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]"; item.sprite_id = 5; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; @@ -3527,9 +3501,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "more"; -#if ENABLE_SVG_ICONS item.icon_filename = "instance_add.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Add instance")) + " [+]"; item.sprite_id = 6; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; @@ -3539,9 +3511,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "fewer"; -#if ENABLE_SVG_ICONS item.icon_filename = "instance_remove.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Remove instance")) + " [-]"; item.sprite_id = 7; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; @@ -3554,9 +3524,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "splitobjects"; -#if ENABLE_SVG_ICONS item.icon_filename = "split_objects.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Split to objects")); item.sprite_id = 8; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; @@ -3566,9 +3534,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "splitvolumes"; -#if ENABLE_SVG_ICONS item.icon_filename = "split_parts.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Split to parts")); item.sprite_id = 9; item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; @@ -3581,9 +3547,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "layersediting"; -#if ENABLE_SVG_ICONS item.icon_filename = "layers_white.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Layers editing")); item.sprite_id = 10; item.is_toggable = true; @@ -3921,9 +3885,6 @@ void GLCanvas3D::_render_overlays() const _render_gizmos_overlay(); _render_warning_texture(); _render_legend_texture(); -#if !ENABLE_SVG_ICONS - _resize_toolbars(); -#endif // !ENABLE_SVG_ICONS _render_toolbar(); _render_view_toolbar(); @@ -4000,7 +3961,6 @@ void GLCanvas3D::_render_current_gizmo() const void GLCanvas3D::_render_gizmos_overlay() const { -#if ENABLE_SVG_ICONS #if ENABLE_RETINA_GL // m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor()); const float scale = m_retina_helper->get_scale_factor()*wxGetApp().toolbar_icon_scale(); @@ -4011,14 +3971,12 @@ void GLCanvas3D::_render_gizmos_overlay() const const float size = int(GLGizmosManager::Default_Icons_Size*wxGetApp().toolbar_icon_scale()); m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment #endif /* __WXMSW__ */ -#endif // ENABLE_SVG_ICONS m_gizmos.render_overlay(*this, m_selection); } void GLCanvas3D::_render_toolbar() const { -#if ENABLE_SVG_ICONS #if ENABLE_RETINA_GL // m_toolbar.set_scale(m_retina_helper->get_scale_factor()); const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true); @@ -4073,20 +4031,12 @@ void GLCanvas3D::_render_toolbar() const } } m_toolbar.set_position(top, left); -#else -#if ENABLE_RETINA_GL - m_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); -#else - m_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); -#endif /* __WXMSW__ */ -#endif // ENABLE_SVG_ICONS m_toolbar.render(*this); } void GLCanvas3D::_render_view_toolbar() const { -#if ENABLE_SVG_ICONS #if ENABLE_RETINA_GL // m_view_toolbar.set_scale(m_retina_helper->get_scale_factor()); const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(); @@ -4106,13 +4056,6 @@ void GLCanvas3D::_render_view_toolbar() const float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; m_view_toolbar.set_position(top, left); -#else -#if ENABLE_RETINA_GL - m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); -#else - m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); -#endif /* __WXMSW__ */ -#endif // ENABLE_SVG_ICONS m_view_toolbar.render(*this); } @@ -5555,73 +5498,6 @@ bool GLCanvas3D::_is_any_volume_outside() const return false; } -#if !ENABLE_SVG_ICONS -void GLCanvas3D::_resize_toolbars() const -{ - Size cnv_size = get_canvas_size(); - float zoom = (float)m_camera.get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - -#if ENABLE_RETINA_GL - m_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); -#else - m_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); -#endif /* __WXMSW__ */ - - GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation(); - - switch (m_toolbar.get_layout_type()) - { - default: - case GLToolbar::Layout::Horizontal: - { - // centers the toolbar on the top edge of the 3d scene - float top, left; - if (orientation == GLToolbar::Layout::Top) - { - top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - left = -0.5f * m_toolbar.get_width() * inv_zoom; - } - else - { - top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; - left = -0.5f * m_toolbar.get_width() * inv_zoom; - } - m_toolbar.set_position(top, left); - break; - } - case GLToolbar::Layout::Vertical: - { - // centers the toolbar on the right edge of the 3d scene - float top, left; - if (orientation == GLToolbar::Layout::Left) - { - top = 0.5f * m_toolbar.get_height() * inv_zoom; - left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; - } - else - { - top = 0.5f * m_toolbar.get_height() * inv_zoom; - left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom; - } - m_toolbar.set_position(top, left); - break; - } - } - -#if ENABLE_RETINA_GL - m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); -#else - m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); -#endif /* __WXMSW__ */ - - // places the toolbar on the bottom-left corner of the 3d scene - float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; - float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; - m_view_toolbar.set_position(top, left); -} -#endif // !ENABLE_SVG_ICONS - void GLCanvas3D::_update_selection_from_hover() { bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 882455a019..2408e02730 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -728,10 +728,6 @@ private: bool _is_any_volume_outside() const; -#if !ENABLE_SVG_ICONS - void _resize_toolbars() const; -#endif // !ENABLE_SVG_ICONS - // updates the selection from the content of m_hover_volume_idxs void _update_selection_from_hover(); diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index a927eba9b1..97bb957ea2 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -38,9 +38,7 @@ const GLToolbarItem::EnabledStateCallback GLToolbarItem::Default_Enabled_State_C GLToolbarItem::Data::Data() : name("") -#if ENABLE_SVG_ICONS , icon_filename("") -#endif // ENABLE_SVG_ICONS , tooltip("") , sprite_id(-1) , is_toggable(false) @@ -105,14 +103,6 @@ GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int return uvs; } -#if !ENABLE_SVG_ICONS -ItemsIconsTexture::Metadata::Metadata() - : filename("") - , icon_size(0) -{ -} -#endif // !ENABLE_SVG_ICONS - BackgroundTexture::Metadata::Metadata() : filename("") , left(0) @@ -122,9 +112,7 @@ BackgroundTexture::Metadata::Metadata() { } -#if ENABLE_SVG_ICONS const float GLToolbar::Default_Icons_Size = 40.0f; -#endif // ENABLE_SVG_ICONS GLToolbar::Layout::Layout() : type(Horizontal) @@ -134,31 +122,19 @@ GLToolbar::Layout::Layout() , border(0.0f) , separator_size(0.0f) , gap_size(0.0f) -#if ENABLE_SVG_ICONS , icons_size(Default_Icons_Size) , scale(1.0f) -#else - , icons_scale(1.0f) -#endif // ENABLE_SVG_ICONS , width(0.0f) , height(0.0f) , dirty(true) { } -#if ENABLE_SVG_ICONS GLToolbar::GLToolbar(GLToolbar::EType type, const std::string& name) -#else -GLToolbar::GLToolbar(GLToolbar::EType type) -#endif // ENABLE_SVG_ICONS : m_type(type) -#if ENABLE_SVG_ICONS , m_name(name) -#endif // ENABLE_SVG_ICONS , m_enabled(false) -#if ENABLE_SVG_ICONS , m_icons_texture_dirty(true) -#endif // ENABLE_SVG_ICONS , m_tooltip("") { } @@ -171,27 +147,13 @@ GLToolbar::~GLToolbar() } } -#if ENABLE_SVG_ICONS bool GLToolbar::init(const BackgroundTexture::Metadata& background_texture) -#else -bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture) -#endif // ENABLE_SVG_ICONS { -#if ENABLE_SVG_ICONS if (m_background_texture.texture.get_id() != 0) return true; std::string path = resources_dir() + "/icons/"; bool res = false; -#else - if (m_icons_texture.texture.get_id() != 0) - return true; - - std::string path = resources_dir() + "/icons/"; - bool res = !icons_texture.filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture.filename, false, true); - if (res) - m_icons_texture.metadata = icons_texture; -#endif // ENABLE_SVG_ICONS if (!background_texture.filename.empty()) res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, true); @@ -247,7 +209,6 @@ void GLToolbar::set_gap_size(float size) m_layout.dirty = true; } -#if ENABLE_SVG_ICONS void GLToolbar::set_icons_size(float size) { if (m_layout.icons_size != size) @@ -267,13 +228,6 @@ void GLToolbar::set_scale(float scale) m_icons_texture_dirty = true; } } -#else -void GLToolbar::set_icons_scale(float scale) -{ - m_layout.icons_scale = scale; - m_layout.dirty = true; -} -#endif // ENABLE_SVG_ICONS bool GLToolbar::is_enabled() const { @@ -385,10 +339,8 @@ void GLToolbar::render(const GLCanvas3D& parent) const if (!m_enabled || m_items.empty()) return; -#if ENABLE_SVG_ICONS if (m_icons_texture_dirty) generate_icons_texture(); -#endif // ENABLE_SVG_ICONS switch (m_layout.type) { @@ -492,20 +444,12 @@ float GLToolbar::get_width_horizontal() const float GLToolbar::get_width_vertical() const { -#if ENABLE_SVG_ICONS return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; -#else - return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS } float GLToolbar::get_height_horizontal() const { -#if ENABLE_SVG_ICONS return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; -#else - return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS } float GLToolbar::get_height_vertical() const @@ -515,7 +459,6 @@ float GLToolbar::get_height_vertical() const float GLToolbar::get_main_size() const { -#if ENABLE_SVG_ICONS float size = 2.0f * m_layout.border; for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) { @@ -531,25 +474,7 @@ float GLToolbar::get_main_size() const if (m_items.size() > 1) size += ((float)m_items.size() - 1.0f) * m_layout.gap_size; - size *= m_layout.scale; -#else - float size = 2.0f * m_layout.border * m_layout.icons_scale; - for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) - { - if (!m_items[i]->is_visible()) - continue; - - if (m_items[i]->is_separator()) - size += m_layout.separator_size * m_layout.icons_scale; - else - size += (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale; - } - - if (m_items.size() > 1) - size += ((float)m_items.size() - 1.0f) * m_layout.gap_size * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS - - return size; + return size * m_layout.scale; } void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) @@ -609,20 +534,12 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; -#else - float factor = m_layout.icons_scale * inv_zoom; -#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -714,20 +631,12 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; -#else - float factor = m_layout.icons_scale * inv_zoom; -#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -831,20 +740,12 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3 float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; -#else - float factor = m_layout.icons_scale * inv_zoom; -#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -914,20 +815,12 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = m_layout.scale * inv_zoom; -#else - float factor = m_layout.icons_scale * inv_zoom; -#endif // ENABLE_SVG_ICONS Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -993,34 +886,15 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& void GLToolbar::render_horizontal(const GLCanvas3D& parent) const { -#if ENABLE_SVG_ICONS unsigned int tex_id = m_icons_texture.get_id(); int tex_width = m_icons_texture.get_width(); int tex_height = m_icons_texture.get_height(); -#else - unsigned int tex_id = m_icons_texture.texture.get_id(); - int tex_width = m_icons_texture.texture.get_width(); - int tex_height = m_icons_texture.texture.get_height(); -#endif // ENABLE_SVG_ICONS - -#if !ENABLE_SVG_ICONS - if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) - return; -#endif // !ENABLE_SVG_ICONS float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = inv_zoom * m_layout.scale; -#else - float factor = inv_zoom * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -1121,10 +995,8 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const left += scaled_border; top -= scaled_border; -#if ENABLE_SVG_ICONS if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; -#endif // ENABLE_SVG_ICONS // renders icons for (const GLToolbarItem* item : m_items) @@ -1136,11 +1008,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const left += separator_stride; else { -#if ENABLE_SVG_ICONS item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); -#else - item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, m_icons_texture.metadata.icon_size); -#endif // ENABLE_SVG_ICONS left += icon_stride; } } @@ -1148,34 +1016,15 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const void GLToolbar::render_vertical(const GLCanvas3D& parent) const { -#if ENABLE_SVG_ICONS unsigned int tex_id = m_icons_texture.get_id(); int tex_width = m_icons_texture.get_width(); int tex_height = m_icons_texture.get_height(); -#else - unsigned int tex_id = m_icons_texture.texture.get_id(); - int tex_width = m_icons_texture.texture.get_width(); - int tex_height = m_icons_texture.texture.get_height(); -#endif // ENABLE_SVG_ICONS - -#if !ENABLE_SVG_ICONS - if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) - return; -#endif // !ENABLE_SVG_ICONS float zoom = (float)parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#if ENABLE_SVG_ICONS float factor = inv_zoom * m_layout.scale; -#else - float factor = inv_zoom * m_layout.icons_scale; -#endif // ENABLE_SVG_ICONS -#if ENABLE_SVG_ICONS float scaled_icons_size = m_layout.icons_size * factor; -#else - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * factor; -#endif // ENABLE_SVG_ICONS float scaled_separator_size = m_layout.separator_size * factor; float scaled_gap_size = m_layout.gap_size * factor; float scaled_border = m_layout.border * factor; @@ -1276,10 +1125,8 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const left += scaled_border; top -= scaled_border; -#if ENABLE_SVG_ICONS if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; -#endif // ENABLE_SVG_ICONS // renders icons for (const GLToolbarItem* item : m_items) @@ -1291,17 +1138,12 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const top -= separator_stride; else { -#if ENABLE_SVG_ICONS item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); -#else - item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, m_icons_texture.metadata.icon_size); -#endif // ENABLE_SVG_ICONS top -= icon_stride; } } } -#if ENABLE_SVG_ICONS bool GLToolbar::generate_icons_texture() const { std::string path = resources_dir() + "/icons/"; @@ -1337,7 +1179,6 @@ bool GLToolbar::generate_icons_texture() const return res; } -#endif // ENABLE_SVG_ICONS bool GLToolbar::update_items_visibility() { diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 24314d60ff..7b4cf8b10f 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -58,9 +58,7 @@ public: struct Data { std::string name; -#if ENABLE_SVG_ICONS std::string icon_filename; -#endif // ENABLE_SVG_ICONS std::string tooltip; unsigned int sprite_id; bool is_toggable; @@ -88,9 +86,7 @@ public: void set_state(EState state) { m_state = state; } const std::string& get_name() const { return m_data.name; } -#if ENABLE_SVG_ICONS const std::string& get_icon_filename() const { return m_data.icon_filename; } -#endif // ENABLE_SVG_ICONS const std::string& get_tooltip() const { return m_data.tooltip; } void do_action() { m_data.action_callback(); } @@ -118,27 +114,6 @@ private: friend class GLToolbar; }; -#if !ENABLE_SVG_ICONS -// items icon textures are assumed to be square and all with the same size in pixels, no internal check is done -// icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState -// from left to right -struct ItemsIconsTexture -{ - struct Metadata - { - // path of the file containing the icons' texture - std::string filename; - // size of the square icons, in pixels - unsigned int icon_size; - - Metadata(); - }; - - GLTexture texture; - Metadata metadata; -}; -#endif // !ENABLE_SVG_ICONS - struct BackgroundTexture { struct Metadata @@ -164,9 +139,7 @@ struct BackgroundTexture class GLToolbar { public: -#if ENABLE_SVG_ICONS static const float Default_Icons_Size; -#endif // ENABLE_SVG_ICONS enum EType : unsigned char { @@ -201,12 +174,8 @@ public: float border; float separator_size; float gap_size; -#if ENABLE_SVG_ICONS float icons_size; float scale; -#else - float icons_scale; -#endif // ENABLE_SVG_ICONS float width; float height; @@ -219,16 +188,10 @@ private: typedef std::vector ItemsList; EType m_type; -#if ENABLE_SVG_ICONS std::string m_name; -#endif // ENABLE_SVG_ICONS bool m_enabled; -#if ENABLE_SVG_ICONS mutable GLTexture m_icons_texture; mutable bool m_icons_texture_dirty; -#else - ItemsIconsTexture m_icons_texture; -#endif // ENABLE_SVG_ICONS BackgroundTexture m_background_texture; mutable Layout m_layout; @@ -251,18 +214,10 @@ private: std::string m_tooltip; public: -#if ENABLE_SVG_ICONS GLToolbar(EType type, const std::string& name); -#else - explicit GLToolbar(EType type); -#endif // ENABLE_SVG_ICONS ~GLToolbar(); -#if ENABLE_SVG_ICONS bool init(const BackgroundTexture::Metadata& background_texture); -#else - bool init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture); -#endif // ENABLE_SVG_ICONS Layout::EType get_layout_type() const; void set_layout_type(Layout::EType type); @@ -273,12 +228,8 @@ public: void set_border(float border); void set_separator_size(float size); void set_gap_size(float size); -#if ENABLE_SVG_ICONS void set_icons_size(float size); void set_scale(float scale); -#else - void set_icons_scale(float scale); -#endif // ENABLE_SVG_ICONS bool is_enabled() const; void set_enabled(bool enable); @@ -297,7 +248,6 @@ public: const std::string& get_tooltip() const { return m_tooltip; } - // returns true if any item changed its state bool update_items_state(); @@ -324,9 +274,7 @@ private: void render_horizontal(const GLCanvas3D& parent) const; void render_vertical(const GLCanvas3D& parent) const; -#if ENABLE_SVG_ICONS bool generate_icons_texture() const; -#endif // ENABLE_SVG_ICONS // returns true if any item changed its state bool update_items_visibility(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 030bd0146e..7da3dec8aa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -132,18 +132,12 @@ void GLGizmoBase::Grabber::render_face(float half_size) const glsafe(::glEnd()); } -#if ENABLE_SVG_ICONS GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) -#else -GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id) -#endif // ENABLE_SVG_ICONS : m_parent(parent) , m_group_id(-1) , m_state(Off) , m_shortcut_key(0) -#if ENABLE_SVG_ICONS , m_icon_filename(icon_filename) -#endif // ENABLE_SVG_ICONS , m_sprite_id(sprite_id) , m_hover_id(-1) , m_dragging(false) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index a29aaed3fe..73c73a2c96 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -89,9 +89,7 @@ protected: int m_group_id; EState m_state; int m_shortcut_key; -#if ENABLE_SVG_ICONS std::string m_icon_filename; -#endif // ENABLE_SVG_ICONS unsigned int m_sprite_id; int m_hover_id; bool m_dragging; @@ -102,11 +100,7 @@ protected: ImGuiWrapper* m_imgui; public: -#if ENABLE_SVG_ICONS GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS virtual ~GLGizmoBase() {} bool init() { return on_init(); } @@ -122,9 +116,7 @@ public: int get_shortcut_key() const { return m_shortcut_key; } void set_shortcut_key(int key) { m_shortcut_key = key; } -#if ENABLE_SVG_ICONS const std::string& get_icon_filename() const { return m_icon_filename; } -#endif // ENABLE_SVG_ICONS bool is_activable(const Selection& selection) const { return on_is_activable(selection); } bool is_selectable() const { return on_is_selectable(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 17db953d46..b1c2950983 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -19,13 +19,8 @@ const double GLGizmoCut::Offset = 10.0; const double GLGizmoCut::Margin = 20.0; const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0 }; -#if ENABLE_SVG_ICONS GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_cut_z(0.0) , m_max_z(0.0) , m_keep_upper(true) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index fd4e8d8dc0..79a7c58e27 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -23,11 +23,7 @@ class GLGizmoCut : public GLGizmoBase bool m_rotate_lower; public: -#if ENABLE_SVG_ICONS GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS protected: virtual bool on_init(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index ec991a2413..6bb81a7032 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -9,13 +9,8 @@ namespace Slic3r { namespace GUI { -#if ENABLE_SVG_ICONS GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_normal(Vec3d::Zero()) , m_starting_center(Vec3d::Zero()) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp index 1bd17e5ef1..926dc34576 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -37,11 +37,7 @@ private: bool is_plane_update_necessary() const; public: -#if ENABLE_SVG_ICONS GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS void set_flattening_data(const ModelObject* model_object); Vec3d get_flattening_normal() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index a2ea738f5f..b97f578b3a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -10,13 +10,8 @@ namespace GUI { const double GLGizmoMove3D::Offset = 10.0; -#if ENABLE_SVG_ICONS GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_displacement(Vec3d::Zero()) , m_snap_step(1.0) , m_starting_drag_position(Vec3d::Zero()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index ddab2b777d..7714967808 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -22,11 +22,7 @@ class GLGizmoMove3D : public GLGizmoBase GLUquadricObj* m_quadric; public: -#if ENABLE_SVG_ICONS GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS virtual ~GLGizmoMove3D(); double get_snap_step(double step) const { return m_snap_step; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 68b9a8c332..f84c3886d7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -19,11 +19,7 @@ const unsigned int GLGizmoRotate::SnapRegionsCount = 8; const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) -#if ENABLE_SVG_ICONS : GLGizmoBase(parent, "", -1) -#else - : GLGizmoBase(parent, -1) -#endif // ENABLE_SVG_ICONS , m_axis(axis) , m_angle(0.0) , m_quadric(nullptr) @@ -40,11 +36,7 @@ GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) } GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other) -#if ENABLE_SVG_ICONS : GLGizmoBase(other.m_parent, other.m_icon_filename, other.m_sprite_id) -#else - : GLGizmoBase(other.m_parent, other.m_sprite_id) -#endif // ENABLE_SVG_ICONS , m_axis(other.m_axis) , m_angle(other.m_angle) , m_quadric(nullptr) @@ -417,13 +409,8 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons return transform(mouse_ray, m).intersect_plane(0.0); } -#if ENABLE_SVG_ICONS GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS { m_gizmos.emplace_back(parent, GLGizmoRotate::X); m_gizmos.emplace_back(parent, GLGizmoRotate::Y); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index c65dee4d89..d2e5649669 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -76,11 +76,7 @@ class GLGizmoRotate3D : public GLGizmoBase std::vector m_gizmos; public: -#if ENABLE_SVG_ICONS GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index ca5383b0d5..d30ceb092a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -13,13 +13,8 @@ namespace GUI { const float GLGizmoScale3D::Offset = 5.0f; -#if ENABLE_SVG_ICONS GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_scale(Vec3d::Ones()) , m_offset(Vec3d::Zero()) , m_snap_step(0.05) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 3b0717f04f..16307165f6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -32,11 +32,7 @@ class GLGizmoScale3D : public GLGizmoBase StartingData m_starting; public: -#if ENABLE_SVG_ICONS GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS double get_snap_step(double step) const { return m_snap_step; } void set_snap_step(double step) { m_snap_step = step; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 7e2b558d94..7b6cb0cc23 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -19,13 +19,8 @@ namespace Slic3r { namespace GUI { -#if ENABLE_SVG_ICONS GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) -#else -GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id) - : GLGizmoBase(parent, sprite_id) -#endif // ENABLE_SVG_ICONS , m_quadric(nullptr) , m_its(nullptr) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 30238cc9dd..f4b2fbb0e5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -58,11 +58,7 @@ private: }; public: -#if ENABLE_SVG_ICONS GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); -#else - GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id); -#endif // ENABLE_SVG_ICONS virtual ~GLGizmoSlaSupports(); void set_sla_support_data(ModelObject* model_object, const Selection& selection); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index c9005807de..315027dadf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -12,17 +12,12 @@ namespace Slic3r { namespace GUI { -#if ENABLE_SVG_ICONS - const float GLGizmosManager::Default_Icons_Size = 64; -#endif // ENABLE_SVG_ICONS +const float GLGizmosManager::Default_Icons_Size = 64; GLGizmosManager::GLGizmosManager() : m_enabled(false) -#if ENABLE_SVG_ICONS , m_icons_texture_dirty(true) -#endif // ENABLE_SVG_ICONS , m_current(Undefined) -#if ENABLE_SVG_ICONS , m_overlay_icons_size(Default_Icons_Size) , m_overlay_scale(1.0f) , m_overlay_border(5.0f) @@ -30,11 +25,6 @@ GLGizmosManager::GLGizmosManager() , m_tooltip("") { } -#else -{ - set_overlay_scale(1.0); -} -#endif // ENABLE_SVG_ICONS GLGizmosManager::~GLGizmosManager() { @@ -43,20 +33,6 @@ GLGizmosManager::~GLGizmosManager() bool GLGizmosManager::init(GLCanvas3D& parent) { -#if !ENABLE_SVG_ICONS - m_icons_texture.metadata.filename = "gizmos.png"; - m_icons_texture.metadata.icon_size = 64; - - if (!m_icons_texture.metadata.filename.empty()) - { - if (!m_icons_texture.texture.load_from_file(resources_dir() + "/icons/" + m_icons_texture.metadata.filename, false, true)) - { - reset(); - return false; - } - } -#endif // !ENABLE_SVG_ICONS - m_background_texture.metadata.filename = "toolbar_background.png"; m_background_texture.metadata.left = 16; m_background_texture.metadata.top = 16; @@ -72,11 +48,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) } } -#if ENABLE_SVG_ICONS GLGizmoBase* gizmo = new GLGizmoMove3D(parent, "move.svg", 0); -#else - GLGizmoBase* gizmo = new GLGizmoMove3D(parent, 0); -#endif // ENABLE_SVG_ICONS if (gizmo == nullptr) return false; @@ -85,11 +57,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Move, gizmo)); -#if ENABLE_SVG_ICONS gizmo = new GLGizmoScale3D(parent, "scale.svg", 1); -#else - gizmo = new GLGizmoScale3D(parent, 1); -#endif // ENABLE_SVG_ICONS if (gizmo == nullptr) return false; @@ -98,11 +66,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Scale, gizmo)); -#if ENABLE_SVG_ICONS gizmo = new GLGizmoRotate3D(parent, "rotate.svg", 2); -#else - gizmo = new GLGizmoRotate3D(parent, 2); -#endif // ENABLE_SVG_ICONS if (gizmo == nullptr) { reset(); @@ -117,11 +81,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo)); -#if ENABLE_SVG_ICONS gizmo = new GLGizmoFlatten(parent, "place.svg", 3); -#else - gizmo = new GLGizmoFlatten(parent, 3); -#endif // ENABLE_SVG_ICONS if (gizmo == nullptr) return false; @@ -132,11 +92,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Flatten, gizmo)); -#if ENABLE_SVG_ICONS gizmo = new GLGizmoCut(parent, "cut.svg", 4); -#else - gizmo = new GLGizmoCut(parent, 4); -#endif // ENABLE_SVG_ICONS if (gizmo == nullptr) return false; @@ -147,11 +103,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Cut, gizmo)); -#if ENABLE_SVG_ICONS gizmo = new GLGizmoSlaSupports(parent, "sla_supports.svg", 5); -#else - gizmo = new GLGizmoSlaSupports(parent, 5); -#endif // ENABLE_SVG_ICONS if (gizmo == nullptr) return false; @@ -165,7 +117,6 @@ bool GLGizmosManager::init(GLCanvas3D& parent) return true; } -#if ENABLE_SVG_ICONS void GLGizmosManager::set_overlay_icon_size(float size) { if (m_overlay_icons_size != size) @@ -174,21 +125,14 @@ void GLGizmosManager::set_overlay_icon_size(float size) m_icons_texture_dirty = true; } } -#endif // ENABLE_SVG_ICONS void GLGizmosManager::set_overlay_scale(float scale) { -#if ENABLE_SVG_ICONS if (m_overlay_scale != scale) { m_overlay_scale = scale; m_icons_texture_dirty = true; } -#else - m_overlay_icons_scale = scale; - m_overlay_border = 5.0f * scale; - m_overlay_gap_y = 5.0f * scale; -#endif // ENABLE_SVG_ICONS } void GLGizmosManager::refresh_on_off_state(const Selection& selection) @@ -526,10 +470,8 @@ void GLGizmosManager::render_overlay(const GLCanvas3D& canvas, const Selection& if (!m_enabled) return; -#if ENABLE_SVG_ICONS if (m_icons_texture_dirty) generate_icons_texture(); -#endif // ENABLE_SVG_ICONS do_render_overlay(canvas, selection); } @@ -935,11 +877,7 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio float height = get_total_overlay_height(); float width = get_total_overlay_width(); -#if ENABLE_SVG_ICONS float scaled_border = m_overlay_border * m_overlay_scale * inv_zoom; -#else - float scaled_border = m_overlay_border * inv_zoom; -#endif // ENABLE_SVG_ICONS float top_x = (-0.5f * cnv_w) * inv_zoom; float top_y = (0.5f * height) * inv_zoom; @@ -1015,7 +953,6 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio } } -#if ENABLE_SVG_ICONS top_x += scaled_border; top_y -= scaled_border; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale * inv_zoom; @@ -1027,21 +964,9 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio unsigned int tex_height = m_icons_texture.get_height(); float inv_tex_width = (tex_width != 0) ? 1.0f / (float)tex_width : 0.0f; float inv_tex_height = (tex_height != 0) ? 1.0f / (float)tex_height : 0.0f; -#else - top_x += m_overlay_border * inv_zoom; - top_y -= m_overlay_border * inv_zoom; - float scaled_gap_y = m_overlay_gap_y * inv_zoom; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale * inv_zoom; - unsigned int icons_texture_id = m_icons_texture.texture.get_id(); - unsigned int texture_size = m_icons_texture.texture.get_width(); - float inv_texture_size = (texture_size != 0) ? 1.0f / (float)texture_size : 0.0f; -#endif // ENABLE_SVG_ICONS - -#if ENABLE_SVG_ICONS if ((icons_texture_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; -#endif // ENABLE_SVG_ICONS for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { @@ -1051,78 +976,44 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio unsigned int sprite_id = it->second->get_sprite_id(); GLGizmoBase::EState state = it->second->get_state(); -#if ENABLE_SVG_ICONS float u_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_width; float v_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_height; float v_top = sprite_id * v_icon_size; float u_left = state * u_icon_size; float v_bottom = v_top + v_icon_size; float u_right = u_left + u_icon_size; -#else - float uv_icon_size = (float)m_icons_texture.metadata.icon_size * inv_texture_size; - float v_top = sprite_id * uv_icon_size; - float u_left = state * uv_icon_size; - float v_bottom = v_top + uv_icon_size; - float u_right = u_left + uv_icon_size; -#endif // ENABLE_SVG_ICONS GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + scaled_icons_size, top_y - scaled_icons_size, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); if (it->second->get_state() == GLGizmoBase::On) { float toolbar_top = (float)cnv_h - canvas.get_view_toolbar_height(); -#if ENABLE_SVG_ICONS it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); -#else - it->second->render_input_window(2.0f * m_overlay_border + scaled_icons_size * zoom, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); -#endif // ENABLE_SVG_ICONS } -#if ENABLE_SVG_ICONS top_y -= scaled_stride_y; -#else - top_y -= (scaled_icons_size + scaled_gap_y); -#endif // ENABLE_SVG_ICONS } } float GLGizmosManager::get_total_overlay_height() const { -#if ENABLE_SVG_ICONS float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; float scaled_stride_y = scaled_icons_size + scaled_gap_y; float height = 2.0f * scaled_border; -#else - float height = 2.0f * m_overlay_border; - - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; -#if ENABLE_SVG_ICONS height += scaled_stride_y; -#else - height += (scaled_icons_size + m_overlay_gap_y); -#endif // ENABLE_SVG_ICONS } -#if ENABLE_SVG_ICONS return height - scaled_gap_y; -#else - return height - m_overlay_gap_y; -#endif // ENABLE_SVG_ICONS } float GLGizmosManager::get_total_overlay_width() const { -#if ENABLE_SVG_ICONS return (2.0f * m_overlay_border + m_overlay_icons_size) * m_overlay_scale; -#else - return (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale + 2.0f * m_overlay_border; -#endif // ENABLE_SVG_ICONS } GLGizmoBase* GLGizmosManager::get_current() const @@ -1131,7 +1022,6 @@ GLGizmoBase* GLGizmosManager::get_current() const return (it != m_gizmos.end()) ? it->second : nullptr; } -#if ENABLE_SVG_ICONS bool GLGizmosManager::generate_icons_texture() const { std::string path = resources_dir() + "/icons/"; @@ -1157,7 +1047,6 @@ bool GLGizmosManager::generate_icons_texture() const return res; } -#endif // ENABLE_SVG_ICONS void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection) { @@ -1167,27 +1056,18 @@ void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = get_total_overlay_height(); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; float scaled_stride_y = scaled_icons_size + scaled_gap_y; float top_y = 0.5f * (cnv_h - height) + scaled_border; -#else - float top_y = 0.5f * (cnv_h - height) + m_overlay_border; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; -#if ENABLE_SVG_ICONS bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); -#else - bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); -#endif // ENABLE_SVG_ICONS if (it->second->is_activable(selection) && inside) { if ((it->second->get_state() == GLGizmoBase::On)) @@ -1204,11 +1084,7 @@ void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& else it->second->set_state(GLGizmoBase::Off); -#if ENABLE_SVG_ICONS top_y += scaled_stride_y; -#else - top_y += (scaled_icons_size + m_overlay_gap_y); -#endif // ENABLE_SVG_ICONS } GizmosMap::iterator it = m_gizmos.find(m_current); @@ -1227,38 +1103,25 @@ std::string GLGizmosManager::update_hover_state(const GLCanvas3D& canvas, const float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = get_total_overlay_height(); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; float scaled_stride_y = scaled_icons_size + scaled_gap_y; float top_y = 0.5f * (cnv_h - height) + scaled_border; -#else - float top_y = 0.5f * (cnv_h - height) + m_overlay_border; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; -#if ENABLE_SVG_ICONS bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); -#else - bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); -#endif // ENABLE_SVG_ICONS if (inside) name = it->second->get_name(); if (it->second->is_activable(selection) && (it->second->get_state() != GLGizmoBase::On)) it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off); -#if ENABLE_SVG_ICONS top_y += scaled_stride_y; -#else - top_y += (scaled_icons_size + m_overlay_gap_y); -#endif // ENABLE_SVG_ICONS } return name; @@ -1272,34 +1135,21 @@ bool GLGizmosManager::overlay_contains_mouse(const GLCanvas3D& canvas, const Vec float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = get_total_overlay_height(); -#if ENABLE_SVG_ICONS float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; float scaled_stride_y = scaled_icons_size + scaled_gap_y; float top_y = 0.5f * (cnv_h - height) + scaled_border; -#else - float top_y = 0.5f * (cnv_h - height) + m_overlay_border; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; -#if ENABLE_SVG_ICONS if ((scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size)) -#else - if ((m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size)) -#endif // ENABLE_SVG_ICONS return true; -#if ENABLE_SVG_ICONS top_y += scaled_stride_y; -#else - top_y += (scaled_icons_size + m_overlay_gap_y); -#endif // ENABLE_SVG_ICONS } return false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 1e42a29e68..87be7da41e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -46,9 +46,7 @@ public: class GLGizmosManager { public: -#if ENABLE_SVG_ICONS static const float Default_Icons_Size; -#endif // ENABLE_SVG_ICONS enum EType : unsigned char { @@ -66,21 +64,13 @@ private: bool m_enabled; typedef std::map GizmosMap; GizmosMap m_gizmos; -#if ENABLE_SVG_ICONS mutable GLTexture m_icons_texture; mutable bool m_icons_texture_dirty; -#else - ItemsIconsTexture m_icons_texture; -#endif // ENABLE_SVG_ICONS BackgroundTexture m_background_texture; EType m_current; -#if ENABLE_SVG_ICONS float m_overlay_icons_size; float m_overlay_scale; -#else - float m_overlay_icons_scale; -#endif // ENABLE_SVG_ICONS float m_overlay_border; float m_overlay_gap_y; @@ -109,9 +99,7 @@ public: bool is_enabled() const { return m_enabled; } void set_enabled(bool enable) { m_enabled = enable; } -#if ENABLE_SVG_ICONS void set_overlay_icon_size(float size); -#endif // ENABLE_SVG_ICONS void set_overlay_scale(float scale); void refresh_on_off_state(const Selection& selection); @@ -173,9 +161,7 @@ private: GLGizmoBase* get_current() const; -#if ENABLE_SVG_ICONS bool generate_icons_texture() const; -#endif // ENABLE_SVG_ICONS void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection); std::string update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2988278d79..402acd59cf 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1736,11 +1736,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) })) , sidebar(new Sidebar(q)) , delayed_scene_refresh(false) -#if ENABLE_SVG_ICONS , view_toolbar(GLToolbar::Radio, "View") -#else - , view_toolbar(GLToolbar::Radio) -#endif // ENABLE_SVG_ICONS , m_project_filename(wxEmptyString) { this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -3395,12 +3391,6 @@ bool Plater::priv::complit_init_part_menu() void Plater::priv::init_view_toolbar() { -#if !ENABLE_SVG_ICONS - ItemsIconsTexture::Metadata icons_data; - icons_data.filename = "view_toolbar.png"; - icons_data.icon_size = 64; -#endif // !ENABLE_SVG_ICONS - BackgroundTexture::Metadata background_data; background_data.filename = "toolbar_background.png"; background_data.left = 16; @@ -3408,11 +3398,7 @@ void Plater::priv::init_view_toolbar() background_data.right = 16; background_data.bottom = 16; -#if ENABLE_SVG_ICONS if (!view_toolbar.init(background_data)) -#else - if (!view_toolbar.init(icons_data, background_data)) -#endif // ENABLE_SVG_ICONS return; view_toolbar.set_layout_orientation(GLToolbar::Layout::Bottom); @@ -3422,9 +3408,7 @@ void Plater::priv::init_view_toolbar() GLToolbarItem::Data item; item.name = "3D"; -#if ENABLE_SVG_ICONS item.icon_filename = "editor.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("3D editor view")) + " [" + GUI::shortkey_ctrl_prefix() + "5]"; item.sprite_id = 0; item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; @@ -3433,9 +3417,7 @@ void Plater::priv::init_view_toolbar() return; item.name = "Preview"; -#if ENABLE_SVG_ICONS item.icon_filename = "preview.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Preview")) + " [" + GUI::shortkey_ctrl_prefix() + "6]"; item.sprite_id = 1; item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; From 2ee572bd31d38a90d87c85794c7d0a6fa71e552c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 12 Jul 2019 12:38:00 +0200 Subject: [PATCH 310/627] GCodeAnalyzer now recognizes tool-changing commands with MakerWare and Sailfish flavor These firmwares use M135 Tn and M108 Tn commands for changing active tool, which the analyzer did not recognize. The toolpaths were then rendered in wrong color, extruder offset etc. This surfaced in issue https://github.com/prusa3d/PrusaSlicer/issues/2566 --- src/libslic3r/GCode.cpp | 3 +++ src/libslic3r/GCode/Analyzer.cpp | 40 ++++++++++++++++++++++++++++++-- src/libslic3r/GCode/Analyzer.hpp | 7 ++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d2e5814935..5af90bc3f5 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -586,6 +586,9 @@ void GCode::_do_export(Print &print, FILE *file) } m_analyzer.set_extruder_offsets(extruder_offsets); + // tell analyzer about the gcode flavor + m_analyzer.set_gcode_flavor(print.config().gcode_flavor); + // resets analyzer's tracking data m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; m_last_width = GCodeAnalyzer::Default_Width; diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 321d9a3427..6cf118cc25 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -106,6 +106,11 @@ void GCodeAnalyzer::set_extruder_offsets(const GCodeAnalyzer::ExtruderOffsetsMap m_extruder_offsets = extruder_offsets; } +void GCodeAnalyzer::set_gcode_flavor(const GCodeFlavor& flavor) +{ + m_gcode_flavor = flavor; +} + void GCodeAnalyzer::reset() { _set_units(Millimeters); @@ -249,6 +254,14 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi _processM83(line); break; } + case 108: + case 135: + { + // these are used by MakerWare and Sailfish firmwares + // for tool changing - we can process it in one place + _processM108orM135(line); + break; + } } break; @@ -426,9 +439,27 @@ void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line) _set_cp_color_id(m_state.cur_cp_color_id); } -void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) +void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) +{ + // These M-codes are used by MakerWare and Sailfish to change active tool. + // They have to be processed otherwise toolchanges will be unrecognised + // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566 + + size_t code = ::atoi(&(line.cmd()[1])); + if ((code == 108 && m_gcode_flavor == gcfSailfish) + || (code == 135 && m_gcode_flavor == gcfMakerWare)) { + + std::string cmd = line.raw(); + size_t T_pos = cmd.find("T"); + if (T_pos != std::string::npos) { + cmd = cmd.substr(T_pos); + _processT(cmd); + } + } +} + +void GCodeAnalyzer::_processT(const std::string& cmd) { - std::string cmd = line.cmd(); if (cmd.length() > 1) { unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10); @@ -442,6 +473,11 @@ void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) } } +void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) +{ + _processT(line.cmd()); +} + bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line) { std::string comment = line.comment(); diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index 4c201c6403..ab8bade71b 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -106,6 +106,7 @@ private: GCodeReader m_parser; TypeToMovesMap m_moves_map; ExtruderOffsetsMap m_extruder_offsets; + GCodeFlavor m_gcode_flavor; // The output of process_layer() std::string m_process_output; @@ -115,6 +116,8 @@ public: void set_extruder_offsets(const ExtruderOffsetsMap& extruder_offsets); + void set_gcode_flavor(const GCodeFlavor& flavor); + // Reinitialize the analyzer void reset(); @@ -164,10 +167,14 @@ private: // Set extruder to relative mode void _processM83(const GCodeReader::GCodeLine& line); + // Set tool (MakerWare and Sailfish flavor) + void _processM108orM135(const GCodeReader::GCodeLine& line); + // Set color change void _processM600(const GCodeReader::GCodeLine& line); // Processes T line (Select Tool) + void _processT(const std::string& command); void _processT(const GCodeReader::GCodeLine& line); // Processes the tags From aed6acc073111d8d6134705e36894aedffba536c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 11 Jul 2019 18:42:02 +0200 Subject: [PATCH 311/627] Add take_snapshot for layers range editing actions --- src/slic3r/GUI/GUI_ObjectList.cpp | 37 ++++++++++++++++++++++++++----- src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- src/slic3r/GUI/Plater.cpp | 14 ++++++------ 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 360318413c..dc4bb87956 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1756,6 +1756,8 @@ void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item) is_layer_settings && opt_cnt == 2 && m_config->has("extruder") && m_config->has("layer_height")) return; + take_snapshot(_(L("Delete Settings"))); + int extruder = -1; if (m_config->has("extruder")) extruder = m_config->option("extruder")->value; @@ -1793,6 +1795,8 @@ void ObjectList::del_layer_from_object(const int obj_idx, const t_layer_height_r const auto del_range = object(obj_idx)->layer_config_ranges.find(layer_range); if (del_range == object(obj_idx)->layer_config_ranges.end()) return; + + take_snapshot(_(L("Delete Layers Range"))); object(obj_idx)->layer_config_ranges.erase(del_range); @@ -1923,8 +1927,10 @@ void ObjectList::layers_editing() t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; // set some default value - if (ranges.empty()) + if (ranges.empty()) { + take_snapshot(_(L("Add Layers"))); ranges[{ 0.0f, 2.0f }] = get_default_layer_config(obj_idx); + } // create layer root item layers_item = add_layer_root_item(obj_item); @@ -1956,6 +1962,7 @@ wxDataViewItem ObjectList::add_layer_root_item(const wxDataViewItem obj_item) for (const auto range : object(obj_idx)->layer_config_ranges) add_layer_item(range.first, layers_item); + Expand(layers_item); return layers_item; } @@ -2406,6 +2413,8 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre if (current_range == last_range) { + take_snapshot(_(L("Add New Layers Range"))); + const t_layer_height_range& new_range = { last_range.second, last_range.second + 2.0f }; ranges[new_range] = get_default_layer_config(obj_idx); add_layer_item(new_range, layers_item); @@ -2433,22 +2442,28 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre t_layer_height_range new_range = { midl_layer, next_range.second }; + take_snapshot(_(L("Add New Layers Range"))); + suppress_snapshots(); + + // create new 2 layers instead of deleted one + // delete old layer wxDataViewItem layer_item = m_objects_model->GetItemByLayerRange(obj_idx, next_range); del_subobject_item(layer_item); - // create new 2 layers instead of deleted one - ranges[new_range] = old_config; add_layer_item(new_range, layers_item, layer_idx); new_range = { current_range.second, midl_layer }; ranges[new_range] = get_default_layer_config(obj_idx); add_layer_item(new_range, layers_item, layer_idx); + allow_snapshots(); } else { + take_snapshot(_(L("Add New Layers Range"))); + const t_layer_height_range new_range = { current_range.second, next_range.first }; ranges[new_range] = get_default_layer_config(obj_idx); add_layer_item(new_range, layers_item, layer_idx); @@ -2477,8 +2492,10 @@ void ObjectList::add_layer_item(const t_layer_height_range& range, config.opt_int("extruder"), layer_idx); - if (config.keys().size() > 2) - select_item(m_objects_model->AddSettingsChild(layer_item)); + if (config.keys().size() > 2) { + m_objects_model->AddSettingsChild(layer_item); + Expand(layer_item); + } } bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height) @@ -2508,6 +2525,8 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay const int obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return false; + take_snapshot(_(L("Edit Layers Range"))); + const ItemType sel_type = m_objects_model->GetItemType(GetSelection()); t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; @@ -3417,11 +3436,13 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const wxGetApp().plater()->update(); } -void ObjectList::recreate_object_list() +void ObjectList::update_after_undo_redo() { m_prevent_list_events = true; m_prevent_canvas_selection_update = true; + suppress_snapshots(); + // Unselect all objects before deleting them, so that no change of selection is emitted during deletion. this->UnselectAll(); m_objects_model->DeleteAll(); @@ -3432,10 +3453,14 @@ void ObjectList::recreate_object_list() ++obj_idx; } + allow_snapshots(); + #ifndef __WXOSX__ selection_changed(); #endif /* __WXOSX__ */ + update_selections(); + m_prevent_canvas_selection_update = false; m_prevent_list_events = false; } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index a5a9c21388..34efa90252 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -335,7 +335,7 @@ public: void msw_rescale(); - void recreate_object_list(); + void update_after_undo_redo(); private: #ifdef __WXOSX__ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e826d748ec..9551524d4f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1280,7 +1280,7 @@ struct Plater::priv PrinterTechnology printer_technology = ptFFF; Slic3r::GCodePreviewData gcode_preview_data; Slic3r::UndoRedo::Stack undo_redo_stack; - bool m_prevent_snapshots = false; /* Used for avoid of excess "snapshoting". + int m_prevent_snapshots = 0; /* Used for avoid of excess "snapshoting". * Like for "delete selected" or "set numbers of copies" * we should call tack_snapshot just ones * instead of calls for each action separately @@ -1587,8 +1587,9 @@ struct Plater::priv void take_snapshot(const std::string& snapshot_name) { - if (this->m_prevent_snapshots) + if (this->m_prevent_snapshots > 0) return; + assert(this->m_prevent_snapshots >= 0); this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection()); } void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } @@ -1597,8 +1598,8 @@ struct Plater::priv void redo(); void undo_to(size_t time_to_load); void redo_to(size_t time_to_load); - void suppress_snapshots() { this->m_prevent_snapshots = true; } - void allow_snapshots() { this->m_prevent_snapshots = false; } + void suppress_snapshots() { this->m_prevent_snapshots++; } + void allow_snapshots() { this->m_prevent_snapshots--; } bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; } void update_print_volume_state(); @@ -3611,9 +3612,8 @@ void Plater::priv::update_after_undo_redo() //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time) this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances); - wxGetApp().obj_list()->recreate_object_list(); - wxGetApp().obj_list()->update_selections(); -// selection_changed(); + wxGetApp().obj_list()->update_after_undo_redo(); + //FIXME what about the state of the manipulators? //FIXME what about the focus? Cursor in the side panel? } From 6826e31e2a1c6fb519c47a58f6052d6d2335a0ea Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 11 Jul 2019 23:46:23 +0200 Subject: [PATCH 312/627] Some code refactoring for settings items --- src/slic3r/GUI/GUI_ObjectList.cpp | 144 ++++++++++++++++++-------- src/slic3r/GUI/GUI_ObjectList.hpp | 10 +- src/slic3r/GUI/GUI_ObjectSettings.cpp | 127 ++++++++++------------- src/slic3r/GUI/GUI_ObjectSettings.hpp | 2 +- 4 files changed, 163 insertions(+), 120 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index dc4bb87956..53c0c653e9 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -25,7 +25,7 @@ namespace GUI wxDEFINE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent); // pt_FFF -FreqSettingsBundle FREQ_SETTINGS_BUNDLE_FFF = +SettingsBundle FREQ_SETTINGS_BUNDLE_FFF = { { L("Layers and Perimeters"), { "layer_height" , "perimeters", "top_solid_layers", "bottom_solid_layers" } }, { L("Infill") , { "fill_density", "fill_pattern" } }, @@ -36,7 +36,7 @@ FreqSettingsBundle FREQ_SETTINGS_BUNDLE_FFF = }; // pt_SLA -FreqSettingsBundle FREQ_SETTINGS_BUNDLE_SLA = +SettingsBundle FREQ_SETTINGS_BUNDLE_SLA = { { L("Pad and Support") , { "supports_enable", "pad_enable" } } }; @@ -676,10 +676,7 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(object_item, wxString::FromUTF8(volume->name.c_str()), volume->type(), volume->get_mesh_errors_count()>0 , volume->config.has("extruder") ? volume->config.option("extruder")->value : 0); - auto opt_keys = volume->config.keys(); - if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) - select_item(m_objects_model->AddSettingsChild(vol_item)); - + add_settings_item(vol_item, &volume->config); items.Add(vol_item); } @@ -991,7 +988,7 @@ std::vector ObjectList::get_options(const bool is_part) const std::vector& ObjectList::get_options_for_bundle(const wxString& bundle_name) { - const FreqSettingsBundle& bundle = printer_technology() == ptSLA ? + const SettingsBundle& bundle = printer_technology() == ptSLA ? FREQ_SETTINGS_BUNDLE_SLA : FREQ_SETTINGS_BUNDLE_FFF; for (auto& it : bundle) @@ -1001,7 +998,7 @@ const std::vector& ObjectList::get_options_for_bundle(const wxStrin } #if 0 // if "Quick menu" is selected - FreqSettingsBundle& bundle_quick = printer_technology() == ptSLA ? + SettingsBundle& bundle_quick = printer_technology() == ptSLA ? m_freq_settings_sla: m_freq_settings_fff; for (auto& it : bundle_quick) @@ -1077,7 +1074,7 @@ void ObjectList::get_settings_choice(const wxString& category_name) if (selection_cnt > 0) { // Add selected items to the "Quick menu" - FreqSettingsBundle& freq_settings = printer_technology() == ptSLA ? + SettingsBundle& freq_settings = printer_technology() == ptSLA ? m_freq_settings_sla : m_freq_settings_fff; bool changed_existing = false; @@ -1149,8 +1146,8 @@ void ObjectList::get_settings_choice(const wxString& category_name) } - // Add settings item for object - update_settings_item(); + // Add settings item for object/sub-object and show them + show_settings(add_settings_item(GetSelection(), m_config)); } void ObjectList::get_freq_settings_choice(const wxString& bundle_name) @@ -1186,13 +1183,21 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) } } - // Add settings item for object - update_settings_item(); + // Add settings item for object/sub-object and show them + show_settings(add_settings_item(GetSelection(), m_config)); } -void ObjectList::update_settings_item() +void ObjectList::show_settings(const wxDataViewItem settings_item) { - auto item = GetSelection(); + if (!settings_item) + return; + + select_item(settings_item); + + // update object selection on Plater + if (!m_prevent_canvas_selection_update) + update_selections_on_canvas(); +/* auto item = GetSelection(); if (item) { if (m_objects_model->GetItemType(item) == itInstance) item = m_objects_model->GetTopParent(item); @@ -1204,12 +1209,14 @@ void ObjectList::update_settings_item() if (!m_prevent_canvas_selection_update) update_selections_on_canvas(); } - else { + else { + //# ys_FIXME ??? use case ??? auto panel = wxGetApp().sidebar().scrolled_panel(); panel->Freeze(); wxGetApp().obj_settings()->UpdateAndShow(true); panel->Thaw(); } + */ } wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) { @@ -1520,7 +1527,7 @@ wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) { // Add default settings bundles - const FreqSettingsBundle& bundle = printer_technology() == ptFFF ? + const SettingsBundle& bundle = printer_technology() == ptFFF ? FREQ_SETTINGS_BUNDLE_FFF : FREQ_SETTINGS_BUNDLE_SLA; const int extruders_cnt = extruders_count(); @@ -1535,7 +1542,7 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) } #if 0 // Add "Quick" settings bundles - const FreqSettingsBundle& bundle_quick = printer_technology() == ptFFF ? + const SettingsBundle& bundle_quick = printer_technology() == ptFFF ? m_freq_settings_fff : m_freq_settings_sla; for (auto& it : bundle_quick) { @@ -1898,11 +1905,7 @@ void ObjectList::split() volume->config.option("extruder")->value : 0, false); // add settings to the part, if it has those - auto opt_keys = volume->config.keys(); - if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) { - select_item(m_objects_model->AddSettingsChild(vol_item)); - Expand(vol_item); - } + add_settings_item(vol_item, &volume->config); } if (parent == item) @@ -2148,6 +2151,77 @@ void ObjectList::part_selection_changed() panel.Thaw(); } +SettingsBundle ObjectList::get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_layers_range_settings) +{ + auto opt_keys = config->keys(); + if (opt_keys.empty()) + return SettingsBundle(); + + update_opt_keys(opt_keys); // update options list according to print technology + + if (opt_keys.size() == 1 && opt_keys[0] == "extruder" || + is_layers_range_settings && opt_keys.size() == 2) + return SettingsBundle(); + + const int extruders_cnt = wxGetApp().extruders_edited_cnt(); + + SettingsBundle bundle; + for (auto& opt_key : opt_keys) + { + auto category = config->def()->get(opt_key)->category; + if (category.empty() || (category == "Extruders" && extruders_cnt == 1)) + continue; + + std::vector< std::string > new_category; + + auto& cat_opt = bundle.find(category) == bundle.end() ? new_category : bundle.at(category); + cat_opt.push_back(opt_key); + if (cat_opt.size() == 1) + bundle[category] = cat_opt; + } + + return bundle; +} + +wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config) +{ + wxDataViewItem ret = wxDataViewItem(0); + + if (!parent_item) + return ret; + + const bool is_layers_range_settings = m_objects_model->GetItemType(parent_item) == itLayer; + SettingsBundle cat_options = get_item_settings_bundle(config, is_layers_range_settings); + if (cat_options.empty()) + return ret; + + std::vector categories; + categories.reserve(cat_options.size()); + for (auto& cat : cat_options) + { + if (cat.second.size() == 1 && + (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height")) + continue; + + categories.push_back(cat.first); + } + + if (categories.empty()) + return ret; + + if (m_objects_model->GetItemType(parent_item) & itInstance) + parent_item = m_objects_model->GetTopParent(parent_item); + + ret = m_objects_model->IsSettingsItem(parent_item) ? parent_item : m_objects_model->GetSettingsItem(parent_item); + + if (!ret) ret = m_objects_model->AddSettingsChild(parent_item); + + m_objects_model->UpdateSettingsDigest(ret, categories); + Expand(parent_item); + + return ret; +} + void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) { auto model_object = (*m_objects)[obj_idx]; @@ -2167,13 +2241,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) !volume->config.has("extruder") ? 0 : volume->config.option("extruder")->value, false); - auto opt_keys = volume->config.keys(); - if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { - const wxDataViewItem &settings_item = m_objects_model->AddSettingsChild(vol_item); - if (call_selection_changed) - select_item(settings_item); - Expand(vol_item); - } + add_settings_item(vol_item, &volume->config); } Expand(item); } @@ -2183,13 +2251,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) increase_object_instances(obj_idx, model_object->instances.size()); // add settings to the object, if it has those - auto opt_keys = model_object->config.keys(); - if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { - const wxDataViewItem &settings_item = m_objects_model->AddSettingsChild(item); - if (call_selection_changed) - select_item(settings_item); - Expand(item); - } + add_settings_item(item, &model_object->config); // Add layers if it has add_layer_root_item(item); @@ -2491,11 +2553,7 @@ void ObjectList::add_layer_item(const t_layer_height_range& range, range, config.opt_int("extruder"), layer_idx); - - if (config.keys().size() > 2) { - m_objects_model->AddSettingsChild(layer_item); - Expand(layer_item); - } + add_settings_item(layer_item, &config); } bool ObjectList::edit_layer_range(const t_layer_height_range& range, coordf_t layer_height) @@ -3010,7 +3068,7 @@ void ObjectList::change_part_type() } else if (!settings_item && (new_type == ModelVolumeType::MODEL_PART || new_type == ModelVolumeType::PARAMETER_MODIFIER)) { - select_item(m_objects_model->AddSettingsChild(item)); + add_settings_item(item, &volume->config); } } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 34efa90252..72bddeec81 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -26,7 +26,7 @@ enum class ModelVolumeType : int; // FIXME: broken build on mac os because of this is missing: typedef std::vector t_config_option_keys; -typedef std::map> FreqSettingsBundle; +typedef std::map> SettingsBundle; // category -> vector ( option ; label ) typedef std::map< std::string, std::vector< std::pair > > settings_menu_hierarchy; @@ -152,8 +152,8 @@ class ObjectList : public wxDataViewCtrl wxDataViewItem m_last_selected_item {nullptr}; #if 0 - FreqSettingsBundle m_freq_settings_fff; - FreqSettingsBundle m_freq_settings_sla; + SettingsBundle m_freq_settings_fff; + SettingsBundle m_freq_settings_sla; #endif public: @@ -209,7 +209,7 @@ public: void get_settings_choice(const wxString& category_name); void get_freq_settings_choice(const wxString& bundle_name); - void update_settings_item(); + void show_settings(const wxDataViewItem settings_item); wxMenu* append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type); void append_menu_items_add_volume(wxMenu* menu); @@ -247,6 +247,7 @@ public: void layers_editing(); wxDataViewItem add_layer_root_item(const wxDataViewItem obj_item); + wxDataViewItem add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config); DynamicPrintConfig get_default_layer_config(const int obj_idx); bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); @@ -258,6 +259,7 @@ public: wxBoxSizer* get_sizer() {return m_sizer;} int get_selected_obj_idx() const; DynamicPrintConfig& get_item_config(const wxDataViewItem& item) const; + SettingsBundle get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_layers_range_settings); void changed_object(const int obj_idx = -1) const; void part_selection_changed(); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index ab2614895c..16c64360a2 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -63,20 +63,28 @@ ObjectSettings::ObjectSettings(wxWindow* parent) : m_bmp_delete = ScalableBitmap(parent, "cross"); } -void ObjectSettings::update_settings_list() +bool ObjectSettings::update_settings_list() { m_settings_list_sizer->Clear(true); + m_og_settings.resize(0); auto objects_ctrl = wxGetApp().obj_list(); auto objects_model = wxGetApp().obj_list()->GetModel(); auto config = wxGetApp().obj_list()->config(); const auto item = objects_ctrl->GetSelection(); - const bool is_layers_range_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itLayer; + + if (!item || !objects_model->IsSettingsItem(item) || !config || objects_ctrl->multiple_selection()) + return false; + + const bool is_layers_range_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itLayer; + SettingsBundle cat_options = objects_ctrl->get_item_settings_bundle(config, is_layers_range_settings); + + if (!cat_options.empty()) + { + std::vector categories; + categories.reserve(cat_options.size()); - if (item && !objects_ctrl->multiple_selection() && - config && objects_model->IsSettingsItem(item)) - { auto extra_column = [config, this](wxWindow* parent, const Line& line) { auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line @@ -96,86 +104,61 @@ void ObjectSettings::update_settings_list() return btn; }; - std::map> cat_options; - auto opt_keys = config->keys(); - objects_ctrl->update_opt_keys(opt_keys); // update options list according to print technology - - m_og_settings.resize(0); - std::vector categories; - if (!(opt_keys.size() == 1 && opt_keys[0] == "extruder"))// return; + for (auto& cat : cat_options) { - const int extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 : - wxGetApp().preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")->values.size(); + if (cat.second.size() == 1 && + (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height")) + continue; - for (auto& opt_key : opt_keys) { - auto category = config->def()->get(opt_key)->category; - if (category.empty() || - (category == "Extruders" && extruders_cnt == 1)) continue; + categories.push_back(cat.first); - std::vector< std::string > new_category; + auto optgroup = std::make_shared(m_og->ctrl_parent(), _(cat.first), config, false, extra_column); + optgroup->label_width = 15; + optgroup->sidetext_width = 5.5; - auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category); - cat_opt.push_back(opt_key); - if (cat_opt.size() == 1) - cat_options[category] = cat_opt; - } + optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) { + wxGetApp().obj_list()->changed_object(); }; - for (auto& cat : cat_options) { - if (cat.second.size() == 1 && - (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height")) + // call back for rescaling of the extracolumn control + optgroup->rescale_extra_column_item = [this](wxWindow* win) { + auto *ctrl = dynamic_cast(win); + if (ctrl == nullptr) + return; + ctrl->SetBitmap_(m_bmp_delete); + }; + + const bool is_extruders_cat = cat.first == "Extruders"; + for (auto& opt : cat.second) + { + if (opt == "extruder" || is_layers_range_settings && opt == "layer_height") continue; - - auto optgroup = std::make_shared(m_og->ctrl_parent(), _(cat.first), config, false, extra_column); - optgroup->label_width = 15; - optgroup->sidetext_width = 5.5; - - optgroup->m_on_change = [](const t_config_option_key& opt_id, const boost::any& value) { - wxGetApp().obj_list()->changed_object(); }; - - const bool is_extruders_cat = cat.first == "Extruders"; - for (auto& opt : cat.second) - { - if (opt == "extruder" || is_layers_range_settings && opt == "layer_height") - continue; - Option option = optgroup->get_option(opt); - option.opt.width = 12; - if (is_extruders_cat) - option.opt.max = wxGetApp().extruders_cnt(); - optgroup->append_single_option_line(option); - } - optgroup->reload_config(); - m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); - - // call back for rescaling of the extracolumn control - optgroup->rescale_extra_column_item = [this](wxWindow* win) { - auto *ctrl = dynamic_cast(win); - if (ctrl == nullptr) - return; - ctrl->SetBitmap_(m_bmp_delete); - }; - - m_og_settings.push_back(optgroup); - - categories.push_back(cat.first); + Option option = optgroup->get_option(opt); + option.opt.width = 12; + if (is_extruders_cat) + option.opt.max = wxGetApp().extruders_edited_cnt(); + optgroup->append_single_option_line(option); } + optgroup->reload_config(); + + m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0); + m_og_settings.push_back(optgroup); } - if (m_og_settings.empty()) { - objects_ctrl->select_item(objects_model->Delete(item)); - } - else { - if (!categories.empty()) - objects_model->UpdateSettingsDigest(item, categories); - } - } + if (!categories.empty()) + objects_model->UpdateSettingsDigest(item, categories); + } + else + { + objects_ctrl->select_item(objects_model->Delete(item)); + return false; + } + + return true; } void ObjectSettings::UpdateAndShow(const bool show) { - if (show) - update_settings_list(); - - OG_Settings::UpdateAndShow(show); + OG_Settings::UpdateAndShow(show ? update_settings_list() : false); } void ObjectSettings::msw_rescale() diff --git a/src/slic3r/GUI/GUI_ObjectSettings.hpp b/src/slic3r/GUI/GUI_ObjectSettings.hpp index 3d49f13b7f..01daa5622a 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.hpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.hpp @@ -44,7 +44,7 @@ public: ObjectSettings(wxWindow* parent); ~ObjectSettings() {} - void update_settings_list(); + bool update_settings_list(); void UpdateAndShow(const bool show) override; void msw_rescale(); }; From 4d8a028262f2911327e371b7c2e4863fb75ecf5d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 12 Jul 2019 13:55:46 +0200 Subject: [PATCH 313/627] Finally fix for settings item selection --- src/slic3r/GUI/GUI_ObjectList.cpp | 52 ++++++++++++++++---- src/slic3r/GUI/GUI_ObjectList.hpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 10 ++-- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 53c0c653e9..9e681257ca 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2183,6 +2183,7 @@ SettingsBundle ObjectList::get_item_settings_bundle(const DynamicPrintConfig* co return bundle; } +// Add new SettingsItem for parent_item if it doesn't exist, or just update a digest according to new config wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config) { wxDataViewItem ret = wxDataViewItem(0); @@ -3100,6 +3101,7 @@ bool ObjectList::has_multi_part_objects() return false; } +/* #lm_FIXME_delete_after_testing void ObjectList::update_settings_items() { m_prevent_canvas_selection_update = true; @@ -3125,22 +3127,52 @@ void ObjectList::update_settings_items() SetSelections(sel); m_prevent_canvas_selection_update = false; } +*/ +void ObjectList::update_and_show_object_settings_item() +{ + const wxDataViewItem item = GetSelection(); + if (!item) return; + + const wxDataViewItem& obj_item = m_objects_model->IsSettingsItem(item) ? m_objects_model->GetParent(item) : item; + select_item(add_settings_item(obj_item, &get_item_config(obj_item))); +} // Update settings item for item had it void ObjectList::update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections) { - const wxDataViewItem& settings_item = m_objects_model->GetSettingsItem(item); - select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); + const wxDataViewItem old_settings_item = m_objects_model->GetSettingsItem(item); + const wxDataViewItem new_settings_item = add_settings_item(item, &get_item_config(item)); - // If settings item was deleted from the list, - // it's need to be deleted from selection array, if it was there - if (settings_item != m_objects_model->GetSettingsItem(item) && - selections.Index(settings_item) != wxNOT_FOUND) { - selections.Remove(settings_item); + if (!new_settings_item && old_settings_item) + m_objects_model->Delete(old_settings_item); - // Select item, if settings_item doesn't exist for item anymore, but was selected - if (selections.Index(item) == wxNOT_FOUND) - selections.Add(item); + // if ols settings item was is selected area + if (selections.Index(old_settings_item) != wxNOT_FOUND) + { + // If settings item was just updated + if (old_settings_item == new_settings_item) + { + Sidebar& panel = wxGetApp().sidebar(); + panel.Freeze(); + + // update settings list + wxGetApp().obj_settings()->UpdateAndShow(true); + + panel.Layout(); + panel.Thaw(); + } + else + // If settings item was deleted from the list, + // it's need to be deleted from selection array, if it was there + { + selections.Remove(old_settings_item); + + // Select item, if settings_item doesn't exist for item anymore, but was selected + if (selections.Index(item) == wxNOT_FOUND) { + selections.Add(item); + select_item(item); // to correct update of the SettingsList and ManipulationPanel sizers + } + } } } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 72bddeec81..2d7e9f5f16 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -319,6 +319,7 @@ public: void last_volume_is_deleted(const int obj_idx); bool has_multi_part_objects(); void update_settings_items(); + void update_and_show_object_settings_item(); void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); void update_object_list_by_printer_technology(); void update_object_menu(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index a4a6d14595..0283209820 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -928,10 +928,12 @@ RENDER_AGAIN: } if (value_changed) { // Update side panel - wxTheApp->CallAfter([]() { - wxGetApp().obj_settings()->UpdateAndShow(true); - wxGetApp().obj_list()->update_settings_items(); - }); +/* wxTheApp->CallAfter([]() { + * wxGetApp().obj_settings()->UpdateAndShow(true); + * wxGetApp().obj_list()->update_settings_items(); + * }); + * #lm_FIXME_delete_after_testing */ + wxGetApp().obj_list()->update_and_show_object_settings_item(); } bool generate = m_imgui->button(m_desc.at("auto_generate")); From de88db59180822ddffd3b94bbd7b370a4971f196 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 12 Jul 2019 15:36:01 +0200 Subject: [PATCH 314/627] #2616 - Added Recent projects item to File menu --- src/slic3r/GUI/AppConfig.cpp | 27 +++++++++++++++++++ src/slic3r/GUI/AppConfig.hpp | 3 +++ src/slic3r/GUI/MainFrame.cpp | 52 ++++++++++++++++++++++++++++++++++++ src/slic3r/GUI/MainFrame.hpp | 5 ++++ src/slic3r/GUI/Plater.cpp | 13 ++++++--- src/slic3r/GUI/Plater.hpp | 1 + 6 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index 3b62100582..dfdc79677d 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -229,6 +229,33 @@ std::string AppConfig::get_last_dir() const return std::string(); } +std::vector AppConfig::get_recent_projects() const +{ + std::vector ret; + const auto it = m_storage.find("recent_projects"); + if (it != m_storage.end()) + { + for (const std::map::value_type& item : it->second) + { + ret.push_back(item.second); + } + } + return ret; +} + +void AppConfig::set_recent_projects(const std::vector& recent_projects) +{ + auto it = m_storage.find("recent_projects"); + if (it == m_storage.end()) + it = m_storage.insert(std::map>::value_type("recent_projects", std::map())).first; + + it->second.clear(); + for (unsigned int i = 0; i < (unsigned int)recent_projects.size(); ++i) + { + it->second[std::to_string(i + 1)] = recent_projects[i]; + } +} + void AppConfig::update_config_dir(const std::string &dir) { this->set("recent", "config_directory", dir); diff --git a/src/slic3r/GUI/AppConfig.hpp b/src/slic3r/GUI/AppConfig.hpp index 5af635a12c..230a922940 100644 --- a/src/slic3r/GUI/AppConfig.hpp +++ b/src/slic3r/GUI/AppConfig.hpp @@ -122,6 +122,9 @@ public: // Does the config file exist? static bool exists(); + std::vector get_recent_projects() const; + void set_recent_projects(const std::vector& recent_projects); + private: // Map of section, name -> value std::map> m_storage; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d800f6f380..99389da410 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -35,6 +35,7 @@ namespace GUI { MainFrame::MainFrame() : DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"), m_printhost_queue_dlg(new PrintHostQueueDialog(this)) + , m_recent_projects(9) { // Fonts were created by the DPIFrame constructor for the monitor, on which the window opened. wxGetApp().update_fonts(this); @@ -383,6 +384,40 @@ void MainFrame::init_menubar() append_menu_item(fileMenu, wxID_ANY, _(L("&Open Project")) + dots + "\tCtrl+O", _(L("Open a project file")), [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, menu_icon("open"), nullptr, [this](){return m_plater != nullptr; }, this); + + wxMenu* recent_projects_menu = new wxMenu(); + wxMenuItem* recent_projects_submenu = append_submenu(fileMenu, recent_projects_menu, wxID_ANY, _(L("Recent projects")), ""); + m_recent_projects.UseMenu(recent_projects_menu); + Bind(wxEVT_MENU, [this](wxCommandEvent& evt) { + size_t file_id = evt.GetId() - wxID_FILE1; + wxString filename = m_recent_projects.GetHistoryFile(file_id); + if (wxFileExists(filename)) + m_plater->load_project(filename); + else + { + wxMessageDialog msg(this, _(L("The selected project is no more available")), _(L("Error"))); + msg.ShowModal(); + + m_recent_projects.RemoveFileFromHistory(file_id); + std::vector recent_projects; + size_t count = m_recent_projects.GetCount(); + for (size_t i = 0; i < count; ++i) + { + recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i))); + } + wxGetApp().app_config->set_recent_projects(recent_projects); + wxGetApp().app_config->save(); + } + }, wxID_FILE1, wxID_FILE9); + + std::vector recent_projects = wxGetApp().app_config->get_recent_projects(); + for (const std::string& project : recent_projects) + { + m_recent_projects.AddFileToHistory(from_u8(project)); + } + + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId()); + append_menu_item(fileMenu, wxID_ANY, _(L("&Save Project")) + "\tCtrl+S", _(L("Save current project file")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, menu_icon("save"), nullptr, [this](){return m_plater != nullptr && can_save(); }, this); @@ -1046,6 +1081,23 @@ void MainFrame::on_config_changed(DynamicPrintConfig* config) const m_plater->on_config_change(*config); // propagate config change events to the plater } +void MainFrame::add_to_recent_projects(const wxString& filename) +{ + if (wxFileExists(filename)) + { + m_recent_projects.AddFileToHistory(filename); + std::vector recent_projects; + size_t count = m_recent_projects.GetCount(); + for (size_t i = 0; i < count; ++i) + { + recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i))); + } + wxGetApp().app_config->set_recent_projects(recent_projects); + wxGetApp().app_config->save(); + } +} + +// // Called after the Preferences dialog is closed and the program settings are saved. // Update the UI based on the current preferences. void MainFrame::update_ui_from_settings() diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 805b663bb2..a41f33824d 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -84,6 +85,8 @@ class MainFrame : public DPIFrame // vector of a MenuBar items changeable in respect to printer technology std::vector m_changeable_menu_items; + wxFileHistory m_recent_projects; + protected: virtual void on_dpi_changed(const wxRect &suggested_rect); @@ -121,6 +124,8 @@ public: // Propagate changed configuration from the Tab to the Platter and save changes to the AppConfig void on_config_changed(DynamicPrintConfig* cfg) const ; + void add_to_recent_projects(const wxString& filename); + PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; } Plater* m_plater { nullptr }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 402acd59cf..b99b3a6aff 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3277,6 +3277,9 @@ void Plater::priv::set_project_filename(const wxString& filename) m_project_filename = from_path(full_path); wxGetApp().mainframe->update_title(); + + if (!filename.empty()) + wxGetApp().mainframe->add_to_recent_projects(filename); } bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/) @@ -3590,15 +3593,19 @@ void Plater::load_project() { wxString input_file; wxGetApp().load_project(this, input_file); + load_project(input_file); +} - if (input_file.empty()) +void Plater::load_project(const wxString& filename) +{ + if (filename.empty()) return; p->reset(); - p->set_project_filename(input_file); + p->set_project_filename(filename); std::vector input_paths; - input_paths.push_back(into_path(input_file)); + input_paths.push_back(into_path(filename)); load_files(input_paths); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 34fd7984e2..9ba63461e0 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -138,6 +138,7 @@ public: void new_project(); void load_project(); + void load_project(const wxString& filename); void add_model(); void extract_config_from_project(); From df7bb94dafea6a64922184fe65db3c61f7c85da0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 12 Jul 2019 21:03:49 +0200 Subject: [PATCH 315/627] Not handling logical beds in arrange() --- src/libnest2d/include/libnest2d.h | 23 ++- src/libnest2d/include/libnest2d/libnest2d.hpp | 141 ++++++------------ .../libnest2d/selections/djd_heuristic.hpp | 7 +- .../include/libnest2d/selections/firstfit.hpp | 6 +- src/libnest2d/tests/test.cpp | 97 ++++++------ src/libslic3r/Arrange.cpp | 62 ++++---- src/libslic3r/Arrange.hpp | 79 ++++++---- src/libslic3r/Model.cpp | 40 +++-- src/libslic3r/Model.hpp | 12 +- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.hpp | 13 +- src/slic3r/GUI/Plater.cpp | 46 ++---- 12 files changed, 256 insertions(+), 272 deletions(-) diff --git a/src/libnest2d/include/libnest2d.h b/src/libnest2d/include/libnest2d.h index f1d2506f48..5f7a29dfbe 100644 --- a/src/libnest2d/include/libnest2d.h +++ b/src/libnest2d/include/libnest2d.h @@ -59,20 +59,20 @@ extern template PackGroup Nester::execute( template::iterator> -PackGroup nest(Iterator from, Iterator to, +void nest(Iterator from, Iterator to, const typename Placer::BinType& bin, Coord dist = 0, const typename Placer::Config& pconf = {}, const typename Selector::Config& sconf = {}) { Nester nester(bin, dist, pconf, sconf); - return nester.execute(from, to); + nester.execute(from, to); } template::iterator> -PackGroup nest(Iterator from, Iterator to, +void nest(Iterator from, Iterator to, const typename Placer::BinType& bin, ProgressFunction prg, StopCondition scond = []() { return false; }, @@ -83,7 +83,7 @@ PackGroup nest(Iterator from, Iterator to, Nester nester(bin, dist, pconf, sconf); if(prg) nester.progressIndicator(prg); if(scond) nester.stopCondition(scond); - return nester.execute(from, to); + nester.execute(from, to); } #ifdef LIBNEST2D_STATIC @@ -91,14 +91,14 @@ PackGroup nest(Iterator from, Iterator to, extern template class Nester; extern template class Nester; -extern template PackGroup nest(std::vector::iterator from, +extern template void nest(std::vector::iterator from, std::vector::iterator to, const Box& bin, Coord dist = 0, const NfpPlacer::Config& pconf, const FirstFitSelection::Config& sconf); -extern template PackGroup nest(std::vector::iterator from, +extern template void nest(std::vector::iterator from, std::vector::iterator to, const Box& bin, ProgressFunction prg, @@ -112,20 +112,19 @@ extern template PackGroup nest(std::vector::iterator from, template> -PackGroup nest(Container&& cont, +void nest(Container&& cont, const typename Placer::BinType& bin, Coord dist = 0, const typename Placer::Config& pconf = {}, const typename Selector::Config& sconf = {}) { - return nest(cont.begin(), cont.end(), - bin, dist, pconf, sconf); + nest(cont.begin(), cont.end(), bin, dist, pconf, sconf); } template> -PackGroup nest(Container&& cont, +void nest(Container&& cont, const typename Placer::BinType& bin, ProgressFunction prg, StopCondition scond = []() { return false; }, @@ -133,8 +132,8 @@ PackGroup nest(Container&& cont, const typename Placer::Config& pconf = {}, const typename Selector::Config& sconf = {}) { - return nest(cont.begin(), cont.end(), - bin, prg, scond, dist, pconf, sconf); + nest(cont.begin(), cont.end(), bin, prg, scond, dist, + pconf, sconf); } } diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 99c8c90c17..a83a16ecf9 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -12,6 +12,8 @@ namespace libnest2d { +static const constexpr int BIN_ID_UNSET = -1; + /** * \brief An item to be placed on a bin. * @@ -34,9 +36,9 @@ class _Item { RawShape sh_; // Transformation data - Vertex translation_; - Radians rotation_; - Coord inflation_; + Vertex translation_{0, 0}; + Radians rotation_{0.0}; + Coord inflation_{0}; // Info about whether the transformations will have to take place // This is needed because if floating point is used, it is hard to say @@ -66,9 +68,7 @@ class _Item { BBCache(): valid(false) {} } bb_cache_; - static const size_t ID_UNSET = size_t(-1); - - size_t id_{ID_UNSET}; + int binid_{BIN_ID_UNSET}; bool fixed_{false}; public: @@ -149,8 +149,8 @@ public: inline bool isFixed() const noexcept { return fixed_; } inline void markAsFixed(bool fixed = true) { fixed_ = fixed; } - inline void id(size_t idx) { id_ = idx; } - inline long id() const noexcept { return id_; } + inline void binId(int idx) { binid_ = idx; } + inline int binId() const noexcept { return binid_; } /** * @brief Convert the polygon to string representation. The format depends @@ -766,25 +766,6 @@ public: void clear() { impl_.clear(); } }; -using BinIdx = unsigned; -template using _NestResult = - std::vector< - std::tuple, // Translation calculated by nesting - Radians, // Rotation calculated by nesting - BinIdx> // Logical bin index, first is zero - >; - -template struct Indexed { - using ShapeType = T; - static T& get(T& obj) { return obj; } -}; - -template struct Indexed> { - using ShapeType = S; - static S& get(std::pair& obj) { return obj.second; } -}; - /** * The Arranger is the front-end class for the libnest2d library. It takes the * input items and outputs the items with the proper transformations to be @@ -805,7 +786,6 @@ public: using Coord = TCoord>; using PackGroup = _PackGroup; using ResultType = PackGroup; - template using NestResult = _NestResult; private: BinType bin_; @@ -816,8 +796,13 @@ private: using TPItem = remove_cvref_t; using TSItem = remove_cvref_t; - std::vector item_cache_; StopCondition stopfn_; + + template using TVal = remove_cvref_t; + + template + using ConvertibleOnly = + enable_if_t< std::is_convertible, TPItem>::value, void>; public: @@ -864,12 +849,20 @@ public: * The number of groups in the pack group is the number of bins opened by * the selection algorithm. */ - template - inline const NestResult execute(It from, It to, - std::function keyfn = nullptr) + template + inline ConvertibleOnly execute(It from, It to) { - if (!keyfn) keyfn = [to](It it) { return to - it; }; - return _execute(from, to, keyfn); + auto infl = static_cast(std::ceil(min_obj_distance_/2.0)); + if(infl > 0) std::for_each(from, to, [this, infl](Item& item) { + item.inflate(infl); + }); + + selector_.template packItems( + from, to, bin_, pconfig_); + + if(min_obj_distance_ > 0) std::for_each(from, to, [infl](Item& item) { + item.inflate(-infl); + }); } /// Set a progress indicator function object for the selector. @@ -890,74 +883,32 @@ public: } private: - - template using TVal = remove_cvref_t; - - template - using ConvertibleOnly = - enable_if_t< std::is_convertible, TPItem>::value, void>; - - template - using NotConvertibleOnly = - enable_if_t< ! std::is_convertible, TPItem>::value, void>; - + + // This function will be used only if the iterators are pointing to // a type compatible with the libnets2d::_Item template. // This way we can use references to input elements as they will // have to exist for the lifetime of this call. - template - inline ConvertibleOnly> _execute( - It from, It to, std::function keyfn) - { - { - auto it = from; size_t id = 0; - while(it != to) - if (it->id() == Item::ID_UNSET) (it++)->id(id++); - else { id = it->id() + 1; ++it; } - } - - NestResult result(to - from); - - __execute(from, to, keyfn); - - BinIdx binidx = 0; - for(auto &itmgrp : lastResult()) { - for(const Item& itm : itmgrp) - result[itm.id()] = - std::make_tuple(keyfn(from + itm.id()), itm.translation(), - itm.rotation(), binidx); - - ++binidx; - } - - return result; - } +// template +// inline ConvertibleOnly _execute(It from, It to) +// { +// __execute(from, to); +// } - template - inline NotConvertibleOnly> _execute( - It from, It to, std::function keyfn) - { - item_cache_.reserve(to - from); - for(auto it = from; it != to; ++it) - item_cache_.emplace_back(Indexed::get(*it)); +// template inline void _execute(It from, It to) +// { +// auto infl = static_cast(std::ceil(min_obj_distance_/2.0)); +// if(infl > 0) std::for_each(from, to, [this](Item& item) { +// item.inflate(infl); +// }); - return _execute(item_cache_.begin(), item_cache_.end(), keyfn); - } - - template inline void __execute(It from, It to) - { - auto infl = static_cast(std::ceil(min_obj_distance_/2.0)); - if(infl > 0) std::for_each(from, to, [this](Item& item) { - item.inflate(infl); - }); - - selector_.template packItems( - from, to, bin_, pconfig_); +// selector_.template packItems( +// from, to, bin_, pconfig_); - if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { - item.inflate(-infl); - }); - } +// if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { +// item.inflate(-infl); +// }); +// } }; } diff --git a/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp index 25007e580e..f904210aac 100644 --- a/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp +++ b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp @@ -711,7 +711,12 @@ public: addBin(); packjob(placers[idx], remaining, idx); idx++; } - + + int binid = 0; + for(auto &bin : packed_bins_) { + for(Item& itm : bin) itm.binId(binid); + binid++; + } } }; diff --git a/src/libnest2d/include/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp index 287204c08a..6a3e2e61b6 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -90,8 +90,10 @@ public: size_t j = 0; while(!was_packed && !cancelled()) { for(; j < placers.size() && !was_packed && !cancelled(); j++) { - if((was_packed = placers[j].pack(*it, rem(it, store_) ))) - makeProgress(placers[j], j); + if((was_packed = placers[j].pack(*it, rem(it, store_) ))) { + it->get().binId(int(j)); + makeProgress(placers[j], j); + } } if(!was_packed) { diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index e5bd871820..6891b16e14 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -372,27 +372,34 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) Nester arrange(bin); - auto groups = arrange.execute(rects.begin(), rects.end()); - - ASSERT_EQ(groups.size(), 1u); - ASSERT_EQ(groups[0].size(), rects.size()); - + arrange.execute(rects.begin(), rects.end()); + + auto max_group = std::max_element(rects.begin(), rects.end(), + [](const Item &i1, const Item &i2) { + return i1.binId() < i2.binId(); + }); + + int groups = max_group == rects.end() ? 0 : max_group->binId() + 1; + + ASSERT_EQ(groups, 1u); + ASSERT_TRUE( + std::all_of(rects.begin(), rects.end(), [](const Rectangle &itm) { + return itm.binId() != BIN_ID_UNSET; + })); + // check for no intersections, no containment: - for(auto result : groups) { - bool valid = true; - for(Item& r1 : result) { - for(Item& r2 : result) { - if(&r1 != &r2 ) { - valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); - ASSERT_TRUE(valid); - valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); - ASSERT_TRUE(valid); - } + bool valid = true; + for(Item& r1 : rects) { + for(Item& r2 : rects) { + if(&r1 != &r2 ) { + valid = !Item::intersects(r1, r2) || Item::touches(r1, r2); + ASSERT_TRUE(valid); + valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); + ASSERT_TRUE(valid); } } } - } TEST(GeometryAlgorithms, ArrangeRectanglesLoose) @@ -433,16 +440,25 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) Nester arrange(bin, min_obj_distance); - auto groups = arrange.execute(rects.begin(), rects.end()); + arrange.execute(rects.begin(), rects.end()); - ASSERT_EQ(groups.size(), 1u); - ASSERT_EQ(groups[0].size(), rects.size()); + auto max_group = std::max_element(rects.begin(), rects.end(), + [](const Item &i1, const Item &i2) { + return i1.binId() < i2.binId(); + }); + + size_t groups = max_group == rects.end() ? 0 : max_group->binId() + 1; + + ASSERT_EQ(groups, 1u); + ASSERT_TRUE( + std::all_of(rects.begin(), rects.end(), [](const Rectangle &itm) { + return itm.binId() != BIN_ID_UNSET; + })); // check for no intersections, no containment: - auto result = groups[0]; bool valid = true; - for(Item& r1 : result) { - for(Item& r2 : result) { + for(Item& r1 : rects) { + for(Item& r2 : rects) { if(&r1 != &r2 ) { valid = !Item::intersects(r1, r2); valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); @@ -554,27 +570,24 @@ TEST(GeometryAlgorithms, convexHull) { TEST(GeometryAlgorithms, NestTest) { std::vector input = prusaParts(); + + libnest2d::nest(input, Box(250000000, 210000000), [](unsigned cnt) { + std::cout << "parts left: " << cnt << std::endl; + }); + + auto max_binid_it = std::max_element(input.begin(), input.end(), + [](const Item &i1, const Item &i2) { + return i1.binId() < i2.binId(); + }); + + size_t bins = max_binid_it == input.end() ? 0 : max_binid_it->binId() + 1; - PackGroup result = libnest2d::nest(input, - Box(250000000, 210000000), - [](unsigned cnt) { - std::cout - << "parts left: " << cnt - << std::endl; - }); - - ASSERT_LE(result.size(), 2); - - size_t partsum = std::accumulate(result.begin(), - result.end(), - size_t(0), - [](size_t s, - const decltype( - result)::value_type &bin) { - return s += bin.size(); - }); - - ASSERT_EQ(input.size(), partsum); + ASSERT_EQ(bins, 2u); + + ASSERT_TRUE( + std::all_of(input.begin(), input.end(), [](const Item &itm) { + return itm.binId() != BIN_ID_UNSET; + })); } namespace { diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 6bbcc9577f..c49c9fb972 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -341,9 +341,9 @@ public: m_pck.configure(m_pconf); } - template inline PackGroup operator()(Args&&...args) { + template inline void operator()(Args&&...args) { m_rtree.clear(); - return m_pck.execute(std::forward(args)...); + m_pck.execute(std::forward(args)...); } inline void preload(std::vector& fixeditems) { @@ -513,7 +513,7 @@ BedShapeHint bedShape(const Polyline &bed) { } template // Arrange for arbitrary bin type -_NestResult _arrange( +void _arrange( std::vector & shapes, std::vector & excludes, const BinT & bin, @@ -553,40 +553,30 @@ _NestResult _arrange( for (auto &itm : shapes ) inp.emplace_back(itm); for (auto &itm : excludes) inp.emplace_back(itm); - return arranger(inp.begin(), inp.end()); -} - -inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) -{ - return w + w / 5; + arranger(inp.begin(), inp.end()); } // The final client function for arrangement. A progress indicator and // a stop predicate can be also be passed to control the process. -bool arrange(ArrangeablePtrs & arrangables, - const ArrangeablePtrs & excludes, +void arrange(ArrangePolygons & arrangables, + const ArrangePolygons & excludes, coord_t min_obj_dist, const BedShapeHint & bedhint, std::function progressind, std::function stopcondition) { - bool ret = true; namespace clppr = ClipperLib; std::vector items, fixeditems; items.reserve(arrangables.size()); - coord_t binwidth = 0; - + + // Create Item from Arrangeable auto process_arrangeable = - [](const Arrangeable *arrangeable, std::vector &outp) + [](const ArrangePolygon &arrpoly, std::vector &outp) { - assert(arrangeable); - - auto arrangeitem = arrangeable->get_arrange_polygon(); - - Polygon & p = std::get<0>(arrangeitem); - const Vec2crd &offs = std::get<1>(arrangeitem); - double rotation = std::get<2>(arrangeitem); + Polygon p = arrpoly.poly.contour; + const Vec2crd & offs = arrpoly.translation; + double rotation = arrpoly.rotation; if (p.is_counter_clockwise()) p.reverse(); @@ -600,10 +590,10 @@ bool arrange(ArrangeablePtrs & arrangables, outp.back().translation({offs.x(), offs.y()}); }; - for (Arrangeable *arrangeable : arrangables) + for (ArrangePolygon &arrangeable : arrangables) process_arrangeable(arrangeable, items); - for (const Arrangeable * fixed: excludes) + for (const ArrangePolygon &fixed: excludes) process_arrangeable(fixed, fixeditems); // Integer ceiling the min distance from the bed perimeters @@ -619,7 +609,6 @@ bool arrange(ArrangeablePtrs & arrangables, BoundingBox bbb = bedhint.shape.box; bbb.min -= Point{md, md}, bbb.max += Point{md, md}; Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; - binwidth = coord_t(binbb.width()); _arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn); break; @@ -627,7 +616,6 @@ bool arrange(ArrangeablePtrs & arrangables, case BedShapeType::CIRCLE: { auto c = bedhint.shape.circ; auto cc = to_lnCircle(c); - binwidth = scaled(c.radius()); _arrange(items, fixeditems, cc, min_obj_dist, pri, cfn); break; @@ -636,7 +624,6 @@ bool arrange(ArrangeablePtrs & arrangables, auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon); auto irrbed = sl::create(std::move(ctour)); BoundingBox polybb(bedhint.shape.polygon); - binwidth = (polybb.max(X) - polybb.min(X)); _arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn); break; @@ -655,19 +642,22 @@ bool arrange(ArrangeablePtrs & arrangables, } }; - if(stopcondition && stopcondition()) return false; - - return ret; + for(size_t i = 0; i < items.size(); ++i) { + clppr::IntPoint tr = items[i].translation(); + arrangables[i].translation = {coord_t(tr.X), coord_t(tr.Y)}; + arrangables[i].rotation = items[i].rotation(); + arrangables[i].bed_idx = items[i].binId(); + } } // Arrange, without the fixed items (excludes) -bool arrange(ArrangeablePtrs & inp, - coord_t min_d, - const BedShapeHint & bedhint, - std::function prfn, - std::function stopfn) +void arrange(ArrangePolygons & inp, + coord_t min_d, + const BedShapeHint & bedhint, + std::function prfn, + std::function stopfn) { - return arrange(inp, {}, min_d, bedhint, prfn, stopfn); + arrange(inp, {}, min_d, bedhint, prfn, stopfn); } } // namespace arr diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 337a7d9590..bc23108cd7 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -1,7 +1,7 @@ #ifndef MODELARRANGE_HPP #define MODELARRANGE_HPP -#include "Polygon.hpp" +#include "ExPolygon.hpp" #include "BoundingBox.hpp" namespace Slic3r { @@ -37,34 +37,57 @@ enum class BedShapeType { /// Info about the print bed for the arrange() function. struct BedShapeHint { BedShapeType type = BedShapeType::INFINITE; - /*union*/ struct { // I know but who cares... TODO: use variant from cpp17? + union BedShape_u { // I know but who cares... TODO: use variant from cpp17? CircleBed circ; BoundingBox box; Polyline polygon; - InfiniteBed infinite; + InfiniteBed infinite{}; + ~BedShape_u() {} + BedShape_u() {}; } shape; + + BedShapeHint() {}; + + ~BedShapeHint() { + if (type == BedShapeType::IRREGULAR) + shape.polygon.Slic3r::Polyline::~Polyline(); + }; + + BedShapeHint(const BedShapeHint &cpy) { + *this = cpy; + } + + BedShapeHint& operator=(const BedShapeHint &cpy) { + type = cpy.type; + switch(type) { + case BedShapeType::BOX: shape.box = cpy.shape.box; break; + case BedShapeType::CIRCLE: shape.circ = cpy.shape.circ; break; + case BedShapeType::IRREGULAR: shape.polygon = cpy.shape.polygon; break; + case BedShapeType::INFINITE: shape.infinite = cpy.shape.infinite; break; + case BedShapeType::UNKNOWN: break; + } + + return *this; + } }; /// Get a bed shape hint for arrange() from a naked Polyline. BedShapeHint bedShape(const Polyline& bed); -/** - * @brief Classes implementing the Arrangeable interface can be used as input - * to the arrange function. - */ -class Arrangeable { -public: +static const constexpr long UNARRANGED = -1; + +struct ArrangePolygon { + const ExPolygon poly; + Vec2crd translation{0, 0}; + double rotation{0.0}; + long bed_idx{UNARRANGED}; - virtual ~Arrangeable() = default; - - /// Apply the result transformation calculated by the arrangement. - virtual void apply_arrange_result(Vec2d offset, double rotation_rads, unsigned bed_num) = 0; - - /// Get the 2D silhouette to arrange and an initial offset and rotation - virtual std::tuple get_arrange_polygon() const = 0; + ArrangePolygon(const ExPolygon &p, const Vec2crd &tr = {}, double rot = 0.0) + : poly{p}, translation{tr}, rotation{rot} + {} }; -using ArrangeablePtrs = std::vector; +using ArrangePolygons = std::vector; /** * \brief Arranges the model objects on the screen. @@ -97,20 +120,20 @@ using ArrangeablePtrs = std::vector; * * \param stopcondition A predicate returning true if abort is needed. */ -bool arrange(ArrangeablePtrs &items, - coord_t min_obj_distance, - const BedShapeHint& bedhint, - std::function progressind = nullptr, - std::function stopcondition = nullptr); +void arrange(ArrangePolygons & items, + coord_t min_obj_distance, + const BedShapeHint & bedhint, + std::function progressind = nullptr, + std::function stopcondition = nullptr); /// Same as the previous, only that it takes unmovable items as an /// additional argument. -bool arrange(ArrangeablePtrs &items, - const ArrangeablePtrs &excludes, - coord_t min_obj_distance, - const BedShapeHint& bedhint, - std::function progressind = nullptr, - std::function stopcondition = nullptr); +void arrange(ArrangePolygons & items, + const ArrangePolygons & excludes, + coord_t min_obj_distance, + const BedShapeHint & bedhint, + std::function progressind = nullptr, + std::function stopcondition = nullptr); } // arr } // Slic3r diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 2af4e4c275..966be4c887 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -404,11 +404,16 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) size_t count = 0; for (auto obj : objects) count += obj->instances.size(); - arrangement::ArrangeablePtrs input; + arrangement::ArrangePolygons input; + ModelInstancePtrs instances; input.reserve(count); + instances.reserve(count); for (ModelObject *mo : objects) - for (ModelInstance *minst : mo->instances) - input.emplace_back(minst); + for (ModelInstance *minst : mo->instances) { + input.emplace_back(minst->get_arrange_polygon()); + instances.emplace_back(minst); + } + arrangement::BedShapeHint bedhint; @@ -417,7 +422,22 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) bedhint.shape.box = BoundingBox(scaled(bb->min), scaled(bb->max)); } - return arrangement::arrange(input, scaled(dist), bedhint); + arrangement::arrange(input, scaled(dist), bedhint); + + bool ret = true; + + for(size_t i = 0; i < input.size(); ++i) { + auto inst = instances[i]; + inst->set_rotation(Z, input[i].rotation); + auto tr = unscaled(input[i].translation); + inst->set_offset(X, tr.x()); + inst->set_offset(Y, tr.y()); + + if (input[i].bed_idx != 0) ret = false; // no logical beds are allowed + } + + + return ret; } // Duplicate the entire model preserving instance relative positions. @@ -1819,7 +1839,7 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } -std::tuple ModelInstance::get_arrange_polygon() const +arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const { static const double SIMPLIFY_TOLERANCE_MM = 0.1; @@ -1835,15 +1855,15 @@ std::tuple ModelInstance::get_arrange_polygon() const // this may happen for malformed models, see: // https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (p.points.empty()) return {}; + if (p.points.empty()) return {{}}; Polygons pp{p}; pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); if (!pp.empty()) p = pp.front(); - - return std::make_tuple(p, - Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}, - get_rotation(Z)); + + ExPolygon ep; ep.contour = std::move(p); + + return {ep, Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}, get_rotation(Z)}; } // Test whether the two models contain the same number of ModelObjects with the same set of IDs diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index d0ed0bc88f..2fd696d143 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -491,7 +491,7 @@ private: // A single instance of a ModelObject. // Knows the affine transformation of an object. -class ModelInstance : public ModelBase, public arrangement::Arrangeable +class ModelInstance : public ModelBase { public: enum EPrintVolumeState : unsigned char @@ -555,19 +555,19 @@ public: bool is_printable() const { return print_volume_state == PVS_Inside; } // ///////////////////////////////////////////////////////////////////////// - // Implement arr::Arrangeable interface + // Implement arrangement::Arrangeable interface // ///////////////////////////////////////////////////////////////////////// // Getting the input polygon for arrange - virtual std::tuple get_arrange_polygon() const override; + arrangement::ArrangePolygon get_arrange_polygon() const; // Apply the arrange result on the ModelInstance - virtual void apply_arrange_result(Vec2d offs, double rot_rads, unsigned /*bed_num*/) override + void apply_arrange_result(Vec2crd offs, double rot_rads) { // write the transformation data into the model instance set_rotation(Z, rot_rads); - set_offset(X, offs(X)); - set_offset(Y, offs(Y)); + set_offset(X, unscale(offs(X))); + set_offset(Y, unscale(offs(Y))); } protected: diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e5b2b38f88..4e30934891 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5739,7 +5739,7 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } -void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2d offset, double rotation_rads, unsigned /*bed_num*/) +void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2d offset, double rotation_rads) { m_pos = offset; m_rotation = rotation_rads; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 8f419a16db..2316637d82 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -612,7 +612,7 @@ public: int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } - class WipeTowerInfo: public arrangement::Arrangeable { + class WipeTowerInfo { Vec2d m_pos = {std::nan(""), std::nan("")}; Vec2d m_bb_size; double m_rotation; @@ -624,9 +624,9 @@ public: return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); } - virtual void apply_arrange_result(Vec2d offset, double rotation_rads, unsigned /*bed_num*/) override; - - virtual std::tuple get_arrange_polygon() const override + void apply_arrange_result(Vec2d offset, double rotation_rads); + + arrangement::ArrangePolygon get_arrange_polygon() const { Polygon p({ {coord_t(0), coord_t(0)}, @@ -635,8 +635,9 @@ public: {coord_t(0), scaled(m_bb_size(Y))}, {coord_t(0), coord_t(0)}, }); - - return std::make_tuple(p, scaled(m_pos), m_rotation); + + ExPolygon ep; ep.contour = std::move(p); + return {ep, scaled(m_pos), m_rotation}; } }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a49b541b57..3adfc0f059 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1217,28 +1217,6 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi return true; } -namespace { -arrangement::ArrangeablePtrs get_arrange_input(Model &model, const Selection &sel) { - auto selmap = sel.get_content(); - - size_t count = 0; - for (auto obj : model.objects) count += obj->instances.size(); - - arrangement::ArrangeablePtrs ret; ret.reserve(count); - - if (selmap.empty()) - for (ModelObject *mo : model.objects) - for (ModelInstance *minst : mo->instances) - ret.emplace_back(minst); - else - for (auto &s : selmap) - for (auto &instid : s.second) - ret.emplace_back(model.objects[s.first]->instances[instid]); - - return ret; -} -} - // Plater / private struct Plater::priv { @@ -1447,17 +1425,18 @@ struct Plater::priv class ArrangeJob : public Job { GLCanvas3D::WipeTowerInfo m_wti; - arrangement::ArrangeablePtrs m_selected, m_unselected; - - static std::array collect( + arrangement::ArrangePolygons m_selected, m_unselected; + + static std::array collect( Model &model, const Selection &sel) { - auto selmap = sel.get_content(); - + const Selection::ObjectIdxsToInstanceIdxsMap &selmap = + sel.get_content(); + size_t count = 0; for (auto obj : model.objects) count += obj->instances.size(); - arrangement::ArrangeablePtrs selected, unselected; + arrangement::ArrangePolygons selected, unselected; selected.reserve(count + 1 /* for optional wti */); unselected.reserve(count + 1 /* for optional wti */); @@ -1475,12 +1454,12 @@ struct Plater::priv ModelInstance *inst = model.objects[oidx] ->instances[iidx]; instit == iids.end() ? - unselected.emplace_back(inst) : - selected.emplace_back(inst); + unselected.emplace_back(inst->get_arrange_polygon()) : + selected.emplace_back(inst->get_arrange_polygon()); } } else // object not selected, all instances are unselected for (auto inst : model.objects[oidx]->instances) - unselected.emplace_back(inst); + unselected.emplace_back(inst->get_arrange_polygon()); } if (selected.empty()) selected.swap(unselected); @@ -1495,14 +1474,15 @@ struct Plater::priv m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info(); const Selection& sel = plater().get_selection(); + BoundingBoxf bedbb(plater().bed.get_shape()); auto arrinput = collect(plater().model, sel); m_selected.swap(arrinput[0]); m_unselected.swap(arrinput[1]); if (m_wti) sel.is_wipe_tower() ? - m_selected.emplace_back(&m_wti) : - m_unselected.emplace_back(&m_wti); + m_selected.emplace_back(m_wti.get_arrange_polygon()) : + m_unselected.emplace_back(m_wti.get_arrange_polygon()); } public: From 44f0e387dcb42b279fb561238aa49fac00dc5bd3 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Sat, 13 Jul 2019 10:37:21 +0200 Subject: [PATCH 316/627] Fix of #2621 --- src/slic3r/GUI/Plater.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b99b3a6aff..8c074709f7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -496,7 +496,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : option = m_og->get_option("fill_density"); option.opt.label = L("Infill"); option.opt.width = 7/*6*/; - option.opt.sidetext = " "; + option.opt.sidetext = " "; line.append_option(option); m_brim_width = config->opt_float("brim_width"); @@ -507,7 +507,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : def.gui_type = ""; def.set_default_value(new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }); option = Option(def, "brim"); - option.opt.sidetext = " "; + option.opt.sidetext = ""; line.append_option(option); auto wiping_dialog_btn = [config, this](wxWindow* parent) { From 63cf5edf28430d1040ec81b8fb93ddd162ac5f9f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 15 Jul 2019 11:49:30 +0200 Subject: [PATCH 317/627] Updated tooltips on custom gcodes to match actual PrusaSlicer behaviour to reflect recent changes --- src/libslic3r/PrintConfig.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 979d9b46e8..06bb0811f2 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -368,7 +368,8 @@ void PrintConfigDef::init_fff_params() def = this->add("end_filament_gcode", coStrings); def->label = L("End G-code"); - def->tooltip = L("This end procedure is inserted at the end of the output file, before the printer end gcode. " + def->tooltip = L("This end procedure is inserted at the end of the output file, before the printer end gcode (and " + "before any toolchange from this filament in case of multimaterial printers). " "Note that you can use placeholder variables for all Slic3r settings. " "If you have multiple extruders, the gcode is processed in extruder order."); def->multiline = true; @@ -1787,7 +1788,8 @@ void PrintConfigDef::init_fff_params() def = this->add("start_filament_gcode", coStrings); def->label = L("Start G-code"); - def->tooltip = L("This start procedure is inserted at the beginning, after any printer start gcode. " + def->tooltip = L("This start procedure is inserted at the beginning, after any printer start gcode (and " + "after any toolchange to this filament in case of multi-material printers). " "This is used to override settings for a specific filament. If Slic3r detects " "M104, M109, M140 or M190 in your custom codes, such commands will " "not be prepended automatically so you're free to customize the order " @@ -2042,9 +2044,10 @@ void PrintConfigDef::init_fff_params() def = this->add("toolchange_gcode", coString); def->label = L("Tool change G-code"); - def->tooltip = L("This custom code is inserted right before every extruder change. " - "Note that you can use placeholder variables for all Slic3r settings as well " - "as [previous_extruder] and [next_extruder]."); + def->tooltip = L("This custom code is inserted at every extruder change. If you don't leave this empty, you are " + "expected to take care of the toolchange yourself - PrusaSlicer will not output any other G-code to " + "change the filament. You can use placeholder variables for all Slic3r settings as well as [previous_extruder] " + "and [next_extruder], so e.g. the standard toolchange command can be scripted as T[next_extruder]."); def->multiline = true; def->full_width = true; def->height = 5; From a492360d198e5cee4bea21e6d1c44386ec3824a5 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 15 Jul 2019 11:59:54 +0200 Subject: [PATCH 318/627] Fix of the merge - missing Undo / Redo toolbar buttons. --- src/slic3r/GUI/GLCanvas3D.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c2948c2626..a878527bbc 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3647,9 +3647,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "undo"; -#if ENABLE_SVG_ICONS item.icon_filename = "undo_toolbar.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]"; item.sprite_id = 11; item.left.toggable = false; @@ -3663,9 +3661,7 @@ bool GLCanvas3D::_init_toolbar() return false; item.name = "redo"; -#if ENABLE_SVG_ICONS item.icon_filename = "redo_toolbar.svg"; -#endif // ENABLE_SVG_ICONS item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]"; item.sprite_id = 12; item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); }; From d2a3a36013050cf0c7eaed9007a05556baea0b42 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 15 Jul 2019 15:51:25 +0200 Subject: [PATCH 319/627] Fix of the SLA Undo --- src/slic3r/GUI/GLCanvas3D.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a878527bbc..a401cabbaf 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2016,9 +2016,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); if (it->new_geometry()) instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); - else - // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. - m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); + else { + // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. + m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); + m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); + } } } From dc80616bf6b5f5232fe30d96dcfa024da83ab0b9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 15 Jul 2019 17:09:06 +0200 Subject: [PATCH 320/627] Fixed a use-after-free problem in object list this was uncovered by ASAN when attempting to Delete All objects with multiple instances --- src/slic3r/GUI/wxExtensions.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 1def2915cd..7e8a2d92df 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -806,8 +806,12 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) if (node_parent) { if (node->m_type & (itInstanceRoot|itLayerRoot)) { - for (int i = node->GetChildCount() - 1; i >= (node->m_type & itInstanceRoot ? 1 : 0); i--) + // node can be deleted by the Delete, let's check its type while we safely can + bool is_instance_root = (node->m_type & itInstanceRoot); + + for (int i = node->GetChildCount() - 1; i >= (is_instance_root ? 1 : 0); i--) Delete(wxDataViewItem(node->GetNthChild(i))); + return parent; } From 1b0e192046e358ba34941d1895f86c94ad6c6ac0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 15 Jul 2019 17:30:44 +0200 Subject: [PATCH 321/627] Arrange cache in ModeInstance and logical bed remembered. --- src/libnest2d/include/libnest2d/libnest2d.hpp | 47 -- .../include/libnest2d/selections/firstfit.hpp | 9 +- src/libslic3r/Arrange.cpp | 45 +- src/libslic3r/Arrange.hpp | 169 ++++--- src/libslic3r/Model.cpp | 103 ++-- src/libslic3r/Model.hpp | 40 +- src/slic3r/GUI/GLCanvas3D.cpp | 7 +- src/slic3r/GUI/GLCanvas3D.hpp | 2 +- src/slic3r/GUI/Plater.cpp | 478 ++++++++++-------- 9 files changed, 488 insertions(+), 412 deletions(-) diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index a83a16ecf9..11c032fae2 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -127,25 +127,6 @@ public: inline _Item(TContour&& contour, THolesContainer&& holes): sh_(sl::create(std::move(contour), std::move(holes))) {} - -// template -// _Item(std::function applyfn, Args &&... args): -// _Item(std::forward(args)...) -// { -// applyfn_ = std::move(applyfn); -// } - - // Call the apply callback set in constructor. Within the callback, the - // original caller can apply the stored transformation to the original - // objects inteded for nesting. It might not be the shape handed over - // to _Item (e.g. arranging 3D shapes based on 2D silhouette or the - // client uses a simplified or processed polygon for nesting) - // This callback, if present, will be called for each item after the nesting - // is finished. -// inline void callApplyFunction(unsigned binidx) const -// { -// if (applyfn_) applyfn_(*this, binidx); -// } inline bool isFixed() const noexcept { return fixed_; } inline void markAsFixed(bool fixed = true) { fixed_ = fixed; } @@ -881,34 +862,6 @@ public: { return selector_.getResult(); } - -private: - - - // This function will be used only if the iterators are pointing to - // a type compatible with the libnets2d::_Item template. - // This way we can use references to input elements as they will - // have to exist for the lifetime of this call. -// template -// inline ConvertibleOnly _execute(It from, It to) -// { -// __execute(from, to); -// } - -// template inline void _execute(It from, It to) -// { -// auto infl = static_cast(std::ceil(min_obj_distance_/2.0)); -// if(infl > 0) std::for_each(from, to, [this](Item& item) { -// item.inflate(infl); -// }); - -// selector_.template packItems( -// from, to, bin_, pconfig_); - -// if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) { -// item.inflate(-infl); -// }); -// } }; } diff --git a/src/libnest2d/include/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp index 6a3e2e61b6..74207f8cf9 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -42,8 +42,13 @@ public: std::for_each(first, last, [this](Item& itm) { if(itm.isFixed()) { - if(packed_bins_.empty()) packed_bins_.emplace_back(); - packed_bins_.front().emplace_back(itm); + if (itm.binId() < 0) itm.binId(0); + auto binidx = size_t(itm.binId()); + + while(packed_bins_.size() <= binidx) + packed_bins_.emplace_back(); + + packed_bins_[binidx].emplace_back(itm); } else { store_.emplace_back(itm); } diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index c49c9fb972..fd3573699f 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -434,9 +434,7 @@ inline Circle to_lnCircle(const CircleBed& circ) { } // Get the type of bed geometry from a simple vector of points. -BedShapeHint bedShape(const Polyline &bed) { - BedShapeHint ret; - +BedShapeHint::BedShapeHint(const Polyline &bed) { auto x = [](const Point& p) { return p(X); }; auto y = [](const Point& p) { return p(Y); }; @@ -497,19 +495,16 @@ BedShapeHint bedShape(const Polyline &bed) { auto parea = poly_area(bed); if( (1.0 - parea/area(bb)) < 1e-3 ) { - ret.type = BedShapeType::BOX; - ret.shape.box = bb; + m_type = BedShapes::bsBox; + m_bed.box = bb; } else if(auto c = isCircle(bed)) { - ret.type = BedShapeType::CIRCLE; - ret.shape.circ = c; + m_type = BedShapes::bsCircle; + m_bed.circ = c; } else { - ret.type = BedShapeType::IRREGULAR; - ret.shape.polygon = bed; + m_type = BedShapes::bsIrregular; + m_bed.polygon = bed; } - - // Determine the bed shape by hand - return ret; } template // Arrange for arbitrary bin type @@ -588,6 +583,7 @@ void arrange(ArrangePolygons & arrangables, outp.emplace_back(std::move(clpath)); outp.back().rotation(rotation); outp.back().translation({offs.x(), offs.y()}); + outp.back().binId(arrpoly.bed_idx); }; for (ArrangePolygon &arrangeable : arrangables) @@ -596,6 +592,8 @@ void arrange(ArrangePolygons & arrangables, for (const ArrangePolygon &fixed: excludes) process_arrangeable(fixed, fixeditems); + for (Item &itm : fixeditems) itm.inflate(-2 * SCALED_EPSILON); + // Integer ceiling the min distance from the bed perimeters coord_t md = min_obj_dist - SCALED_EPSILON; md = (md % 2) ? md / 2 + 1 : md / 2; @@ -603,39 +601,38 @@ void arrange(ArrangePolygons & arrangables, auto &cfn = stopcondition; auto &pri = progressind; - switch (bedhint.type) { - case BedShapeType::BOX: { + switch (bedhint.get_type()) { + case bsBox: { // Create the arranger for the box shaped bed - BoundingBox bbb = bedhint.shape.box; + BoundingBox bbb = bedhint.get_box(); bbb.min -= Point{md, md}, bbb.max += Point{md, md}; Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; _arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn); break; } - case BedShapeType::CIRCLE: { - auto c = bedhint.shape.circ; - auto cc = to_lnCircle(c); + case bsCircle: { + auto cc = to_lnCircle(bedhint.get_circle()); _arrange(items, fixeditems, cc, min_obj_dist, pri, cfn); break; } - case BedShapeType::IRREGULAR: { - auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon); + case bsIrregular: { + auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular()); auto irrbed = sl::create(std::move(ctour)); - BoundingBox polybb(bedhint.shape.polygon); + BoundingBox polybb(bedhint.get_irregular()); _arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn); break; } - case BedShapeType::INFINITE: { - const InfiniteBed& nobin = bedhint.shape.infinite; + case bsInfinite: { + const InfiniteBed& nobin = bedhint.get_infinite(); auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()}); _arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn); break; } - case BedShapeType::UNKNOWN: { + case bsUnknown: { // We know nothing about the bed, let it be infinite and zero centered _arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn); break; diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index bc23108cd7..c705b612bf 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -22,97 +22,152 @@ public: inline operator bool() { return !std::isnan(radius_); } }; -/// Representing an unbounded bin +/// Representing an unbounded bed. struct InfiniteBed { Point center; }; /// Types of print bed shapes. -enum class BedShapeType { - BOX, - CIRCLE, - IRREGULAR, - INFINITE, - UNKNOWN +enum BedShapes { + bsBox, + bsCircle, + bsIrregular, + bsInfinite, + bsUnknown }; -/// Info about the print bed for the arrange() function. -struct BedShapeHint { - BedShapeType type = BedShapeType::INFINITE; - union BedShape_u { // I know but who cares... TODO: use variant from cpp17? +/// Info about the print bed for the arrange() function. This is a variant +/// holding one of the four shapes a bed can be. +class BedShapeHint { + BedShapes m_type = BedShapes::bsInfinite; + + union BedShape_u { // TODO: use variant from cpp17? CircleBed circ; BoundingBox box; Polyline polygon; - InfiniteBed infinite{}; + InfiniteBed infbed{}; ~BedShape_u() {} BedShape_u() {}; - } shape; + } m_bed; - BedShapeHint() {}; +public: + + BedShapeHint(){}; - ~BedShapeHint() { - if (type == BedShapeType::IRREGULAR) - shape.polygon.Slic3r::Polyline::~Polyline(); - }; - - BedShapeHint(const BedShapeHint &cpy) { - *this = cpy; + /// Get a bed shape hint for arrange() from a naked Polyline. + explicit BedShapeHint(const Polyline &polyl); + explicit BedShapeHint(const BoundingBox &bb) + { + m_type = bsBox; m_bed.box = bb; } - BedShapeHint& operator=(const BedShapeHint &cpy) { - type = cpy.type; - switch(type) { - case BedShapeType::BOX: shape.box = cpy.shape.box; break; - case BedShapeType::CIRCLE: shape.circ = cpy.shape.circ; break; - case BedShapeType::IRREGULAR: shape.polygon = cpy.shape.polygon; break; - case BedShapeType::INFINITE: shape.infinite = cpy.shape.infinite; break; - case BedShapeType::UNKNOWN: break; + explicit BedShapeHint(const CircleBed &c) + { + m_type = bsCircle; m_bed.circ = c; + } + + explicit BedShapeHint(const InfiniteBed &ibed) + { + m_type = bsInfinite; m_bed.infbed = ibed; + } + + ~BedShapeHint() + { + if (m_type == BedShapes::bsIrregular) + m_bed.polygon.Slic3r::Polyline::~Polyline(); + }; + + BedShapeHint(const BedShapeHint &cpy) { *this = cpy; } + BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); } + + BedShapeHint &operator=(const BedShapeHint &cpy) + { + m_type = cpy.m_type; + switch(m_type) { + case bsBox: m_bed.box = cpy.m_bed.box; break; + case bsCircle: m_bed.circ = cpy.m_bed.circ; break; + case bsIrregular: m_bed.polygon = cpy.m_bed.polygon; break; + case bsInfinite: m_bed.infbed = cpy.m_bed.infbed; break; + case bsUnknown: break; } return *this; } + + BedShapeHint& operator=(BedShapeHint &&cpy) + { + m_type = cpy.m_type; + switch(m_type) { + case bsBox: m_bed.box = std::move(cpy.m_bed.box); break; + case bsCircle: m_bed.circ = std::move(cpy.m_bed.circ); break; + case bsIrregular: m_bed.polygon = std::move(cpy.m_bed.polygon); break; + case bsInfinite: m_bed.infbed = std::move(cpy.m_bed.infbed); break; + case bsUnknown: break; + } + + return *this; + } + + BedShapes get_type() const { return m_type; } + + const BoundingBox &get_box() const + { + assert(m_type == bsBox); return m_bed.box; + } + const CircleBed &get_circle() const + { + assert(m_type == bsCircle); return m_bed.circ; + } + const Polyline &get_irregular() const + { + assert(m_type == bsIrregular); return m_bed.polygon; + } + const InfiniteBed &get_infinite() const + { + assert(m_type == bsInfinite); return m_bed.infbed; + } }; -/// Get a bed shape hint for arrange() from a naked Polyline. -BedShapeHint bedShape(const Polyline& bed); - -static const constexpr long UNARRANGED = -1; +/// A logical bed representing an object not being arranged. Either the arrange +/// has not yet succesfully run on this ArrangePolygon or it could not fit the +/// object due to overly large size or invalid geometry. +static const constexpr int UNARRANGED = -1; +/// Input/Output structure for the arrange() function. The poly field will not +/// be modified during arrangement. Instead, the translation and rotation fields +/// will mark the needed transformation for the polygon to be in the arranged +/// position. These can also be set to an initial offset and rotation. +/// +/// The bed_idx field will indicate the logical bed into which the +/// polygon belongs: UNARRANGED means no place for the polygon +/// (also the initial state before arrange), 0..N means the index of the bed. +/// Zero is the physical bed, larger than zero means a virtual bed. struct ArrangePolygon { - const ExPolygon poly; - Vec2crd translation{0, 0}; - double rotation{0.0}; - long bed_idx{UNARRANGED}; + const ExPolygon poly; /// The 2D silhouette to be arranged + Vec2crd translation{0, 0}; /// The translation of the poly + double rotation{0.0}; /// The rotation of the poly in radians + int bed_idx{UNARRANGED}; /// To which logical bed does poly belong... - ArrangePolygon(const ExPolygon &p, const Vec2crd &tr = {}, double rot = 0.0) - : poly{p}, translation{tr}, rotation{rot} + ArrangePolygon(ExPolygon p, const Vec2crd &tr = {}, double rot = 0.0) + : poly{std::move(p)}, translation{tr}, rotation{rot} {} }; using ArrangePolygons = std::vector; /** - * \brief Arranges the model objects on the screen. + * \brief Arranges the input polygons. * - * The arrangement considers multiple bins (aka. print beds) for placing - * all the items provided in the model argument. If the items don't fit on - * one print bed, the remaining will be placed onto newly created print - * beds. The first_bin_only parameter, if set to true, disables this - * behavior and makes sure that only one print bed is filled and the - * remaining items will be untouched. When set to false, the items which - * could not fit onto the print bed will be placed next to the print bed so - * the user should see a pile of items on the print bed and some other - * piles outside the print area that can be dragged later onto the print - * bed as a group. + * WARNING: Currently, only convex polygons are supported by the libnest2d + * library which is used to do the arrangement. This might change in the future + * this is why the interface contains a general polygon capable to have holes. * - * \param items Input which are object pointers implementing the - * Arrangeable interface. + * \param items Input vector of ArrangePolygons. The transformation, rotation + * and bin_idx fields will be changed after the call finished and can be used + * to apply the result on the input polygon. * * \param min_obj_distance The minimum distance which is allowed for any * pair of items on the print bed in any direction. * - * \param bedhint Info about the shape and type of the - * bed. remaining items which do not fit onto the print area next to the - * print bed or leave them untouched (let the user arrange them by hand or - * remove them). + * \param bedhint Info about the shape and type of the bed. * * \param progressind Progress indicator callback called when * an object gets packed. The unsigned argument is the number of items @@ -127,7 +182,7 @@ void arrange(ArrangePolygons & items, std::function stopcondition = nullptr); /// Same as the previous, only that it takes unmovable items as an -/// additional argument. +/// additional argument. Those will be considered as already arranged objects. void arrange(ArrangePolygons & items, const ArrangePolygons & excludes, coord_t min_obj_distance, diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 966be4c887..5f296cc5f8 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -372,35 +372,7 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb /* arrange objects preserving their instance count but altering their instance positions */ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) -{ - // get the (transformed) size of each instance so that we take - // into account their different transformations when packing -// Pointfs instance_sizes; -// Pointfs instance_centers; -// for (const ModelObject *o : this->objects) -// for (size_t i = 0; i < o->instances.size(); ++ i) { -// // an accurate snug bounding box around the transformed mesh. -// BoundingBoxf3 bbox(o->instance_bounding_box(i, true)); -// instance_sizes.emplace_back(to_2d(bbox.size())); -// instance_centers.emplace_back(to_2d(bbox.center())); -// } - -// Pointfs positions; -// if (! _arrange(instance_sizes, dist, bb, positions)) -// return false; - -// size_t idx = 0; -// for (ModelObject *o : this->objects) { -// for (ModelInstance *i : o->instances) { -// Vec2d offset_xy = positions[idx] - instance_centers[idx]; -// i->set_offset(Vec3d(offset_xy(0), offset_xy(1), i->get_offset(Z))); -// ++idx; -// } -// o->invalidate_bounding_box(); -// } - -// return true; - +{ size_t count = 0; for (auto obj : objects) count += obj->instances.size(); @@ -414,29 +386,23 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) instances.emplace_back(minst); } - arrangement::BedShapeHint bedhint; - - if (bb) { - bedhint.type = arrangement::BedShapeType::BOX; - bedhint.shape.box = BoundingBox(scaled(bb->min), scaled(bb->max)); - } - + + if (bb) + bedhint = arrangement::BedShapeHint( + BoundingBox(scaled(bb->min), scaled(bb->max))); + arrangement::arrange(input, scaled(dist), bedhint); bool ret = true; for(size_t i = 0; i < input.size(); ++i) { - auto inst = instances[i]; - inst->set_rotation(Z, input[i].rotation); - auto tr = unscaled(input[i].translation); - inst->set_offset(X, tr.x()); - inst->set_offset(Y, tr.y()); - - if (input[i].bed_idx != 0) ret = false; // no logical beds are allowed + if (input[i].bed_idx == 0) { // no logical beds are allowed + instances[i]->apply_arrange_result(input[i].translation, + input[i].rotation); + } else ret = false; } - return ret; } @@ -1842,28 +1808,37 @@ void ModelInstance::transform_polygon(Polygon* polygon) const arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const { static const double SIMPLIFY_TOLERANCE_MM = 0.1; - - Vec3d rotation = get_rotation(); - rotation.z() = 0.; - Transform3d trafo_instance = - Geometry::assemble_transform(Vec3d::Zero(), rotation, - get_scaling_factor(), get_mirror()); - - Polygon p = get_object()->convex_hull_2d(trafo_instance); - - assert(!p.points.empty()); - - // this may happen for malformed models, see: - // https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (p.points.empty()) return {{}}; - - Polygons pp{p}; - pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); - if (!pp.empty()) p = pp.front(); - ExPolygon ep; ep.contour = std::move(p); + if (!m_arrange_cache.valid) { + Vec3d rotation = get_rotation(); + rotation.z() = 0.; + Transform3d trafo_instance = + Geometry::assemble_transform(Vec3d::Zero(), rotation, + get_scaling_factor(), get_mirror()); - return {ep, Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}, get_rotation(Z)}; + Polygon p = get_object()->convex_hull_2d(trafo_instance); + + assert(!p.points.empty()); + + // this may happen for malformed models, see: + // https://github.com/prusa3d/PrusaSlicer/issues/2209 + if (p.points.empty()) return {{}}; + + Polygons pp{p}; + pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); + if (!pp.empty()) p = pp.front(); + m_arrange_cache.poly.contour = std::move(p); + m_arrange_cache.valid = true; + } + + arrangement::ArrangePolygon ret{m_arrange_cache.poly, + Vec2crd{scaled(get_offset(X)), + scaled(get_offset(Y))}, + get_rotation(Z)}; + + ret.bed_idx = m_arrange_cache.bed_idx; + + return ret; } // Test whether the two models contain the same number of ModelObjects with the same set of IDs diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 2fd696d143..7ce790c7c7 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -512,7 +512,7 @@ public: ModelObject* get_object() const { return this->object; } const Geometry::Transformation& get_transformation() const { return m_transformation; } - void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } + void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; m_arrange_cache.valid = false; } const Vec3d& get_offset() const { return m_transformation.get_offset(); } double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } @@ -523,21 +523,21 @@ public: const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } - void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } - void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } + void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); m_arrange_cache.valid = false; } + void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); if (axis != Z) m_arrange_cache.valid = false; } const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); } double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } - void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } - void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } + void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); m_arrange_cache.valid = false; } + void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); m_arrange_cache.valid = false; } const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } bool is_left_handed() const { return m_transformation.is_left_handed(); } - - void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } - void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } + + void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); m_arrange_cache.valid = false; } + void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); m_arrange_cache.valid = false; } // To be called on an external mesh void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; @@ -554,20 +554,17 @@ public: bool is_printable() const { return print_volume_state == PVS_Inside; } - // ///////////////////////////////////////////////////////////////////////// - // Implement arrangement::Arrangeable interface - // ///////////////////////////////////////////////////////////////////////// - // Getting the input polygon for arrange arrangement::ArrangePolygon get_arrange_polygon() const; // Apply the arrange result on the ModelInstance - void apply_arrange_result(Vec2crd offs, double rot_rads) + void apply_arrange_result(Vec2crd offs, double rot_rads, int bed_idx = 0) { // write the transformation data into the model instance set_rotation(Z, rot_rads); set_offset(X, unscale(offs(X))); set_offset(Y, unscale(offs(Y))); + m_arrange_cache.bed_idx = bed_idx; } protected: @@ -583,15 +580,28 @@ private: ModelObject* object; // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) {} + explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) + { + get_arrange_polygon(); // initialize the arrange cache + } // Constructor, which assigns a new unique ID. explicit ModelInstance(ModelObject *object, const ModelInstance &other) : - m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) {} + m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) + { + get_arrange_polygon(); // initialize the arrange cache + } ModelInstance() = delete; explicit ModelInstance(ModelInstance &&rhs) = delete; ModelInstance& operator=(const ModelInstance &rhs) = delete; ModelInstance& operator=(ModelInstance &&rhs) = delete; + + // Warning! This object is not guarded against concurrency. + mutable struct ArrangeCache { + bool valid = false; + int bed_idx { arrangement::UNARRANGED }; + ExPolygon poly; + } m_arrange_cache; }; // The print bed content. diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 4e30934891..771d092a7f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5739,10 +5739,11 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } -void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2d offset, double rotation_rads) +void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2crd off, double rotation_rads) { - m_pos = offset; - m_rotation = rotation_rads; + Vec2d offset = unscaled(off); + m_pos = offset; + m_rotation = rotation_rads; DynamicPrintConfig cfg; cfg.opt("wipe_tower_x", true)->value = m_pos(X); cfg.opt("wipe_tower_y", true)->value = m_pos(Y); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 2316637d82..a28791ed02 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -624,7 +624,7 @@ public: return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); } - void apply_arrange_result(Vec2d offset, double rotation_rads); + void apply_arrange_result(Vec2crd offset, double rotation_rads); arrangement::ArrangePolygon get_arrange_polygon() const { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3adfc0f059..f615c66d14 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1272,147 +1272,290 @@ struct Plater::priv // 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; + class Job : public wxEvtHandler + { + int m_range = 100; std::future m_ftr; - priv *m_plater = nullptr; - std::atomic m_running {false}, m_canceled {false}; - bool m_finalized = false; - - void run() { - m_running.store(true); process(); m_running.store(false); - + priv * m_plater = nullptr; + std::atomic m_running{false}, m_canceled{false}; + bool m_finalized = false; + + void run() + { + m_running.store(true); + process(); + m_running.store(false); + // ensure to call the last status to finalize the job update_status(status_range(), ""); } - + protected: - // status range for a particular job virtual int status_range() const { return 100; } - + // status update, to be used from the work thread (process() method) - void update_status(int st, const wxString& msg = "") { - auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg); - wxQueueEvent(this, evt); + void update_status(int st, const wxString &msg = "") + { + auto evt = new wxThreadEvent(); + evt->SetInt(st); + evt->SetString(msg); + wxQueueEvent(this, evt); } - - priv& plater() { return *m_plater; } - bool was_canceled() const { return m_canceled.load(); } - + + priv &plater() { return *m_plater; } + bool was_canceled() const { return m_canceled.load(); } + // Launched just before start(), a job can use it to prepare internals virtual void prepare() {} - - // Launched when the job is finished. It refreshes the 3dscene by def. - virtual void finalize() { + + // Launched when the job is finished. It refreshes the 3Dscene by def. + virtual void finalize() + { // Do a full refresh of scene tree, including regenerating // all the GLVolumes. FIXME The update function shall just // reload the modified matrices. - if(! was_canceled()) - plater().update(true); + if (!was_canceled()) plater().update(true); } - + public: - - Job(priv *_plater): m_plater(_plater) + Job(priv *_plater) : m_plater(_plater) { - Bind(wxEVT_THREAD, [this](const wxThreadEvent& evt){ + Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { auto msg = evt.GetString(); - if(! msg.empty()) plater().statusbar()->set_status_text(msg); - - if(m_finalized) return; - + if (!msg.empty()) + plater().statusbar()->set_status_text(msg); + + if (m_finalized) return; + plater().statusbar()->set_progress(evt.GetInt()); - if(evt.GetInt() == status_range()) { - + if (evt.GetInt() == status_range()) { // set back the original range and cancel callback plater().statusbar()->set_range(m_range); plater().statusbar()->set_cancel_callback(); wxEndBusyCursor(); - + finalize(); - + // dont do finalization again for the same process m_finalized = true; } }); } - - // TODO: use this when we all migrated to VS2019 - // Job(const Job&) = delete; - // Job(Job&&) = default; - // Job& operator=(const Job&) = delete; - // Job& operator=(Job&&) = default; - Job(const Job&) = delete; - Job& operator=(const Job&) = delete; - Job(Job &&o) : - m_range(o.m_range), - m_ftr(std::move(o.m_ftr)), - m_plater(o.m_plater), - m_finalized(o.m_finalized) - { - m_running.store(o.m_running.load()); - m_canceled.store(o.m_canceled.load()); - } - + + Job(const Job &) = delete; + Job(Job &&) = default; + Job &operator=(const Job &) = delete; + Job &operator=(Job &&) = default; + virtual void process() = 0; - - void start() { // Start the job. No effect if the job is already running - if(! m_running.load()) { - - prepare(); - + + void start() + { // Start the job. No effect if the job is already running + if (!m_running.load()) { + prepare(); + // Save the current status indicatior range and push the new one m_range = plater().statusbar()->get_range(); plater().statusbar()->set_range(status_range()); - + // init cancellation flag and set the cancel callback m_canceled.store(false); - plater().statusbar()->set_cancel_callback( [this](){ - m_canceled.store(true); - }); - + plater().statusbar()->set_cancel_callback( + [this]() { m_canceled.store(true); }); + m_finalized = false; - + // Changing cursor to busy wxBeginBusyCursor(); - - try { // Execute the job + + try { // Execute the job m_ftr = std::async(std::launch::async, &Job::run, this); - } catch(std::exception& ) { - update_status(status_range(), - _(L("ERROR: not enough resources to execute a new job."))); + } catch (std::exception &) { + update_status(status_range(), + _(L("ERROR: not enough resources to " + "execute a new job."))); } - + // The state changes will be undone when the process hits the // last status value, in the status update handler (see ctor) } } - - // To wait for the running job and join the threads. False is returned - // if the timeout has been reached and the job is still running. Call - // cancel() before this fn if you want to explicitly end the job. - bool join(int timeout_ms = 0) const { - if(!m_ftr.valid()) return true; - - if(timeout_ms <= 0) + + // To wait for the running job and join the threads. False is + // returned if the timeout has been reached and the job is still + // running. Call cancel() before this fn if you want to explicitly + // end the job. + bool join(int timeout_ms = 0) const + { + if (!m_ftr.valid()) return true; + + if (timeout_ms <= 0) m_ftr.wait(); - else if(m_ftr.wait_for(std::chrono::milliseconds(timeout_ms)) == - std::future_status::timeout) + else if (m_ftr.wait_for(std::chrono::milliseconds( + timeout_ms)) == std::future_status::timeout) return false; - + return true; } - + bool is_running() const { return m_running.load(); } void cancel() { m_canceled.store(true); } }; - + enum class Jobs : size_t { Arrange, Rotoptimize }; + class ArrangeJob : public Job + { + // 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.; + + // Cache the wti info + GLCanvas3D::WipeTowerInfo m_wti; + + // Cache the selected instances needed to write back the arrange + // result. The order of instances is the same as the arrange polys + struct IndexedArrangePolys { + ModelInstancePtrs insts; + arrangement::ArrangePolygons polys; + + void reserve(size_t cap) { insts.reserve(cap); polys.reserve(cap); } + void clear() { insts.clear(); polys.clear(); } + + void emplace_back(ModelInstance *inst) { + insts.emplace_back(inst); + polys.emplace_back(inst->get_arrange_polygon()); + } + + void swap(IndexedArrangePolys &pp) { + insts.swap(pp.insts); polys.swap(pp.polys); + } + }; + + IndexedArrangePolys m_selected, m_unselected; + + protected: + + void prepare() override + { + m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info(); + + // Get the selection map + Selection& sel = plater().get_selection(); + const Selection::ObjectIdxsToInstanceIdxsMap &selmap = + sel.get_content(); + + Model &model = plater().model; + + size_t count = 0; // To know how much space to reserve + for (auto obj : model.objects) count += obj->instances.size(); + + m_selected.clear(), m_unselected.clear(); + m_selected.reserve(count + 1 /* for optional wti */); + m_unselected.reserve(count + 1 /* for optional wti */); + + // Go through the objects and check if inside the selection + for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { + auto oit = selmap.find(int(oidx)); + + if (oit != selmap.end()) { // Object is selected + auto &iids = oit->second; + + // Go through instances and check if inside selection + size_t instcnt = model.objects[oidx]->instances.size(); + for (size_t iidx = 0; iidx < instcnt; ++iidx) { + auto instit = iids.find(iidx); + ModelInstance *oi = model.objects[oidx] + ->instances[iidx]; + + // Instance is selected + instit != iids.end() ? + m_selected.emplace_back(oi) : + m_unselected.emplace_back(oi); + } + } else // object not selected, all instances are unselected + for (ModelInstance *oi : model.objects[oidx]->instances) + m_unselected.emplace_back(oi); + } + + // If the selection is completely empty, consider all items as the + // selection + if (m_selected.insts.empty() && m_selected.polys.empty()) + m_selected.swap(m_unselected); + + if (m_wti) + sel.is_wipe_tower() ? + m_selected.polys.emplace_back(m_wti.get_arrange_polygon()) : + m_unselected.polys.emplace_back(m_wti.get_arrange_polygon()); + + // Stride between logical beds + double bedwidth = plater().bed_shape_bb().size().x(); + coord_t stride = scaled((1. + LOGICAL_BED_GAP) * bedwidth); + + for (arrangement::ArrangePolygon &ap : m_selected.polys) + if (ap.bed_idx > 0) ap.translation.x() -= ap.bed_idx * stride; + + for (arrangement::ArrangePolygon &ap : m_unselected.polys) + if (ap.bed_idx > 0) ap.translation.x() -= ap.bed_idx * stride; + } + + public: + using Job::Job; + + + int status_range() const override + { + return int(m_selected.polys.size()); + } + + void process() override; + + void finalize() override { + + if (was_canceled()) { // Ignore the arrange result if aborted. + Job::finalize(); + return; + } + + // Stride between logical beds + double bedwidth = plater().bed_shape_bb().size().x(); + coord_t stride = scaled((1. + LOGICAL_BED_GAP) * bedwidth); + + for(size_t i = 0; i < m_selected.insts.size(); ++i) { + if (m_selected.polys[i].bed_idx != arrangement::UNARRANGED) { + Vec2crd offs = m_selected.polys[i].translation; + double rot = m_selected.polys[i].rotation; + int bdidx = m_selected.polys[i].bed_idx; + offs.x() += bdidx * stride; + m_selected.insts[i]->apply_arrange_result(offs, rot, bdidx); + } + } + + // Handle the wipe tower + const arrangement::ArrangePolygon &wtipoly = m_selected.polys.back(); + if (m_wti && wtipoly.bed_idx != arrangement::UNARRANGED) { + Vec2crd o = wtipoly.translation; + double r = wtipoly.rotation; + o.x() += wtipoly.bed_idx * stride; + m_wti.apply_arrange_result(o, r); + } + + // Call original finalize (will update the scene) + Job::finalize(); + } + }; + + class RotoptimizeJob : public Job + { + public: + using Job::Job; + 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. @@ -1422,84 +1565,8 @@ struct Plater::priv priv * m_plater; - class ArrangeJob : public Job - { - GLCanvas3D::WipeTowerInfo m_wti; - arrangement::ArrangePolygons m_selected, m_unselected; - - static std::array collect( - Model &model, const Selection &sel) - { - const Selection::ObjectIdxsToInstanceIdxsMap &selmap = - sel.get_content(); - - size_t count = 0; - for (auto obj : model.objects) count += obj->instances.size(); - - arrangement::ArrangePolygons selected, unselected; - selected.reserve(count + 1 /* for optional wti */); - unselected.reserve(count + 1 /* for optional wti */); - - for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { - auto oit = selmap.find(int(oidx)); - - if (oit != selmap.end()) { - auto &iids = oit->second; - - for (size_t iidx = 0; - iidx < model.objects[oidx]->instances.size(); - ++iidx) - { - auto instit = iids.find(iidx); - ModelInstance *inst = model.objects[oidx] - ->instances[iidx]; - instit == iids.end() ? - unselected.emplace_back(inst->get_arrange_polygon()) : - selected.emplace_back(inst->get_arrange_polygon()); - } - } else // object not selected, all instances are unselected - for (auto inst : model.objects[oidx]->instances) - unselected.emplace_back(inst->get_arrange_polygon()); - } - - if (selected.empty()) selected.swap(unselected); - - return {selected, unselected}; - } - - protected: - - void prepare() override - { - m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info(); - - const Selection& sel = plater().get_selection(); - BoundingBoxf bedbb(plater().bed.get_shape()); - auto arrinput = collect(plater().model, sel); - m_selected.swap(arrinput[0]); - m_unselected.swap(arrinput[1]); - - if (m_wti) - sel.is_wipe_tower() ? - m_selected.emplace_back(m_wti.get_arrange_polygon()) : - m_unselected.emplace_back(m_wti.get_arrange_polygon()); - } - - public: - using Job::Job; - int status_range() const override - { - return int(m_selected.size()); - } - void process() override; - } arrange_job{m_plater}; - - class RotoptimizeJob : public Job - { - public: - using Job::Job; - void process() override; - } rotoptimize_job{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 @@ -2447,50 +2514,47 @@ void Plater::priv::sla_optimize_rotation() { } arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { - arrangement::BedShapeHint bedshape; const auto *bed_shape_opt = config->opt("bed_shape"); assert(bed_shape_opt); - if (bed_shape_opt) { - auto &bedpoints = bed_shape_opt->values; - Polyline bedpoly; bedpoly.points.reserve(bedpoints.size()); - for (auto &v : bedpoints) bedpoly.append(scaled(v)); - bedshape = arrangement::bedShape(bedpoly); - } + if (!bed_shape_opt) return {}; - return bedshape; + 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::ExclusiveJobGroup::ArrangeJob::process() { - auto count = unsigned(m_selected.size()); - plater().model.arrange_objects(6.f, nullptr); -// static const auto arrangestr = _(L("Arranging")); +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); -// } + // 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_obj_distance = scaled(dist); -// auto count = unsigned(m_selected.size()); -// arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); + coord_t min_obj_distance = scaled(dist); + auto count = unsigned(m_selected.polys.size()); + arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); -// try { -// arrangement::arrange(m_selected, m_unselected, min_obj_distance, -// bedshape, -// [this, count](unsigned st) { -// if (st > 0) // will not finalize after last one -// update_status(count - st, arrangestr); -// }, -// [this]() { return was_canceled(); }); -// } catch (std::exception & /*e*/) { -// GUI::show_error(plater().q, -// _(L("Could not arrange model objects! " -// "Some geometries may be invalid."))); -// } + try { + arrangement::arrange(m_selected.polys, m_unselected.polys, + min_obj_distance, + bedshape, + [this, count](unsigned st) { + if (st > 0) // will not finalize after last one + update_status(count - st, arrangestr); + }, + [this]() { return was_canceled(); }); + } 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), @@ -2503,11 +2567,27 @@ void find_new_position(const Model & model, coord_t min_d, const arrangement::BedShapeHint &bedhint) { + arrangement::ArrangePolygons movable, fixed; - // TODO + for (const ModelObject *mo : model.objects) + for (const ModelInstance *inst : mo->instances) { + auto it = std::find(instances.begin(), instances.end(), inst); + if (it != instances.end()) + fixed.emplace_back(inst->get_arrange_polygon()); + } + + for (ModelInstance *inst : instances) + movable.emplace_back(inst->get_arrange_polygon()); + + arrangement::arrange(movable, fixed, min_d, bedhint); + + for (size_t i = 0; i < instances.size(); ++i) + if (movable[i].bed_idx == 0) + instances[i]->apply_arrange_result(movable[i].translation, + movable[i].rotation); } -void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process() +void Plater::priv::RotoptimizeJob::process() { int obj_idx = plater().get_selected_object_idx(); if (obj_idx < 0) { return; } From 5446167c1112fda5c2baefd10715ad0af48416d8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 15 Jul 2019 18:18:34 +0200 Subject: [PATCH 322/627] Fixes for arranging wipe tower --- src/slic3r/GUI/Plater.cpp | 90 ++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f615c66d14..3ae5c3005e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1481,16 +1481,16 @@ struct Plater::priv for (ModelInstance *oi : model.objects[oidx]->instances) m_unselected.emplace_back(oi); } - - // If the selection is completely empty, consider all items as the - // selection - if (m_selected.insts.empty() && m_selected.polys.empty()) - m_selected.swap(m_unselected); - + if (m_wti) sel.is_wipe_tower() ? m_selected.polys.emplace_back(m_wti.get_arrange_polygon()) : m_unselected.polys.emplace_back(m_wti.get_arrange_polygon()); + + // If the selection is completely empty, consider all items as the + // selection + if (m_selected.insts.empty() && m_selected.polys.empty()) + m_selected.swap(m_unselected); // Stride between logical beds double bedwidth = plater().bed_shape_bb().size().x(); @@ -1534,16 +1534,18 @@ struct Plater::priv m_selected.insts[i]->apply_arrange_result(offs, rot, bdidx); } } - + // Handle the wipe tower - const arrangement::ArrangePolygon &wtipoly = m_selected.polys.back(); - if (m_wti && wtipoly.bed_idx != arrangement::UNARRANGED) { - Vec2crd o = wtipoly.translation; - double r = wtipoly.rotation; - o.x() += wtipoly.bed_idx * stride; - m_wti.apply_arrange_result(o, r); + if (m_wti && m_selected.polys.size() > m_selected.insts.size()) { + auto &wtipoly = m_selected.polys.back(); + if (wtipoly.bed_idx != arrangement::UNARRANGED) { + Vec2crd o = wtipoly.translation; + double r = wtipoly.rotation; + o.x() += wtipoly.bed_idx * stride; + m_wti.apply_arrange_result(o, r); + } } - + // Call original finalize (will update the scene) Job::finalize(); } @@ -1638,6 +1640,8 @@ struct Plater::priv 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 load_files(const std::vector& input_files, bool load_model, bool load_config); std::vector load_model_objects(const ModelObjectPtrs &model_objects); wxString get_export_file(GUI::FileType file_type); @@ -2527,6 +2531,32 @@ arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { return arrangement::BedShapeHint(bedpoly); } +void Plater::priv::find_new_position(const ModelInstancePtrs &instances, coord_t min_d) +{ + arrangement::ArrangePolygons movable, fixed; + + for (const ModelObject *mo : model.objects) + for (const ModelInstance *inst : mo->instances) { + auto it = std::find(instances.begin(), instances.end(), inst); + auto arrpoly = inst->get_arrange_polygon(); + + if (it == instances.end()) + fixed.emplace_back(std::move(arrpoly)); + else + movable.emplace_back(std::move(arrpoly)); + } + + auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); + if (wti) fixed.emplace_back(wti.get_arrange_polygon()); + + arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint()); + + for (size_t i = 0; i < instances.size(); ++i) + if (movable[i].bed_idx == 0) + instances[i]->apply_arrange_result(movable[i].translation, + movable[i].rotation); +} + void Plater::priv::ArrangeJob::process() { static const auto arrangestr = _(L("Arranging")); @@ -2562,31 +2592,6 @@ void Plater::priv::ArrangeJob::process() { : _(L("Arranging done."))); } -void find_new_position(const Model & model, - ModelInstancePtrs instances, - coord_t min_d, - const arrangement::BedShapeHint &bedhint) -{ - arrangement::ArrangePolygons movable, fixed; - - for (const ModelObject *mo : model.objects) - for (const ModelInstance *inst : mo->instances) { - auto it = std::find(instances.begin(), instances.end(), inst); - if (it != instances.end()) - fixed.emplace_back(inst->get_arrange_polygon()); - } - - for (ModelInstance *inst : instances) - movable.emplace_back(inst->get_arrange_polygon()); - - arrangement::arrange(movable, fixed, min_d, bedhint); - - for (size_t i = 0; i < instances.size(); ++i) - if (movable[i].bed_idx == 0) - instances[i]->apply_arrange_result(movable[i].translation, - movable[i].rotation); -} - void Plater::priv::RotoptimizeJob::process() { int obj_idx = plater().get_selected_object_idx(); @@ -2624,11 +2629,8 @@ void Plater::priv::RotoptimizeJob::process() oi->set_rotation(rt); } - - find_new_position(plater().model, - o->instances, - scaled(mindist), - plater().get_bed_shape_hint()); + + plater().find_new_position(o->instances, scaled(mindist)); // Correct the z offset of the object which was corrupted be // the rotation From 4865240a9cd79069cc9e72bfa2e44451c55ef5c6 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 16 Jul 2019 09:19:00 +0200 Subject: [PATCH 323/627] Fixed compilation issue --- src/libslic3r/PrintObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 72fab87399..37cf0cccc7 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -2351,7 +2351,7 @@ void PrintObject::discover_horizontal_shells() // Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom'; size_t solid_layers = (type == stTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value; - for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - i) < solid_layers; (type == stTop) ? -- n : ++ n) { + for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - (int)i) < solid_layers; (type == stTop) ? -- n : ++ n) { if (n < 0 || n >= int(m_layers.size())) continue; // Slic3r::debugf " looking for neighbors on layer %d...\n", $n; From 52ab8a5f19892af2874a670f19e93b8bcd94a00e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 16 Jul 2019 13:06:58 +0200 Subject: [PATCH 324/627] Wipe tower fix (do not skip the first toolchange when printing without the wipe tower) Also, test multi.t updated so it matches new logic of inserting custom gcodes --- src/libslic3r/GCode.cpp | 21 ++++++++++----------- t/multi.t | 4 +++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index cd0f8142d7..37a14d42c2 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2811,17 +2811,16 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) gcode += m_ooze_prevention.pre_toolchange(*this); const std::string& toolchange_gcode = m_config.toolchange_gcode.value; - if (m_writer.extruder() != nullptr) { - // Process the custom toolchange_gcode. If it is empty, insert just a Tn command. - if (!toolchange_gcode.empty()) { - DynamicConfig config; - config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id())); - config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); - gcode += placeholder_parser_process("toolchange_gcode", toolchange_gcode, extruder_id, &config); - check_add_eol(gcode); - } + + // Process the custom toolchange_gcode. If it is empty, insert just a Tn command. + if (!toolchange_gcode.empty()) { + DynamicConfig config; + config.set_key_value("previous_extruder", new ConfigOptionInt((int)(m_writer.extruder() != nullptr ? m_writer.extruder()->id() : -1 ))); + config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += placeholder_parser_process("toolchange_gcode", toolchange_gcode, extruder_id, &config); + check_add_eol(gcode); } // We inform the writer about what is happening, but we may not use the resulting gcode. diff --git a/t/multi.t b/t/multi.t index 75ce0c286c..8e7225bec7 100644 --- a/t/multi.t +++ b/t/multi.t @@ -25,7 +25,9 @@ use Slic3r::Test; $config->set('extruder_offset', [ [0,0], [20,0], [0,20], [20,20] ]); $config->set('temperature', [200, 180, 170, 160]); $config->set('first_layer_temperature', [206, 186, 166, 156]); - $config->set('toolchange_gcode', ';toolchange'); # test that it doesn't crash when this is supplied + $config->set('toolchange_gcode', 'T[next_extruder] ;toolchange'); # test that it doesn't crash when this is supplied + # Since July 2019, PrusaSlicer only emits automatic Tn command in case that the toolchange_gcode is empty + # The "T[next_extruder]" is therefore needed in this test. my $print = Slic3r::Test::init_print('20mm_cube', config => $config); From 44801f442932dfc3c61c5bbe6aa2eff597e448b5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 16 Jul 2019 18:33:42 +0200 Subject: [PATCH 325/627] Priority for wipe tower, Increased safety distance from bed edges. * WipeTowerInfo class extended in plater (WipeTower) instead of GLCanvas3D * Bed origin support in ModelInstance and WipeTower --- src/libnest2d/include/libnest2d/libnest2d.hpp | 6 +- .../include/libnest2d/selections/firstfit.hpp | 5 +- src/libslic3r/Arrange.cpp | 3 +- src/libslic3r/Arrange.hpp | 13 +- src/libslic3r/Model.cpp | 34 +-- src/libslic3r/Model.hpp | 15 +- src/slic3r/GUI/GLCanvas3D.cpp | 5 +- src/slic3r/GUI/GLCanvas3D.hpp | 25 +-- src/slic3r/GUI/Plater.cpp | 211 +++++++++--------- 9 files changed, 164 insertions(+), 153 deletions(-) diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 11c032fae2..d91b3c8f9e 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -68,7 +68,7 @@ class _Item { BBCache(): valid(false) {} } bb_cache_; - int binid_{BIN_ID_UNSET}; + int binid_{BIN_ID_UNSET}, priority_{0}; bool fixed_{false}; public: @@ -130,8 +130,12 @@ public: inline bool isFixed() const noexcept { return fixed_; } inline void markAsFixed(bool fixed = true) { fixed_ = fixed; } + inline void binId(int idx) { binid_ = idx; } inline int binId() const noexcept { return binid_; } + + inline void priority(int p) { priority_ = p; } + inline int priority() const noexcept { return priority_; } /** * @brief Convert the polygon to string representation. The format depends diff --git a/src/libnest2d/include/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp index 74207f8cf9..5585e565d5 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -62,9 +62,10 @@ public: placers.back().configure(pconfig); placers.back().preload(ig); } - + auto sortfunc = [](Item& i1, Item& i2) { - return i1.area() > i2.area(); + int p1 = i1.priority(), p2 = i2.priority(); + return p1 == p2 ? i1.area() > i2.area() : p1 > p2; }; std::sort(store_.begin(), store_.end(), sortfunc); diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index fd3573699f..8086dc3fd6 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -584,6 +584,7 @@ void arrange(ArrangePolygons & arrangables, outp.back().rotation(rotation); outp.back().translation({offs.x(), offs.y()}); outp.back().binId(arrpoly.bed_idx); + outp.back().priority(arrpoly.priority); }; for (ArrangePolygon &arrangeable : arrangables) @@ -595,7 +596,7 @@ void arrange(ArrangePolygons & arrangables, for (Item &itm : fixeditems) itm.inflate(-2 * SCALED_EPSILON); // Integer ceiling the min distance from the bed perimeters - coord_t md = min_obj_dist - SCALED_EPSILON; + coord_t md = min_obj_dist - 2 * scaled(0.1 + EPSILON); md = (md % 2) ? md / 2 + 1 : md / 2; auto &cfn = stopcondition; diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index c705b612bf..3391eb0d75 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -140,15 +140,18 @@ static const constexpr int UNARRANGED = -1; /// polygon belongs: UNARRANGED means no place for the polygon /// (also the initial state before arrange), 0..N means the index of the bed. /// Zero is the physical bed, larger than zero means a virtual bed. -struct ArrangePolygon { - const ExPolygon poly; /// The 2D silhouette to be arranged +struct ArrangePolygon { + ExPolygon poly; /// The 2D silhouette to be arranged Vec2crd translation{0, 0}; /// The translation of the poly double rotation{0.0}; /// The rotation of the poly in radians int bed_idx{UNARRANGED}; /// To which logical bed does poly belong... + int priority{0}; - ArrangePolygon(ExPolygon p, const Vec2crd &tr = {}, double rot = 0.0) - : poly{std::move(p)}, translation{tr}, rotation{rot} - {} + /// Optional setter function which can store arbitrary data in its closure + std::function setter = nullptr; + + /// Helper function to call the setter with the arrange data arguments + void apply() const { if (setter) setter(*this); } }; using ArrangePolygons = std::vector; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 5f296cc5f8..cf0f7c8556 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -387,20 +387,24 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) } arrangement::BedShapeHint bedhint; - - if (bb) + coord_t bedwidth = 0; + + if (bb) { + bedwidth = scaled(bb->size().x()); bedhint = arrangement::BedShapeHint( BoundingBox(scaled(bb->min), scaled(bb->max))); + } arrangement::arrange(input, scaled(dist), bedhint); bool ret = true; + coord_t stride = bedwidth + bedwidth / 5; for(size_t i = 0; i < input.size(); ++i) { - if (input[i].bed_idx == 0) { // no logical beds are allowed - instances[i]->apply_arrange_result(input[i].translation, - input[i].rotation); - } else ret = false; + if (input[i].bed_idx != 0) ret = false; + if (input[i].bed_idx >= 0) + instances[i]->apply_arrange_result(input[i], + {input[i].bed_idx * stride, 0}); } return ret; @@ -1822,22 +1826,24 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const // this may happen for malformed models, see: // https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (p.points.empty()) return {{}}; + if (p.points.empty()) return {}; Polygons pp{p}; pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); if (!pp.empty()) p = pp.front(); m_arrange_cache.poly.contour = std::move(p); + m_arrange_cache.bed_origin = {0, 0}; + m_arrange_cache.bed_idx = arrangement::UNARRANGED; m_arrange_cache.valid = true; } - arrangement::ArrangePolygon ret{m_arrange_cache.poly, - Vec2crd{scaled(get_offset(X)), - scaled(get_offset(Y))}, - get_rotation(Z)}; - - ret.bed_idx = m_arrange_cache.bed_idx; - + arrangement::ArrangePolygon ret; + ret.poly = m_arrange_cache.poly; + ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))} - + m_arrange_cache.bed_origin; + ret.rotation = get_rotation(Z); + ret.bed_idx = m_arrange_cache.bed_idx; + return ret; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 7ce790c7c7..548e0f015d 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -558,13 +558,15 @@ public: arrangement::ArrangePolygon get_arrange_polygon() const; // Apply the arrange result on the ModelInstance - void apply_arrange_result(Vec2crd offs, double rot_rads, int bed_idx = 0) + void apply_arrange_result(const arrangement::ArrangePolygon& ap, + const Vec2crd& bed_origin = {0, 0}) { // write the transformation data into the model instance - set_rotation(Z, rot_rads); - set_offset(X, unscale(offs(X))); - set_offset(Y, unscale(offs(Y))); - m_arrange_cache.bed_idx = bed_idx; + set_rotation(Z, ap.rotation); + set_offset(X, unscale(ap.translation(X) + bed_origin.x())); + set_offset(Y, unscale(ap.translation(Y) + bed_origin.y())); + m_arrange_cache.bed_origin = bed_origin; + m_arrange_cache.bed_idx = ap.bed_idx; } protected: @@ -599,8 +601,9 @@ private: // Warning! This object is not guarded against concurrency. mutable struct ArrangeCache { bool valid = false; - int bed_idx { arrangement::UNARRANGED }; + Vec2crd bed_origin {0, 0}; ExPolygon poly; + int bed_idx = arrangement::UNARRANGED; } m_arrange_cache; }; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 771d092a7f..c674172e3a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5739,11 +5739,8 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } -void GLCanvas3D::WipeTowerInfo::apply_arrange_result(Vec2crd off, double rotation_rads) +void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const { - Vec2d offset = unscaled(off); - m_pos = offset; - m_rotation = rotation_rads; DynamicPrintConfig cfg; cfg.opt("wipe_tower_x", true)->value = m_pos(X); cfg.opt("wipe_tower_y", true)->value = m_pos(Y); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index a28791ed02..6c5e6475c5 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -613,9 +613,10 @@ public: int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } class WipeTowerInfo { + protected: Vec2d m_pos = {std::nan(""), std::nan("")}; - Vec2d m_bb_size; - double m_rotation; + Vec2d m_bb_size = {0., 0.}; + double m_rotation = 0.; friend class GLCanvas3D; public: @@ -623,22 +624,12 @@ public: { return !std::isnan(m_pos.x()) && !std::isnan(m_pos.y()); } - - void apply_arrange_result(Vec2crd offset, double rotation_rads); - arrangement::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)}, - }); - - ExPolygon ep; ep.contour = std::move(p); - return {ep, scaled(m_pos), m_rotation}; - } + inline const Vec2d& pos() const { return m_pos; } + inline double rotation() const { return m_rotation; } + inline const Vec2d bb_size() const { return m_bb_size; } + + void apply_wipe_tower() const; }; WipeTowerInfo get_wipe_tower_info() const; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3ae5c3005e..a6360a0205 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1262,6 +1262,56 @@ struct Plater::priv BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; + // Cache the wti info + class WipeTower: public GLCanvas3D::WipeTowerInfo { + Vec2d m_bed_origin = {0., 0.}; + int m_bed_idx = arrangement::UNARRANGED; + friend priv; + public: + + void apply_arrange_result(const arrangement::ArrangePolygon& ap, + const Vec2crd& bedc) { + m_bed_origin = unscaled(bedc); + m_pos = unscaled(ap.translation) + m_bed_origin; + m_rotation = ap.rotation; + m_bed_idx = ap.bed_idx; + apply_wipe_tower(); + } + + arrangement::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)}, + }); + + arrangement::ArrangePolygon ret; + ret.poly.contour = std::move(p); + ret.translation = scaled(m_pos) - scaled(m_bed_origin); + ret.rotation = m_rotation; + ret.bed_idx = m_bed_idx; + return ret; + } + + // For future use + int bed_index() const { return m_bed_idx; } + }; + +private: + WipeTower m_wipetower; + +public: + WipeTower& wipe_tower() { + auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); + m_wipetower.m_pos = wti.pos(); + m_wipetower.m_rotation = wti.rotation(); + m_wipetower.m_bb_size = wti.bb_size(); + return m_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 @@ -1410,40 +1460,20 @@ struct Plater::priv class ArrangeJob : public Job { + 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.; + static const constexpr int UNARRANGED = arrangement::UNARRANGED; - // Cache the wti info - GLCanvas3D::WipeTowerInfo m_wti; - - // Cache the selected instances needed to write back the arrange - // result. The order of instances is the same as the arrange polys - struct IndexedArrangePolys { - ModelInstancePtrs insts; - arrangement::ArrangePolygons polys; - - void reserve(size_t cap) { insts.reserve(cap); polys.reserve(cap); } - void clear() { insts.clear(); polys.clear(); } - - void emplace_back(ModelInstance *inst) { - insts.emplace_back(inst); - polys.emplace_back(inst->get_arrange_polygon()); - } - - void swap(IndexedArrangePolys &pp) { - insts.swap(pp.insts); polys.swap(pp.polys); - } - }; - - IndexedArrangePolys m_selected, m_unselected; + ArrangePolygons m_selected, m_unselected; protected: void prepare() override { - m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info(); - // Get the selection map Selection& sel = plater().get_selection(); const Selection::ObjectIdxsToInstanceIdxsMap &selmap = @@ -1458,59 +1488,57 @@ struct Plater::priv m_selected.reserve(count + 1 /* for optional wti */); m_unselected.reserve(count + 1 /* for optional wti */); - // Go through the objects and check if inside the selection - for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { - auto oit = selmap.find(int(oidx)); - - if (oit != selmap.end()) { // Object is selected - auto &iids = oit->second; - - // Go through instances and check if inside selection - size_t instcnt = model.objects[oidx]->instances.size(); - for (size_t iidx = 0; iidx < instcnt; ++iidx) { - auto instit = iids.find(iidx); - ModelInstance *oi = model.objects[oidx] - ->instances[iidx]; - - // Instance is selected - instit != iids.end() ? - m_selected.emplace_back(oi) : - m_unselected.emplace_back(oi); - } - } else // object not selected, all instances are unselected - for (ModelInstance *oi : model.objects[oidx]->instances) - m_unselected.emplace_back(oi); - } - - if (m_wti) - sel.is_wipe_tower() ? - m_selected.polys.emplace_back(m_wti.get_arrange_polygon()) : - m_unselected.polys.emplace_back(m_wti.get_arrange_polygon()); - - // If the selection is completely empty, consider all items as the - // selection - if (m_selected.insts.empty() && m_selected.polys.empty()) - m_selected.swap(m_unselected); - // Stride between logical beds double bedwidth = plater().bed_shape_bb().size().x(); coord_t stride = scaled((1. + LOGICAL_BED_GAP) * bedwidth); - for (arrangement::ArrangePolygon &ap : m_selected.polys) - if (ap.bed_idx > 0) ap.translation.x() -= ap.bed_idx * stride; + // Go through the objects and check if inside the selection + for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { + auto oit = selmap.find(int(oidx)); + ModelObject *mo = model.objects[oidx]; + + std::vector inst_sel(mo->instances.size(), false); + + if (oit != selmap.end()) + for (auto inst_id : oit->second) inst_sel[inst_id] = true; + + for (size_t i = 0; i < inst_sel.size(); ++i) { + ModelInstance *mi = mo->instances[i]; + ArrangePolygon ap = mi->get_arrange_polygon(); + ap.priority = 0; + ap.setter = [mi, stride](const ArrangePolygon &p) { + if (p.bed_idx != UNARRANGED) + mi->apply_arrange_result(p, {p.bed_idx * stride, 0}); + }; + + inst_sel[i] ? + m_selected.emplace_back(std::move(ap)) : + m_unselected.emplace_back(std::move(ap)); + } + } - for (arrangement::ArrangePolygon &ap : m_unselected.polys) - if (ap.bed_idx > 0) ap.translation.x() -= ap.bed_idx * stride; + auto& wti = plater().wipe_tower(); + if (wti) { + ArrangePolygon ap = wti.get_arrange_polygon(); + ap.setter = [&wti, stride](const ArrangePolygon &p) { + if (p.bed_idx != UNARRANGED) + wti.apply_arrange_result(p, {p.bed_idx * stride, 0}); + }; + + ap.priority = 1; + sel.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); } public: using Job::Job; - - int status_range() const override - { - return int(m_selected.polys.size()); - } + int status_range() const override { return int(m_selected.size()); } void process() override; @@ -1521,30 +1549,8 @@ struct Plater::priv return; } - // Stride between logical beds - double bedwidth = plater().bed_shape_bb().size().x(); - coord_t stride = scaled((1. + LOGICAL_BED_GAP) * bedwidth); - - for(size_t i = 0; i < m_selected.insts.size(); ++i) { - if (m_selected.polys[i].bed_idx != arrangement::UNARRANGED) { - Vec2crd offs = m_selected.polys[i].translation; - double rot = m_selected.polys[i].rotation; - int bdidx = m_selected.polys[i].bed_idx; - offs.x() += bdidx * stride; - m_selected.insts[i]->apply_arrange_result(offs, rot, bdidx); - } - } - - // Handle the wipe tower - if (m_wti && m_selected.polys.size() > m_selected.insts.size()) { - auto &wtipoly = m_selected.polys.back(); - if (wtipoly.bed_idx != arrangement::UNARRANGED) { - Vec2crd o = wtipoly.translation; - double r = wtipoly.rotation; - o.x() += wtipoly.bed_idx * stride; - m_wti.apply_arrange_result(o, r); - } - } + // Apply the arrange result to all selected objects + for (ArrangePolygon &ap : m_selected) ap.apply(); // Call original finalize (will update the scene) Job::finalize(); @@ -2531,7 +2537,8 @@ arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { return arrangement::BedShapeHint(bedpoly); } -void Plater::priv::find_new_position(const ModelInstancePtrs &instances, coord_t min_d) +void Plater::priv::find_new_position(const ModelInstancePtrs &instances, + coord_t min_d) { arrangement::ArrangePolygons movable, fixed; @@ -2546,15 +2553,14 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances, coord_t movable.emplace_back(std::move(arrpoly)); } - auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); - if (wti) fixed.emplace_back(wti.get_arrange_polygon()); + if (wipe_tower()) + fixed.emplace_back(m_wipetower.get_arrange_polygon()); arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint()); for (size_t i = 0; i < instances.size(); ++i) if (movable[i].bed_idx == 0) - instances[i]->apply_arrange_result(movable[i].translation, - movable[i].rotation); + instances[i]->apply_arrange_result(movable[i]); } void Plater::priv::ArrangeJob::process() { @@ -2567,16 +2573,15 @@ void Plater::priv::ArrangeJob::process() { dist = PrintConfig::min_object_distance(plater().config); } - coord_t min_obj_distance = scaled(dist); - auto count = unsigned(m_selected.polys.size()); + coord_t min_d = scaled(dist); + auto count = unsigned(m_selected.size()); arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); try { - arrangement::arrange(m_selected.polys, m_unselected.polys, - min_obj_distance, - bedshape, + arrangement::arrange(m_selected, m_unselected, min_d, bedshape, [this, count](unsigned st) { - if (st > 0) // will not finalize after last one + if (st > + 0) // will not finalize after last one update_status(count - st, arrangestr); }, [this]() { return was_canceled(); }); From 21624f53058b1cdb8afba0a9c6eb97c1ad997b15 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 17 Jul 2019 08:38:48 +0200 Subject: [PATCH 326/627] Framework to serialize gizmos into undo/redo stack Serialization into undo/redo of Cut gizmo Refactoring of GLGizmosManager --- src/slic3r/GUI/GLCanvas3D.cpp | 36 ++-- src/slic3r/GUI/GLCanvas3D.hpp | 3 + src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 6 + src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 1 - src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 + src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 211 +++++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 80 +++++--- src/slic3r/GUI/Plater.cpp | 25 +-- src/slic3r/Utils/UndoRedo.cpp | 50 +++-- src/slic3r/Utils/UndoRedo.hpp | 7 +- 11 files changed, 254 insertions(+), 168 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a401cabbaf..9e05f60ad1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1719,7 +1719,7 @@ void GLCanvas3D::deselect_all() m_selection.set_mode(Selection::Instance); wxGetApp().obj_manipul()->set_dirty(); m_gizmos.reset_all_states(); - m_gizmos.update_data(*this); + m_gizmos.update_data(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); } @@ -2082,8 +2082,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re m_selection.volumes_changed(map_glvolume_old_to_new); } - m_gizmos.update_data(*this); - m_gizmos.refresh_on_off_state(m_selection); + m_gizmos.update_data(); + m_gizmos.refresh_on_off_state(); // Update the toolbar if (update_object_list) @@ -2323,7 +2323,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) if ((keyCode == WXK_ESCAPE) && _deactivate_undo_redo_toolbar_items()) return; - if (m_gizmos.on_char(evt, *this)) + if (m_gizmos.on_char(evt)) return; //#ifdef __APPLE__ @@ -2450,7 +2450,7 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) } else { - if (!m_gizmos.on_key(evt, *this)) + if (!m_gizmos.on_key(evt)) { if (evt.GetEventType() == wxEVT_KEY_UP) { if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) { @@ -2560,7 +2560,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) } // Inform gizmos about the event so they have the opportunity to react. - if (m_gizmos.on_mouse_wheel(evt, *this)) + if (m_gizmos.on_mouse_wheel(evt)) return; // Calculate the zoom delta and apply it to the current zoom factor @@ -2688,7 +2688,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) return; } - if (m_gizmos.on_mouse(evt, *this)) + if (m_gizmos.on_mouse(evt)) { if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) mouse_up_cleanup(); @@ -2815,9 +2815,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_selection.is_empty()) m_gizmos.reset_all_states(); else - m_gizmos.refresh_on_off_state(m_selection); + m_gizmos.refresh_on_off_state(); - m_gizmos.update_data(*this); + m_gizmos.update_data(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); m_dirty = true; } @@ -2985,9 +2985,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) { // forces the selection of the volume m_selection.add(volume_idx); - m_gizmos.refresh_on_off_state(m_selection); + m_gizmos.refresh_on_off_state(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); - m_gizmos.update_data(*this); + m_gizmos.update_data(); wxGetApp().obj_manipul()->set_dirty(); // forces a frame render to update the view before the context menu is shown render(); @@ -3355,8 +3355,8 @@ void GLCanvas3D::set_camera_zoom(double zoom) void GLCanvas3D::update_gizmos_on_off_state() { set_as_dirty(); - m_gizmos.update_data(*this); - m_gizmos.refresh_on_off_state(get_selection()); + m_gizmos.update_data(); + m_gizmos.refresh_on_off_state(); } void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on) @@ -3762,7 +3762,7 @@ void GLCanvas3D::_picking_pass() const if (m_camera_clipping_plane.is_active()) ::glDisable(GL_CLIP_PLANE0); - m_gizmos.render_current_gizmo_for_picking_pass(m_selection); + m_gizmos.render_current_gizmo_for_picking_pass(); if (m_multisample_allowed) glsafe(::glEnable(GL_MULTISAMPLE)); @@ -4072,7 +4072,7 @@ void GLCanvas3D::_render_volumes_for_picking() const void GLCanvas3D::_render_current_gizmo() const { - m_gizmos.render_current_gizmo(m_selection); + m_gizmos.render_current_gizmo(); } void GLCanvas3D::_render_gizmos_overlay() const @@ -4088,7 +4088,7 @@ void GLCanvas3D::_render_gizmos_overlay() const m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment #endif /* __WXMSW__ */ - m_gizmos.render_overlay(*this, m_selection); + m_gizmos.render_overlay(); } void GLCanvas3D::_render_toolbar() const @@ -5661,9 +5661,9 @@ void GLCanvas3D::_update_selection_from_hover() if (m_selection.is_empty()) m_gizmos.reset_all_states(); else - m_gizmos.refresh_on_off_state(m_selection); + m_gizmos.refresh_on_off_state(); - m_gizmos.update_data(*this); + m_gizmos.update_data(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); m_dirty = true; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index f5a53c6a56..6bb17da4a9 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -517,6 +517,9 @@ public: const Selection& get_selection() const { return m_selection; } Selection& get_selection() { return m_selection; } + const GLGizmosManager& get_gizmos_manager() const { return m_gizmos; } + GLGizmosManager& get_gizmos_manager() { return m_gizmos; } + void bed_shape_changed(); void set_clipping_plane(unsigned int id, const ClippingPlane& plane) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 7da3dec8aa..ba0ea48252 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -132,6 +132,7 @@ void GLGizmoBase::Grabber::render_face(float half_size) const glsafe(::glEnd()); } + GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : m_parent(parent) , m_group_id(-1) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 73c73a2c96..88927aee23 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/Selection.hpp" +#include class wxWindow; class GLUquadric; @@ -105,6 +106,9 @@ public: bool init() { return on_init(); } + void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); } + void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); } + std::string get_name() const { return on_get_name(); } int get_group_id() const { return m_group_id; } @@ -144,6 +148,8 @@ public: protected: virtual bool on_init() = 0; + virtual void on_load(cereal::BinaryInputArchive& ar) {} + virtual void on_save(cereal::BinaryOutputArchive& ar) const {} virtual std::string on_get_name() const = 0; virtual void on_set_state() {} virtual void on_set_hover_id() {} diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index b1c2950983..52174d2d6f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -28,7 +28,6 @@ GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, uns , m_rotate_lower(false) {} - bool GLGizmoCut::on_init() { m_grabbers.emplace_back(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 79a7c58e27..628bcf508e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -27,6 +27,8 @@ public: protected: virtual bool on_init(); + virtual void on_load(cereal::BinaryInputArchive& ar) { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } + virtual void on_save(cereal::BinaryOutputArchive& ar) const { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } virtual std::string on_get_name() const; virtual void on_set_state(); virtual bool on_is_activable(const Selection& selection) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 602235d4a0..5922f96004 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -15,7 +15,8 @@ namespace GUI { const float GLGizmosManager::Default_Icons_Size = 64; GLGizmosManager::GLGizmosManager() - : m_enabled(false) + : m_parent(nullptr) + , m_enabled(false) , m_icons_texture_dirty(true) , m_current(Undefined) , m_overlay_icons_size(Default_Icons_Size) @@ -23,6 +24,7 @@ GLGizmosManager::GLGizmosManager() , m_overlay_border(5.0f) , m_overlay_gap_y(5.0f) , m_tooltip("") + , m_serializing(false) { } @@ -33,6 +35,8 @@ GLGizmosManager::~GLGizmosManager() bool GLGizmosManager::init(GLCanvas3D& parent) { + m_parent = &parent; + m_background_texture.metadata.filename = "toolbar_background.png"; m_background_texture.metadata.left = 16; m_background_texture.metadata.top = 16; @@ -135,12 +139,18 @@ void GLGizmosManager::set_overlay_scale(float scale) } } -void GLGizmosManager::refresh_on_off_state(const Selection& selection) +void GLGizmosManager::refresh_on_off_state() { + if (m_parent == nullptr) + return; + + if (m_serializing) + return; + GizmosMap::iterator it = m_gizmos.find(m_current); if ((it != m_gizmos.end()) && (it->second != nullptr)) { - if (!it->second->is_activable(selection)) + if (!it->second->is_activable(m_parent->get_selection())) { it->second->set_state(GLGizmoBase::Off); m_current = Undefined; @@ -153,6 +163,9 @@ void GLGizmosManager::reset_all_states() if (!m_enabled) return; + if (m_serializing) + return; + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if (it->second != nullptr) @@ -192,22 +205,22 @@ void GLGizmosManager::enable_grabber(EType type, unsigned int id, bool enable) } } -void GLGizmosManager::update(const Linef3& mouse_ray, const Selection& selection, const Point* mouse_pos) +void GLGizmosManager::update(const Linef3& mouse_ray, const Point* mouse_pos) { - if (!m_enabled) + if (!m_enabled || (m_parent == nullptr)) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos), selection); + curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos), m_parent->get_selection()); } -void GLGizmosManager::update_data(GLCanvas3D& canvas) +void GLGizmosManager::update_data() { - if (!m_enabled) + if (!m_enabled || (m_parent == nullptr)) return; - const Selection& selection = canvas.get_selection(); + const Selection& selection = m_parent->get_selection(); bool is_wipe_tower = selection.is_wipe_tower(); enable_grabber(Move, 2, !is_wipe_tower); @@ -228,7 +241,7 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) set_rotation(Vec3d::Zero()); ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()]; set_flattening_data(model_object); - set_sla_support_data(model_object, selection); + set_sla_support_data(model_object); } else if (selection.is_single_volume() || selection.is_single_modifier()) { @@ -236,7 +249,7 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) set_scale(volume->get_volume_scaling_factor()); set_rotation(Vec3d::Zero()); set_flattening_data(nullptr); - set_sla_support_data(nullptr, selection); + set_sla_support_data(nullptr); } else if (is_wipe_tower) { @@ -244,14 +257,14 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) set_scale(Vec3d::Ones()); set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast(config.option("wipe_tower_rotation_angle"))->value)); set_flattening_data(nullptr); - set_sla_support_data(nullptr, selection); + set_sla_support_data(nullptr); } else { set_scale(Vec3d::Ones()); set_rotation(Vec3d::Zero()); set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); - set_sla_support_data(nullptr, selection); + set_sla_support_data(nullptr); } } @@ -264,9 +277,13 @@ bool GLGizmosManager::is_running() const return (curr != nullptr) ? (curr->get_state() == GLGizmoBase::On) : false; } -bool GLGizmosManager::handle_shortcut(int key, const Selection& selection) +bool GLGizmosManager::handle_shortcut(int key) { - if (!m_enabled || selection.is_empty()) + if (!m_enabled || (m_parent == nullptr)) + return false; + + const Selection& selection = m_parent->get_selection(); + if (selection.is_empty()) return false; EType old_current = m_current; @@ -314,14 +331,14 @@ bool GLGizmosManager::is_dragging() const return (curr != nullptr) ? curr->is_dragging() : false; } -void GLGizmosManager::start_dragging(const Selection& selection) +void GLGizmosManager::start_dragging() { - if (!m_enabled) + if (!m_enabled || (m_parent == nullptr)) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->start_dragging(selection); + curr->start_dragging(m_parent->get_selection()); } void GLGizmosManager::stop_dragging() @@ -409,14 +426,14 @@ void GLGizmosManager::set_flattening_data(const ModelObject* model_object) reinterpret_cast(it->second)->set_flattening_data(model_object); } -void GLGizmosManager::set_sla_support_data(ModelObject* model_object, const Selection& selection) +void GLGizmosManager::set_sla_support_data(ModelObject* model_object) { - if (!m_enabled) + if (!m_enabled || (m_parent == nullptr)) return; GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); if (it != m_gizmos.end()) - reinterpret_cast(it->second)->set_sla_support_data(model_object, selection); + reinterpret_cast(it->second)->set_sla_support_data(model_object, m_parent->get_selection()); } // Returns true if the gizmo used the event to do something, false otherwise. @@ -445,39 +462,42 @@ ClippingPlane GLGizmosManager::get_sla_clipping_plane() const } -void GLGizmosManager::render_current_gizmo(const Selection& selection) const +void GLGizmosManager::render_current_gizmo() const { - if (!m_enabled) + if (!m_enabled || (m_parent == nullptr)) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->render(selection); + curr->render(m_parent->get_selection()); } -void GLGizmosManager::render_current_gizmo_for_picking_pass(const Selection& selection) const +void GLGizmosManager::render_current_gizmo_for_picking_pass() const { - if (!m_enabled) + if (!m_enabled || (m_parent == nullptr)) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->render_for_picking(selection); + curr->render_for_picking(m_parent->get_selection()); } -void GLGizmosManager::render_overlay(const GLCanvas3D& canvas, const Selection& selection) const +void GLGizmosManager::render_overlay() const { - if (!m_enabled) + if (!m_enabled || (m_parent == nullptr)) return; if (m_icons_texture_dirty) generate_icons_texture(); - do_render_overlay(canvas, selection); + do_render_overlay(); } -bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas) +bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt) { + if (m_parent == nullptr) + return false; + bool processed = false; if (m_current == SlaSupports) { @@ -489,14 +509,15 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas) return processed; } - - -bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) +bool GLGizmosManager::on_mouse(wxMouseEvent& evt) { + if (m_parent == nullptr) + return false; + Point pos(evt.GetX(), evt.GetY()); Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY()); - Selection& selection = canvas.get_selection(); + Selection& selection = m_parent->get_selection(); int selected_object_idx = selection.get_object_idx(); bool processed = false; @@ -512,7 +533,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) // mouse anywhere if (evt.Moving()) - m_tooltip = update_hover_state(canvas, mouse_pos); + m_tooltip = update_hover_state(mouse_pos); else if (evt.LeftUp()) m_mouse_capture.left = false; else if (evt.MiddleUp()) @@ -523,7 +544,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) // if the button down was done on this toolbar, prevent from dragging into the scene processed = true; - if (!overlay_contains_mouse(canvas, mouse_pos)) + if (!overlay_contains_mouse(mouse_pos)) { // mouse is outside the toolbar m_tooltip = ""; @@ -535,40 +556,40 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) processed = true; else if (!selection.is_empty() && grabber_contains_mouse()) { - update_data(canvas); + update_data(); selection.start_dragging(); - start_dragging(selection); + start_dragging(); if (m_current == Flatten) { // Rotate the object so the normal points downward: - canvas.do_flatten(get_flattening_normal(), "Place on Face"); + m_parent->do_flatten(get_flattening_normal(), "Gizmo - Place on Face"); wxGetApp().obj_manipul()->set_dirty(); } - canvas.set_as_dirty(); + m_parent->set_as_dirty(); processed = true; } } else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::RightDown)) // event was taken care of by the SlaSupports gizmo processed = true; - else if (evt.Dragging() && (canvas.get_move_volume_id() != -1) && (m_current == SlaSupports)) + else if (evt.Dragging() && (m_parent->get_move_volume_id() != -1) && (m_current == SlaSupports)) // don't allow dragging objects with the Sla gizmo on processed = true; else if (evt.Dragging() && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) { // the gizmo got the event and took some action, no need to do anything more here - canvas.set_as_dirty(); + m_parent->set_as_dirty(); processed = true; } else if (evt.Dragging() && is_dragging()) { - if (!canvas.get_wxglcanvas()->HasCapture()) - canvas.get_wxglcanvas()->CaptureMouse(); + if (!m_parent->get_wxglcanvas()->HasCapture()) + m_parent->get_wxglcanvas()->CaptureMouse(); - canvas.set_mouse_as_dragging(); - update(canvas.mouse_ray(pos), selection, &pos); + m_parent->set_mouse_as_dragging(); + update(m_parent->mouse_ray(pos), &pos); switch (m_current) { @@ -605,7 +626,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) break; } - canvas.set_as_dirty(); + m_parent->set_as_dirty(); processed = true; } else if (evt.LeftUp() && is_dragging()) @@ -614,18 +635,18 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) { case Move: { - canvas.disable_regenerate_volumes(); - canvas.do_move("Gizmo-Move Object"); + m_parent->disable_regenerate_volumes(); + m_parent->do_move("Gizmo-Move Object"); break; } case Scale: { - canvas.do_scale("Gizmo-Scale Object"); + m_parent->do_scale("Gizmo-Scale Object"); break; } case Rotate: { - canvas.do_rotate("Gizmo-Rotate Object"); + m_parent->do_rotate("Gizmo-Rotate Object"); break; } default: @@ -633,25 +654,25 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) } stop_dragging(); - update_data(canvas); + update_data(); wxGetApp().obj_manipul()->set_dirty(); // Let the platter know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. - canvas.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + m_parent->post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); // updates camera target constraints - canvas.refresh_camera_scene_box(); + m_parent->refresh_camera_scene_box(); processed = true; } - else if (evt.LeftUp() && (m_current == SlaSupports) && !canvas.is_mouse_dragging()) + else if (evt.LeftUp() && (m_current == SlaSupports) && !m_parent->is_mouse_dragging()) { // in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither // object moving or selecting is suppressed in that case gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()); processed = true; } - else if (evt.LeftUp() && (m_current == Flatten) && ((canvas.get_first_hover_volume_idx() != -1) || grabber_contains_mouse())) + else if (evt.LeftUp() && (m_current == Flatten) && ((m_parent->get_first_hover_volume_idx() != -1) || grabber_contains_mouse())) { // to avoid to loose the selection when user clicks an object while the Flatten gizmo is active processed = true; @@ -663,24 +684,24 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) if (evt.LeftDown() || evt.LeftDClick()) { m_mouse_capture.left = true; - m_mouse_capture.parent = &canvas; + m_mouse_capture.parent = m_parent; processed = true; if (!selection.is_empty()) { - update_on_off_state(canvas, mouse_pos, selection); - update_data(canvas); - canvas.set_as_dirty(); + update_on_off_state(mouse_pos); + update_data(); + m_parent->set_as_dirty(); } } else if (evt.MiddleDown()) { m_mouse_capture.middle = true; - m_mouse_capture.parent = &canvas; + m_mouse_capture.parent = m_parent; } else if (evt.RightDown()) { m_mouse_capture.right = true; - m_mouse_capture.parent = &canvas; + m_mouse_capture.parent = m_parent; } else if (evt.LeftUp()) processed = true; @@ -689,8 +710,11 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas) return processed; } -bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas) +bool GLGizmosManager::on_char(wxKeyEvent& evt) { + if (m_parent == nullptr) + return false; + // see include/wx/defs.h enum wxKeyCode int keyCode = evt.GetKeyCode(); int ctrlMask = wxMOD_CONTROL; @@ -797,21 +821,24 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas) if (!processed && !evt.HasModifiers()) { - if (handle_shortcut(keyCode, canvas.get_selection())) + if (handle_shortcut(keyCode)) { - update_data(canvas); + update_data(); processed = true; } } if (processed) - canvas.set_as_dirty(); + m_parent->set_as_dirty(); return processed; } -bool GLGizmosManager::on_key(wxKeyEvent& evt, GLCanvas3D& canvas) +bool GLGizmosManager::on_key(wxKeyEvent& evt) { + if (m_parent == nullptr) + return false; + const int keyCode = evt.GetKeyCode(); bool processed = false; @@ -836,23 +863,29 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt, GLCanvas3D& canvas) } // if (processed) -// canvas.set_cursor(GLCanvas3D::Standard); +// m_parent->set_cursor(GLCanvas3D::Standard); } else if (evt.GetEventType() == wxEVT_KEY_DOWN) { if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)) && reinterpret_cast(get_current())->is_in_editing_mode()) { -// canvas.set_cursor(GLCanvas3D::Cross); +// m_parent->set_cursor(GLCanvas3D::Cross); processed = true; } } if (processed) - canvas.set_as_dirty(); + m_parent->set_as_dirty(); return processed; } +void GLGizmosManager::update_after_undo_redo() +{ + update_data(); + m_serializing = false; +} + void GLGizmosManager::reset() { for (GizmosMap::value_type& gizmo : m_gizmos) @@ -864,14 +897,16 @@ void GLGizmosManager::reset() m_gizmos.clear(); } -void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selection& selection) const +void GLGizmosManager::do_render_overlay() const { - if (m_gizmos.empty()) + if ((m_parent == nullptr) || m_gizmos.empty()) return; - float cnv_w = (float)canvas.get_canvas_size().get_width(); - float cnv_h = (float)canvas.get_canvas_size().get_height(); - float zoom = (float)canvas.get_camera().get_zoom(); + const Selection& selection = m_parent->get_selection(); + + float cnv_w = (float)m_parent->get_canvas_size().get_width(); + float cnv_h = (float)m_parent->get_canvas_size().get_height(); + float zoom = (float)m_parent->get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float height = get_total_overlay_height(); @@ -984,7 +1019,7 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + scaled_icons_size, top_y - scaled_icons_size, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); if (it->second->get_state() == GLGizmoBase::On) { - float toolbar_top = (float)cnv_h - canvas.get_view_toolbar_height(); + float toolbar_top = (float)cnv_h - m_parent->get_view_toolbar_height(); it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); } top_y -= scaled_stride_y; @@ -1047,12 +1082,14 @@ bool GLGizmosManager::generate_icons_texture() const return res; } -void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection) +void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos) { - if (!m_enabled) + if (!m_enabled || (m_parent == nullptr)) return; - float cnv_h = (float)canvas.get_canvas_size().get_height(); + const Selection& selection = m_parent->get_selection(); + + float cnv_h = (float)m_parent->get_canvas_size().get_height(); float height = get_total_overlay_height(); float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; @@ -1091,16 +1128,16 @@ void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& it->second->set_state(GLGizmoBase::On); } -std::string GLGizmosManager::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos) +std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos) { std::string name = ""; - if (!m_enabled) + if (!m_enabled || (m_parent == nullptr)) return name; - const Selection& selection = canvas.get_selection(); + const Selection& selection = m_parent->get_selection(); - float cnv_h = (float)canvas.get_canvas_size().get_height(); + float cnv_h = (float)m_parent->get_canvas_size().get_height(); float height = get_total_overlay_height(); float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; @@ -1126,12 +1163,12 @@ std::string GLGizmosManager::update_hover_state(const GLCanvas3D& canvas, const return name; } -bool GLGizmosManager::overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const +bool GLGizmosManager::overlay_contains_mouse(const Vec2d& mouse_pos) const { - if (!m_enabled) + if (!m_enabled || (m_parent == nullptr)) return false; - float cnv_h = (float)canvas.get_canvas_size().get_height(); + float cnv_h = (float)m_parent->get_canvas_size().get_height(); float height = get_total_overlay_height(); float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 87be7da41e..db73345aea 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -4,14 +4,13 @@ #include "slic3r/GUI/GLTexture.hpp" #include "slic3r/GUI/GLToolbar.hpp" #include "slic3r/GUI/Gizmos/GLGizmos.hpp" +#include "libslic3r/ObjectID.hpp" #include namespace Slic3r { namespace GUI { -class Selection; -class GLGizmoBase; class GLCanvas3D; class ClippingPlane; @@ -43,7 +42,7 @@ public: float get_height() const { return m_top - m_bottom; } }; -class GLGizmosManager +class GLGizmosManager : public Slic3r::ObjectBase { public: static const float Default_Icons_Size; @@ -61,6 +60,7 @@ public: }; private: + GLCanvas3D* m_parent; bool m_enabled; typedef std::map GizmosMap; GizmosMap m_gizmos; @@ -89,6 +89,7 @@ private: MouseCapture m_mouse_capture; std::string m_tooltip; + bool m_serializing; public: GLGizmosManager(); @@ -96,29 +97,59 @@ public: bool init(GLCanvas3D& parent); + template + void load(Archive& ar) + { + if (!m_enabled) + return; + + m_serializing = true; + + ar(m_current); + + GLGizmoBase* curr = get_current(); + if (curr != nullptr) + { + curr->set_state(GLGizmoBase::On); + curr->load(ar); + } + } + + template + void save(Archive& ar) const + { + if (!m_enabled) + return; + + ar(m_current); + + GLGizmoBase* curr = get_current(); + if (curr != nullptr) + curr->save(ar); + } + bool is_enabled() const { return m_enabled; } void set_enabled(bool enable) { m_enabled = enable; } void set_overlay_icon_size(float size); void set_overlay_scale(float scale); - void refresh_on_off_state(const Selection& selection); + void refresh_on_off_state(); void reset_all_states(); void set_hover_id(int id); void enable_grabber(EType type, unsigned int id, bool enable); - void update(const Linef3& mouse_ray, const Selection& selection, const Point* mouse_pos = nullptr); - void update_data(GLCanvas3D& canvas); + void update(const Linef3& mouse_ray, const Point* mouse_pos = nullptr); + void update_data(); - Rect get_reset_rect_viewport(const GLCanvas3D& canvas) const; EType get_current_type() const { return m_current; } bool is_running() const; - bool handle_shortcut(int key, const Selection& selection); + bool handle_shortcut(int key); bool is_dragging() const; - void start_dragging(const Selection& selection); + void start_dragging(); void stop_dragging(); Vec3d get_displacement() const; @@ -135,26 +166,28 @@ public: void set_flattening_data(const ModelObject* model_object); - void set_sla_support_data(ModelObject* model_object, const Selection& selection); + void set_sla_support_data(ModelObject* model_object); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false); ClippingPlane get_sla_clipping_plane() const; - void render_current_gizmo(const Selection& selection) const; - void render_current_gizmo_for_picking_pass(const Selection& selection) const; + void render_current_gizmo() const; + void render_current_gizmo_for_picking_pass() const; - void render_overlay(const GLCanvas3D& canvas, const Selection& selection) const; + void render_overlay() const; const std::string& get_tooltip() const { return m_tooltip; } - bool on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas); - bool on_mouse_wheel(wxMouseEvent& evt, GLCanvas3D& canvas); - bool on_char(wxKeyEvent& evt, GLCanvas3D& canvas); - bool on_key(wxKeyEvent& evt, GLCanvas3D& canvas); + bool on_mouse(wxMouseEvent& evt); + bool on_mouse_wheel(wxMouseEvent& evt); + bool on_char(wxKeyEvent& evt); + bool on_key(wxKeyEvent& evt); + + void update_after_undo_redo(); private: void reset(); - void do_render_overlay(const GLCanvas3D& canvas, const Selection& selection) const; + void do_render_overlay() const; float get_total_overlay_height() const; float get_total_overlay_width() const; @@ -163,13 +196,18 @@ private: bool generate_icons_texture() const; - void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection); - std::string update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos); - bool overlay_contains_mouse(const GLCanvas3D& canvas, const Vec2d& mouse_pos) const; + void update_on_off_state(const Vec2d& mouse_pos); + std::string update_hover_state(const Vec2d& mouse_pos); + bool overlay_contains_mouse(const Vec2d& mouse_pos) const; bool grabber_contains_mouse() const; }; } // namespace GUI } // namespace Slic3r +namespace cereal +{ + template struct specialize {}; +} + #endif // slic3r_GUI_GLGizmosManager_hpp_ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f5f2454717..c5a4cceadf 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1633,8 +1633,8 @@ struct Plater::priv if (this->m_prevent_snapshots > 0) return; assert(this->m_prevent_snapshots >= 0); - this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection()); - } + this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager()); + } void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } int get_active_snapshot_index(); void undo(); @@ -3611,34 +3611,35 @@ int Plater::priv::get_active_snapshot_index() void Plater::priv::undo() { - if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection())) - this->update_after_undo_redo(); + if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager())) + this->update_after_undo_redo(); } void Plater::priv::redo() { - if (this->undo_redo_stack.redo(model)) - this->update_after_undo_redo(); + if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager())) + this->update_after_undo_redo(); } void Plater::priv::undo_to(size_t time_to_load) { - if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), time_to_load)) - this->update_after_undo_redo(); + if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load)) + this->update_after_undo_redo(); } void Plater::priv::redo_to(size_t time_to_load) { - if (this->undo_redo_stack.redo(model, time_to_load)) - this->update_after_undo_redo(); + if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load)) + this->update_after_undo_redo(); } void Plater::priv::update_after_undo_redo() { - this->view3D->get_canvas3d()->get_selection().clear(); + this->view3D->get_canvas3d()->get_selection().clear(); this->update(false); // update volumes from the deserializd model //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time) this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances); + this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo(); wxGetApp().obj_list()->update_after_undo_redo(); @@ -3884,7 +3885,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe return; } - this->take_snapshot(_(L("Cut"))); + this->take_snapshot(_(L("Gizmo - Cut"))); wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 058062502d..69d60603eb 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -17,8 +17,6 @@ #define CEREAL_FUTURE_EXPERIMENTAL #include -#include - #include #ifndef NDEBUG @@ -358,18 +356,14 @@ public: // Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning. StackImpl() : m_active_snapshot_time(0), m_current_time(0) {} - // The Undo / Redo stack is being initialized with an empty model and an empty selection. - // The first snapshot cannot be removed. - void initialize(const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); - - // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. - void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); - void load_snapshot(size_t timestamp, Slic3r::Model &model); + // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. + void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos); + void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos); bool has_undo_snapshot() const; bool has_redo_snapshot() const; - bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t jump_to_time); - bool redo(Slic3r::Model &model, size_t jump_to_time); + bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t jump_to_time); + bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t jump_to_time); // Snapshot history (names with timestamps). const std::vector& snapshots() const { return m_snapshots; } @@ -551,6 +545,7 @@ namespace cereal #include #include #include +#include namespace Slic3r { namespace UndoRedo { @@ -632,7 +627,7 @@ template void StackImpl::load_mutable_object(const Sl } // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. -void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) +void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos) { // Release old snapshot data. assert(m_active_snapshot_time <= m_current_time); @@ -650,7 +645,8 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo for (unsigned int volume_idx : selection.get_volume_idxs()) m_selection.volumes_and_instances.emplace_back(selection.get_volume(volume_idx)->geometry_id); this->save_mutable_object(m_selection); - // Save the snapshot info. + this->save_mutable_object(gizmos); + // Save the snapshot info. m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id); m_active_snapshot_time = m_current_time; // Save snapshot info of the last "current" aka "top most" state, that is only being serialized @@ -665,7 +661,7 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo #endif /* SLIC3R_UNDOREDO_DEBUG */ } -void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model) +void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos) { // Find the snapshot by time. It must exist. const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp)); @@ -679,7 +675,9 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model &model) model.update_links_bottom_up_recursive(); m_selection.volumes_and_instances.clear(); this->load_mutable_object(m_selection.id(), m_selection); - // Sort the volumes so that we may use binary search. + gizmos.reset_all_states(); + this->load_mutable_object(gizmos.id(), gizmos); + // Sort the volumes so that we may use binary search. std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end()); this->m_active_snapshot_time = timestamp; assert(this->valid()); @@ -699,8 +697,8 @@ bool StackImpl::has_redo_snapshot() const return ++ it != m_snapshots.end(); } -bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load) -{ +bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) +{ assert(this->valid()); if (time_to_load == SIZE_MAX) { auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); @@ -712,8 +710,8 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { // The current state is temporary. The current state needs to be captured to be redoable. - this->take_snapshot(topmost_snapsnot_name, model, selection); - // The line above entered another topmost_snapshot_name. + this->take_snapshot(topmost_snapsnot_name, model, selection, gizmos); + // The line above entered another topmost_snapshot_name. assert(m_snapshots.back().is_topmost()); assert(! m_snapshots.back().is_topmost_captured()); // Pop it back, it is not needed as there is now a captured topmost state. @@ -723,7 +721,7 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti assert(m_snapshots.back().is_topmost()); assert(m_snapshots.back().is_topmost_captured()); } - this->load_snapshot(time_to_load, model); + this->load_snapshot(time_to_load, model, gizmos); #ifdef SLIC3R_UNDOREDO_DEBUG std::cout << "After undo" << std::endl; this->print(); @@ -731,8 +729,8 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti return true; } -bool StackImpl::redo(Slic3r::Model &model, size_t time_to_load) -{ +bool StackImpl::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) +{ assert(this->valid()); if (time_to_load == SIZE_MAX) { auto it_current = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); @@ -742,7 +740,7 @@ bool StackImpl::redo(Slic3r::Model &model, size_t time_to_load) } assert(time_to_load > m_active_snapshot_time); assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); - this->load_snapshot(time_to_load, model); + this->load_snapshot(time_to_load, model, gizmos); #ifdef SLIC3R_UNDOREDO_DEBUG std::cout << "After redo" << std::endl; this->print(); @@ -767,11 +765,11 @@ void StackImpl::collect_garbage() // Wrappers of the private implementation. Stack::Stack() : pimpl(new StackImpl()) {} Stack::~Stack() {} -void Stack::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->take_snapshot(snapshot_name, model, selection); } +void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos) { pimpl->take_snapshot(snapshot_name, model, selection, gizmos); } bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); } bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); } -bool Stack::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load) { return pimpl->undo(model, selection, time_to_load); } -bool Stack::redo(Slic3r::Model &model, size_t time_to_load) { return pimpl->redo(model, time_to_load); } +bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->undo(model, selection, gizmos, time_to_load); } +bool Stack::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->redo(model, gizmos, time_to_load); } const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); } const std::vector& Stack::snapshots() const { return pimpl->snapshots(); } diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index f8bfda08cf..236970b681 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -15,6 +15,7 @@ class Model; namespace GUI { class Selection; + class GLGizmosManager; } // namespace GUI namespace UndoRedo { @@ -56,7 +57,7 @@ public: ~Stack(); // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. - void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); + void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos); // To be queried to enable / disable the Undo / Redo buttons at the UI. bool has_undo_snapshot() const; @@ -64,10 +65,10 @@ public: // Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated. // Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible. - bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load = SIZE_MAX); + bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX); // Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated. - bool redo(Slic3r::Model &model, size_t time_to_load = SIZE_MAX); + bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX); // Snapshot history (names with timestamps). // Each snapshot indicates start of an interval in which this operation is performed. From 0a530ab7bc3913d3b1d3333c110d0dafd18393de Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 17 Jul 2019 10:03:00 +0200 Subject: [PATCH 327/627] Added undo/redo snapshot for layers height editing --- src/slic3r/GUI/GLCanvas3D.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9e05f60ad1..9bb9c21585 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -604,6 +604,7 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) { if (last_object_id >= 0) { if (m_layer_height_profile_modified) { + wxGetApp().plater()->take_snapshot(_(L("Layers heights"))); const_cast(m_model_object)->layer_height_profile = m_layer_height_profile; canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } From da1fa0b6e339163836cc7a8165c0a05b58c5f0d3 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 17 Jul 2019 12:06:23 +0200 Subject: [PATCH 328/627] Refactoring of GLGizmosXX classes to cleanup their interface --- src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 8 ++--- src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 30 ++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 24 ++++++++------ src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 12 +++---- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 17 ++++++---- src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp | 9 +++--- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 22 +++++++------ src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 12 ++++--- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 34 +++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 26 +++++++-------- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 30 ++++++++++------- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 14 ++++---- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 23 +++++++------ src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 12 +++---- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 31 +++++++----------- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 2 +- 16 files changed, 165 insertions(+), 141 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index ba0ea48252..4b975e87e9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -180,7 +180,7 @@ void GLGizmoBase::disable_grabber(unsigned int id) on_disable_grabber(id); } -void GLGizmoBase::start_dragging(const Selection& selection) +void GLGizmoBase::start_dragging() { m_dragging = true; @@ -189,7 +189,7 @@ void GLGizmoBase::start_dragging(const Selection& selection) m_grabbers[i].dragging = (m_hover_id == i); } - on_start_dragging(selection); + on_start_dragging(); } void GLGizmoBase::stop_dragging() @@ -204,10 +204,10 @@ void GLGizmoBase::stop_dragging() on_stop_dragging(); } -void GLGizmoBase::update(const UpdateData& data, const Selection& selection) +void GLGizmoBase::update(const UpdateData& data) { if (m_hover_id != -1) - on_update(data, selection); + on_update(data); } std::array GLGizmoBase::picking_color_component(unsigned int id) const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 88927aee23..b84442b94d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -76,10 +76,10 @@ public: struct UpdateData { - const Linef3 mouse_ray; - const Point* mouse_pos; + const Linef3& mouse_ray; + const Point& mouse_pos; - UpdateData(const Linef3& mouse_ray, const Point* mouse_pos = nullptr) + UpdateData(const Linef3& mouse_ray, const Point& mouse_pos) : mouse_ray(mouse_ray), mouse_pos(mouse_pos) {} }; @@ -122,7 +122,7 @@ public: const std::string& get_icon_filename() const { return m_icon_filename; } - bool is_activable(const Selection& selection) const { return on_is_activable(selection); } + bool is_activable() const { return on_is_activable(); } bool is_selectable() const { return on_is_selectable(); } unsigned int get_sprite_id() const { return m_sprite_id; } @@ -135,16 +135,16 @@ public: void enable_grabber(unsigned int id); void disable_grabber(unsigned int id); - void start_dragging(const Selection& selection); + void start_dragging(); void stop_dragging(); bool is_dragging() const { return m_dragging; } - void update(const UpdateData& data, const Selection& selection); + void update(const UpdateData& data); - void render(const Selection& selection) const { on_render(selection); } - void render_for_picking(const Selection& selection) const { on_render_for_picking(selection); } - void render_input_window(float x, float y, float bottom_limit, const Selection& selection) { on_render_input_window(x, y, bottom_limit, selection); } + void render() const { on_render(); } + void render_for_picking() const { on_render_for_picking(); } + void render_input_window(float x, float y, float bottom_limit) { on_render_input_window(x, y, bottom_limit); } protected: virtual bool on_init() = 0; @@ -153,16 +153,16 @@ protected: virtual std::string on_get_name() const = 0; virtual void on_set_state() {} virtual void on_set_hover_id() {} - virtual bool on_is_activable(const Selection& selection) const { return true; } + virtual bool on_is_activable() const { return true; } virtual bool on_is_selectable() const { return true; } virtual void on_enable_grabber(unsigned int id) {} virtual void on_disable_grabber(unsigned int id) {} - virtual void on_start_dragging(const Selection& selection) {} + virtual void on_start_dragging() {} virtual void on_stop_dragging() {} - virtual void on_update(const UpdateData& data, const Selection& selection) = 0; - virtual void on_render(const Selection& selection) const = 0; - virtual void on_render_for_picking(const Selection& selection) const = 0; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) {} + virtual void on_update(const UpdateData& data) {} + virtual void on_render() const = 0; + virtual void on_render_for_picking() const = 0; + virtual void on_render_input_window(float x, float y, float bottom_limit) {} // Returns the picking color for the given id, based on the BASE_ID constant // No check is made for clashing with other picking color (i.e. GLVolumes) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 52174d2d6f..39399fc0d3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -48,15 +48,18 @@ void GLGizmoCut::on_set_state() } } -bool GLGizmoCut::on_is_activable(const Selection& selection) const +bool GLGizmoCut::on_is_activable() const { + const Selection& selection = m_parent.get_selection(); return selection.is_single_full_instance() && !selection.is_wipe_tower(); } -void GLGizmoCut::on_start_dragging(const Selection& selection) +void GLGizmoCut::on_start_dragging() { - if (m_hover_id == -1) { return; } + if (m_hover_id == -1) + return; + const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); m_start_z = m_cut_z; update_max_z(selection); @@ -65,19 +68,21 @@ void GLGizmoCut::on_start_dragging(const Selection& selection) m_drag_center(2) = m_cut_z; } -void GLGizmoCut::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoCut::on_update(const UpdateData& data) { if (m_hover_id != -1) { set_cut_z(m_start_z + calc_projection(data.mouse_ray)); } } -void GLGizmoCut::on_render(const Selection& selection) const +void GLGizmoCut::on_render() const { if (m_grabbers[0].dragging) { set_tooltip("Z: " + format(m_cut_z, 2)); } + const Selection& selection = m_parent.get_selection(); + update_max_z(selection); const BoundingBoxf3& box = selection.get_bounding_box(); @@ -123,14 +128,13 @@ void GLGizmoCut::on_render(const Selection& selection) const m_grabbers[0].render(m_hover_id == 0, (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0)); } -void GLGizmoCut::on_render_for_picking(const Selection& selection) const +void GLGizmoCut::on_render_for_picking() const { glsafe(::glDisable(GL_DEPTH_TEST)); - - render_grabbers_for_picking(selection.get_bounding_box()); + render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); } -void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) +void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) { const float approx_height = m_imgui->scaled(11.0f); y = std::min(y, bottom_limit - approx_height); @@ -153,7 +157,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, co m_imgui->end(); if (cut_clicked && (m_keep_upper || m_keep_lower)) { - perform_cut(selection); + perform_cut(m_parent.get_selection()); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 628bcf508e..5bfeda526a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -31,12 +31,12 @@ protected: virtual void on_save(cereal::BinaryOutputArchive& ar) const { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); } virtual std::string on_get_name() const; virtual void on_set_state(); - virtual bool on_is_activable(const Selection& selection) const; - virtual void on_start_dragging(const Selection& selection); - virtual void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); + virtual bool on_is_activable() const; + virtual void on_start_dragging(); + virtual void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; + virtual void on_render_input_window(float x, float y, float bottom_limit); private: void update_max_z(const Selection& selection) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 6bb81a7032..78f5da58b9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -1,5 +1,6 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoFlatten.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" #include @@ -27,23 +28,25 @@ std::string GLGizmoFlatten::on_get_name() const return (_(L("Place on face")) + " [F]").ToUTF8().data(); } -bool GLGizmoFlatten::on_is_activable(const Selection& selection) const +bool GLGizmoFlatten::on_is_activable() const { - return selection.is_single_full_instance(); + return m_parent.get_selection().is_single_full_instance(); } -void GLGizmoFlatten::on_start_dragging(const Selection& selection) +void GLGizmoFlatten::on_start_dragging() { if (m_hover_id != -1) { assert(m_planes_valid); m_normal = m_planes[m_hover_id].normal; - m_starting_center = selection.get_bounding_box().center(); + m_starting_center = m_parent.get_selection().get_bounding_box().center(); } } -void GLGizmoFlatten::on_render(const Selection& selection) const +void GLGizmoFlatten::on_render() const { + const Selection& selection = m_parent.get_selection(); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); @@ -78,8 +81,10 @@ void GLGizmoFlatten::on_render(const Selection& selection) const glsafe(::glDisable(GL_BLEND)); } -void GLGizmoFlatten::on_render_for_picking(const Selection& selection) const +void GLGizmoFlatten::on_render_for_picking() const { + const Selection& selection = m_parent.get_selection(); + glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glDisable(GL_BLEND)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp index 926dc34576..c8bd056bc1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -45,11 +45,10 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const; - virtual bool on_is_activable(const Selection& selection) const; - virtual void on_start_dragging(const Selection& selection); - virtual void on_update(const UpdateData& data, const Selection& selection) {} - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; + virtual bool on_is_activable() const; + virtual void on_start_dragging(); + virtual void on_render() const; + virtual void on_render_for_picking() const; virtual void on_set_state() { if (m_state == On && is_plane_update_necessary()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index b97f578b3a..11bdcd4f83 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -1,5 +1,6 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoMove.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" #include @@ -47,12 +48,12 @@ std::string GLGizmoMove3D::on_get_name() const return (_(L("Move")) + " [M]").ToUTF8().data(); } -void GLGizmoMove3D::on_start_dragging(const Selection& selection) +void GLGizmoMove3D::on_start_dragging() { if (m_hover_id != -1) { m_displacement = Vec3d::Zero(); - const BoundingBoxf3& box = selection.get_bounding_box(); + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); m_starting_drag_position = m_grabbers[m_hover_id].center; m_starting_box_center = box.center(); m_starting_box_bottom_center = box.center(); @@ -65,7 +66,7 @@ void GLGizmoMove3D::on_stop_dragging() m_displacement = Vec3d::Zero(); } -void GLGizmoMove3D::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoMove3D::on_update(const UpdateData& data) { if (m_hover_id == 0) m_displacement(0) = calc_projection(data); @@ -75,8 +76,10 @@ void GLGizmoMove3D::on_update(const UpdateData& data, const Selection& selection m_displacement(2) = calc_projection(data); } -void GLGizmoMove3D::on_render(const Selection& selection) const +void GLGizmoMove3D::on_render() const { + const Selection& selection = m_parent.get_selection(); + bool show_position = selection.is_single_full_instance(); const Vec3d& position = selection.get_bounding_box().center(); @@ -152,20 +155,21 @@ void GLGizmoMove3D::on_render(const Selection& selection) const } } -void GLGizmoMove3D::on_render_for_picking(const Selection& selection) const +void GLGizmoMove3D::on_render_for_picking() const { glsafe(::glDisable(GL_DEPTH_TEST)); - const BoundingBoxf3& box = selection.get_bounding_box(); + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); render_grabbers_for_picking(box); render_grabber_extension(X, box, true); render_grabber_extension(Y, box, true); render_grabber_extension(Z, box, true); } -void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) -{ #if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit) +{ + const Selection& selection = m_parent.get_selection(); bool show_position = selection.is_single_full_instance(); const Vec3d& position = selection.get_bounding_box().center(); @@ -178,8 +182,8 @@ void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit, m_imgui->input_vec3("", displacement, 100.0f, "%.2f"); m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI } +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI double GLGizmoMove3D::calc_projection(const UpdateData& data) const { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 7714967808..21b1d397be 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -33,12 +33,14 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const; - virtual void on_start_dragging(const Selection& selection); + virtual void on_start_dragging(); virtual void on_stop_dragging(); - virtual void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); + virtual void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + virtual void on_render_input_window(float x, float y, float bottom_limit); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI private: double calc_projection(const UpdateData& data) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index f84c3886d7..f481bb5d74 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -1,5 +1,6 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoRotate.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" #include @@ -72,9 +73,9 @@ bool GLGizmoRotate::on_init() return true; } -void GLGizmoRotate::on_start_dragging(const Selection& selection) +void GLGizmoRotate::on_start_dragging() { - const BoundingBoxf3& box = selection.get_bounding_box(); + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); m_center = box.center(); m_radius = Offset + box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; @@ -83,9 +84,9 @@ void GLGizmoRotate::on_start_dragging(const Selection& selection) m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; } -void GLGizmoRotate::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoRotate::on_update(const UpdateData& data) { - Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, selection)); + Vec2d mouse_pos = to_2d(mouse_position_in_local_plane(data.mouse_ray, m_parent.get_selection())); Vec2d orig_dir = Vec2d::UnitX(); Vec2d new_dir = mouse_pos.normalized(); @@ -118,11 +119,12 @@ void GLGizmoRotate::on_update(const UpdateData& data, const Selection& selection m_angle = theta; } -void GLGizmoRotate::on_render(const Selection& selection) const +void GLGizmoRotate::on_render() const { if (!m_grabbers[0].enabled) return; + const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); std::string axis; @@ -175,8 +177,10 @@ void GLGizmoRotate::on_render(const Selection& selection) const glsafe(::glPopMatrix()); } -void GLGizmoRotate::on_render_for_picking(const Selection& selection) const +void GLGizmoRotate::on_render_for_picking() const { + const Selection& selection = m_parent.get_selection(); + glsafe(::glDisable(GL_DEPTH_TEST)); glsafe(::glPushMatrix()); @@ -445,10 +449,10 @@ std::string GLGizmoRotate3D::on_get_name() const return (_(L("Rotate")) + " [R]").ToUTF8().data(); } -void GLGizmoRotate3D::on_start_dragging(const Selection& selection) +void GLGizmoRotate3D::on_start_dragging() { if ((0 <= m_hover_id) && (m_hover_id < 3)) - m_gizmos[m_hover_id].start_dragging(selection); + m_gizmos[m_hover_id].start_dragging(); } void GLGizmoRotate3D::on_stop_dragging() @@ -457,23 +461,23 @@ void GLGizmoRotate3D::on_stop_dragging() m_gizmos[m_hover_id].stop_dragging(); } -void GLGizmoRotate3D::on_render(const Selection& selection) const +void GLGizmoRotate3D::on_render() const { glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); if ((m_hover_id == -1) || (m_hover_id == 0)) - m_gizmos[X].render(selection); + m_gizmos[X].render(); if ((m_hover_id == -1) || (m_hover_id == 1)) - m_gizmos[Y].render(selection); + m_gizmos[Y].render(); if ((m_hover_id == -1) || (m_hover_id == 2)) - m_gizmos[Z].render(selection); + m_gizmos[Z].render(); } -void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) -{ #if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit) +{ Vec3d rotation(Geometry::rad2deg(m_gizmos[0].get_angle()), Geometry::rad2deg(m_gizmos[1].get_angle()), Geometry::rad2deg(m_gizmos[2].get_angle())); wxString label = _(L("Rotation (deg)")); @@ -482,8 +486,8 @@ void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limi m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); m_imgui->input_vec3("", rotation, 100.0f, "%.2f"); m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI } +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index d2e5649669..7846edb22e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -52,10 +52,10 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const { return ""; } - virtual void on_start_dragging(const Selection& selection); - virtual void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; + virtual void on_start_dragging(); + virtual void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; private: void render_circle() const; @@ -98,7 +98,6 @@ protected: m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); } } - virtual bool on_is_activable(const Selection& selection) const { return true; } virtual void on_enable_grabber(unsigned int id) { if ((0 <= id) && (id < 3)) @@ -109,25 +108,26 @@ protected: if ((0 <= id) && (id < 3)) m_gizmos[id].disable_grabber(0); } - virtual void on_start_dragging(const Selection& selection); + virtual void on_start_dragging(); virtual void on_stop_dragging(); - virtual void on_update(const UpdateData& data, const Selection& selection) + virtual void on_update(const UpdateData& data) { for (GLGizmoRotate& g : m_gizmos) { - g.update(data, selection); + g.update(data); } } - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const + virtual void on_render() const; + virtual void on_render_for_picking() const { for (const GLGizmoRotate& g : m_gizmos) { - g.render_for_picking(selection); + g.render_for_picking(); } } - - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + virtual void on_render_input_window(float x, float y, float bottom_limit); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index d30ceb092a..7dc38b8011 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -1,7 +1,6 @@ - - // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoScale.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" #include @@ -48,13 +47,18 @@ std::string GLGizmoScale3D::on_get_name() const return (_(L("Scale")) + " [S]").ToUTF8().data(); } -void GLGizmoScale3D::on_start_dragging(const Selection& selection) +bool GLGizmoScale3D::on_is_activable() const +{ + return !m_parent.get_selection().is_wipe_tower(); +} + +void GLGizmoScale3D::on_start_dragging() { if (m_hover_id != -1) { m_starting.drag_position = m_grabbers[m_hover_id].center; m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); - m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : selection.get_bounding_box(); + m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box(); const Vec3d& center = m_starting.box.center(); m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max(0), center(1), center(2)); @@ -66,7 +70,7 @@ void GLGizmoScale3D::on_start_dragging(const Selection& selection) } } -void GLGizmoScale3D::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoScale3D::on_update(const UpdateData& data) { if ((m_hover_id == 0) || (m_hover_id == 1)) do_scale_along_axis(X, data); @@ -78,8 +82,10 @@ void GLGizmoScale3D::on_update(const UpdateData& data, const Selection& selectio do_scale_uniform(data); } -void GLGizmoScale3D::on_render(const Selection& selection) const +void GLGizmoScale3D::on_render() const { + const Selection& selection = m_parent.get_selection(); + bool single_instance = selection.is_single_full_instance(); bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); bool single_selection = single_instance || single_volume; @@ -272,16 +278,16 @@ void GLGizmoScale3D::on_render(const Selection& selection) const } } -void GLGizmoScale3D::on_render_for_picking(const Selection& selection) const +void GLGizmoScale3D::on_render_for_picking() const { glsafe(::glDisable(GL_DEPTH_TEST)); - - render_grabbers_for_picking(selection.get_bounding_box()); + render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); } -void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) -{ #if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI +void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit) +{ + const Selection& selection = m_parent.get_selection(); bool single_instance = selection.is_single_full_instance(); wxString label = _(L("Scale (%)")); @@ -290,8 +296,8 @@ void GLGizmoScale3D::on_render_input_window(float x, float y, float bottom_limit m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); m_imgui->input_vec3("", m_scale * 100.f, 100.0f, "%.2f"); m_imgui->end(); -#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI } +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 16307165f6..7b77fe5599 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -45,12 +45,14 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const; - virtual bool on_is_activable(const Selection& selection) const { return !selection.is_wipe_tower(); } - virtual void on_start_dragging(const Selection& selection); - virtual void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection); + virtual bool on_is_activable() const; + virtual void on_start_dragging(); + virtual void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; +#if !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI + virtual void on_render_input_window(float x, float y, float bottom_limit); +#endif // !DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI private: void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index fdc15e928b..3e6530d30f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -94,8 +94,10 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S } } -void GLGizmoSlaSupports::on_render(const Selection& selection) const +void GLGizmoSlaSupports::on_render() const { + const Selection& selection = m_parent.get_selection(); + // If current m_model_object does not match selection, ask GLCanvas3D to turn us off if (m_state == On && (m_model_object != selection.get_model()->objects[selection.get_object_idx()] @@ -252,8 +254,9 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const } -void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const +void GLGizmoSlaSupports::on_render_for_picking() const { + const Selection& selection = m_parent.get_selection(); #if ENABLE_RENDER_PICKING_PASS m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); #endif @@ -702,12 +705,12 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) //m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } -void GLGizmoSlaSupports::on_update(const UpdateData& data, const Selection& selection) +void GLGizmoSlaSupports::on_update(const UpdateData& data) { - if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) { + if (m_editing_mode && m_hover_id != -1 && (!m_editing_mode_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) { std::pair pos_and_normal; try { - pos_and_normal = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); + pos_and_normal = unproject_on_mesh(data.mouse_pos.cast()); } catch (...) { return; } m_editing_mode_cache[m_hover_id].support_point.pos = pos_and_normal.first; @@ -822,7 +825,7 @@ void GLGizmoSlaSupports::make_line_segments() const */ -void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) +void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit) { if (!m_model_object) return; @@ -1002,11 +1005,13 @@ RENDER_AGAIN: m_parent.set_as_dirty(); } -bool GLGizmoSlaSupports::on_is_activable(const Selection& selection) const +bool GLGizmoSlaSupports::on_is_activable() const { + const Selection& selection = m_parent.get_selection(); + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA || !selection.is_from_single_instance()) - return false; + return false; // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. const Selection::IndicesList& list = selection.get_volume_idxs(); @@ -1075,7 +1080,7 @@ void GLGizmoSlaSupports::on_set_state() -void GLGizmoSlaSupports::on_start_dragging(const Selection& selection) +void GLGizmoSlaSupports::on_start_dragging() { if (m_hover_id != -1) { select_point(NoPoints); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 5e50eae0be..b0a505b823 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -70,9 +70,9 @@ public: private: bool on_init(); - void on_update(const UpdateData& data, const Selection& selection); - virtual void on_render(const Selection& selection) const; - virtual void on_render_for_picking(const Selection& selection) const; + void on_update(const UpdateData& data); + virtual void on_render() const; + virtual void on_render_for_picking() const; //void render_selection_rectangle() const; void render_points(const Selection& selection, bool picking = false) const; @@ -133,11 +133,11 @@ protected: if ((int)m_editing_mode_cache.size() <= m_hover_id) m_hover_id = -1; } - void on_start_dragging(const Selection& selection) override; - virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) override; + void on_start_dragging() override; + virtual void on_render_input_window(float x, float y, float bottom_limit) override; virtual std::string on_get_name() const; - virtual bool on_is_activable(const Selection& selection) const; + virtual bool on_is_activable() const; virtual bool on_is_selectable() const; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 5922f96004..22b49ee5d3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -150,7 +150,7 @@ void GLGizmosManager::refresh_on_off_state() GizmosMap::iterator it = m_gizmos.find(m_current); if ((it != m_gizmos.end()) && (it->second != nullptr)) { - if (!it->second->is_activable(m_parent->get_selection())) + if (!it->second->is_activable()) { it->second->set_state(GLGizmoBase::Off); m_current = Undefined; @@ -205,14 +205,14 @@ void GLGizmosManager::enable_grabber(EType type, unsigned int id, bool enable) } } -void GLGizmosManager::update(const Linef3& mouse_ray, const Point* mouse_pos) +void GLGizmosManager::update(const Linef3& mouse_ray, const Point& mouse_pos) { if (!m_enabled || (m_parent == nullptr)) return; GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos), m_parent->get_selection()); + curr->update(GLGizmoBase::UpdateData(mouse_ray, mouse_pos)); } void GLGizmosManager::update_data() @@ -282,8 +282,7 @@ bool GLGizmosManager::handle_shortcut(int key) if (!m_enabled || (m_parent == nullptr)) return false; - const Selection& selection = m_parent->get_selection(); - if (selection.is_empty()) + if (m_parent->get_selection().is_empty()) return false; EType old_current = m_current; @@ -295,7 +294,7 @@ bool GLGizmosManager::handle_shortcut(int key) int it_key = it->second->get_shortcut_key(); - if (it->second->is_activable(selection) && ((it_key == key - 64) || (it_key == key - 96))) + if (it->second->is_activable() && ((it_key == key - 64) || (it_key == key - 96))) { if ((it->second->get_state() == GLGizmoBase::On)) { @@ -338,7 +337,7 @@ void GLGizmosManager::start_dragging() GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->start_dragging(m_parent->get_selection()); + curr->start_dragging(); } void GLGizmosManager::stop_dragging() @@ -469,7 +468,7 @@ void GLGizmosManager::render_current_gizmo() const GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->render(m_parent->get_selection()); + curr->render(); } void GLGizmosManager::render_current_gizmo_for_picking_pass() const @@ -479,7 +478,7 @@ void GLGizmosManager::render_current_gizmo_for_picking_pass() const GLGizmoBase* curr = get_current(); if (curr != nullptr) - curr->render_for_picking(m_parent->get_selection()); + curr->render_for_picking(); } void GLGizmosManager::render_overlay() const @@ -589,7 +588,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) m_parent->get_wxglcanvas()->CaptureMouse(); m_parent->set_mouse_as_dragging(); - update(m_parent->mouse_ray(pos), &pos); + update(m_parent->mouse_ray(pos), pos); switch (m_current) { @@ -902,8 +901,6 @@ void GLGizmosManager::do_render_overlay() const if ((m_parent == nullptr) || m_gizmos.empty()) return; - const Selection& selection = m_parent->get_selection(); - float cnv_w = (float)m_parent->get_canvas_size().get_width(); float cnv_h = (float)m_parent->get_canvas_size().get_height(); float zoom = (float)m_parent->get_camera().get_zoom(); @@ -1020,7 +1017,7 @@ void GLGizmosManager::do_render_overlay() const GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + scaled_icons_size, top_y - scaled_icons_size, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); if (it->second->get_state() == GLGizmoBase::On) { float toolbar_top = (float)cnv_h - m_parent->get_view_toolbar_height(); - it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); + it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top); } top_y -= scaled_stride_y; } @@ -1087,8 +1084,6 @@ void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos) if (!m_enabled || (m_parent == nullptr)) return; - const Selection& selection = m_parent->get_selection(); - float cnv_h = (float)m_parent->get_canvas_size().get_height(); float height = get_total_overlay_height(); @@ -1104,7 +1099,7 @@ void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos) continue; bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); - if (it->second->is_activable(selection) && inside) + if (it->second->is_activable() && inside) { if ((it->second->get_state() == GLGizmoBase::On)) { @@ -1135,8 +1130,6 @@ std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos) if (!m_enabled || (m_parent == nullptr)) return name; - const Selection& selection = m_parent->get_selection(); - float cnv_h = (float)m_parent->get_canvas_size().get_height(); float height = get_total_overlay_height(); float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; @@ -1154,7 +1147,7 @@ std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos) if (inside) name = it->second->get_name(); - if (it->second->is_activable(selection) && (it->second->get_state() != GLGizmoBase::On)) + if (it->second->is_activable() && (it->second->get_state() != GLGizmoBase::On)) it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off); top_y += scaled_stride_y; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index db73345aea..f41bed1e63 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -140,7 +140,7 @@ public: void set_hover_id(int id); void enable_grabber(EType type, unsigned int id, bool enable); - void update(const Linef3& mouse_ray, const Point* mouse_pos = nullptr); + void update(const Linef3& mouse_ray, const Point& mouse_pos); void update_data(); EType get_current_type() const { return m_current; } From 401707a6fed24f44e468b10538092e74bd17d9ab Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 17 Jul 2019 12:43:27 +0200 Subject: [PATCH 329/627] Another refactoring of GLGizmosManager --- src/slic3r/GUI/GLCanvas3D.cpp | 3 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 133 ++++++++++------------ src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 6 +- src/slic3r/GUI/Plater.cpp | 2 +- 4 files changed, 64 insertions(+), 80 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9bb9c21585..192112ba75 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1203,6 +1203,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , m_camera(camera) , m_view_toolbar(view_toolbar) , m_toolbar(GLToolbar::Normal, "Top") + , m_gizmos(*this) , m_use_clipping_planes(false) , m_sidebar_field("") , m_keep_dirty(false) @@ -1319,7 +1320,7 @@ bool GLCanvas3D::init() // if (!m_volumes.empty()) // m_volumes.finalize_geometry(); - if (m_gizmos.is_enabled() && !m_gizmos.init(*this)) + if (m_gizmos.is_enabled() && !m_gizmos.init()) std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; if (!_init_toolbar()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 22b49ee5d3..622fdb2d8f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -14,8 +14,8 @@ namespace GUI { const float GLGizmosManager::Default_Icons_Size = 64; -GLGizmosManager::GLGizmosManager() - : m_parent(nullptr) +GLGizmosManager::GLGizmosManager(GLCanvas3D& parent) + : m_parent(parent) , m_enabled(false) , m_icons_texture_dirty(true) , m_current(Undefined) @@ -33,10 +33,8 @@ GLGizmosManager::~GLGizmosManager() reset(); } -bool GLGizmosManager::init(GLCanvas3D& parent) +bool GLGizmosManager::init() { - m_parent = &parent; - m_background_texture.metadata.filename = "toolbar_background.png"; m_background_texture.metadata.left = 16; m_background_texture.metadata.top = 16; @@ -52,7 +50,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) } } - GLGizmoBase* gizmo = new GLGizmoMove3D(parent, "move.svg", 0); + GLGizmoBase* gizmo = new GLGizmoMove3D(m_parent, "move.svg", 0); if (gizmo == nullptr) return false; @@ -61,7 +59,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Move, gizmo)); - gizmo = new GLGizmoScale3D(parent, "scale.svg", 1); + gizmo = new GLGizmoScale3D(m_parent, "scale.svg", 1); if (gizmo == nullptr) return false; @@ -70,7 +68,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Scale, gizmo)); - gizmo = new GLGizmoRotate3D(parent, "rotate.svg", 2); + gizmo = new GLGizmoRotate3D(m_parent, "rotate.svg", 2); if (gizmo == nullptr) { reset(); @@ -85,7 +83,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo)); - gizmo = new GLGizmoFlatten(parent, "place.svg", 3); + gizmo = new GLGizmoFlatten(m_parent, "place.svg", 3); if (gizmo == nullptr) return false; @@ -96,7 +94,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Flatten, gizmo)); - gizmo = new GLGizmoCut(parent, "cut.svg", 4); + gizmo = new GLGizmoCut(m_parent, "cut.svg", 4); if (gizmo == nullptr) return false; @@ -107,7 +105,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) m_gizmos.insert(GizmosMap::value_type(Cut, gizmo)); - gizmo = new GLGizmoSlaSupports(parent, "sla_supports.svg", 5); + gizmo = new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 5); if (gizmo == nullptr) return false; @@ -141,9 +139,6 @@ void GLGizmosManager::set_overlay_scale(float scale) void GLGizmosManager::refresh_on_off_state() { - if (m_parent == nullptr) - return; - if (m_serializing) return; @@ -207,7 +202,7 @@ void GLGizmosManager::enable_grabber(EType type, unsigned int id, bool enable) void GLGizmosManager::update(const Linef3& mouse_ray, const Point& mouse_pos) { - if (!m_enabled || (m_parent == nullptr)) + if (!m_enabled) return; GLGizmoBase* curr = get_current(); @@ -217,10 +212,10 @@ void GLGizmosManager::update(const Linef3& mouse_ray, const Point& mouse_pos) void GLGizmosManager::update_data() { - if (!m_enabled || (m_parent == nullptr)) + if (!m_enabled) return; - const Selection& selection = m_parent->get_selection(); + const Selection& selection = m_parent.get_selection(); bool is_wipe_tower = selection.is_wipe_tower(); enable_grabber(Move, 2, !is_wipe_tower); @@ -279,10 +274,10 @@ bool GLGizmosManager::is_running() const bool GLGizmosManager::handle_shortcut(int key) { - if (!m_enabled || (m_parent == nullptr)) + if (!m_enabled) return false; - if (m_parent->get_selection().is_empty()) + if (m_parent.get_selection().is_empty()) return false; EType old_current = m_current; @@ -332,7 +327,7 @@ bool GLGizmosManager::is_dragging() const void GLGizmosManager::start_dragging() { - if (!m_enabled || (m_parent == nullptr)) + if (!m_enabled) return; GLGizmoBase* curr = get_current(); @@ -427,12 +422,12 @@ void GLGizmosManager::set_flattening_data(const ModelObject* model_object) void GLGizmosManager::set_sla_support_data(ModelObject* model_object) { - if (!m_enabled || (m_parent == nullptr)) + if (!m_enabled) return; GizmosMap::const_iterator it = m_gizmos.find(SlaSupports); if (it != m_gizmos.end()) - reinterpret_cast(it->second)->set_sla_support_data(model_object, m_parent->get_selection()); + reinterpret_cast(it->second)->set_sla_support_data(model_object, m_parent.get_selection()); } // Returns true if the gizmo used the event to do something, false otherwise. @@ -463,7 +458,7 @@ ClippingPlane GLGizmosManager::get_sla_clipping_plane() const void GLGizmosManager::render_current_gizmo() const { - if (!m_enabled || (m_parent == nullptr)) + if (!m_enabled) return; GLGizmoBase* curr = get_current(); @@ -473,7 +468,7 @@ void GLGizmosManager::render_current_gizmo() const void GLGizmosManager::render_current_gizmo_for_picking_pass() const { - if (!m_enabled || (m_parent == nullptr)) + if (!m_enabled) return; GLGizmoBase* curr = get_current(); @@ -483,7 +478,7 @@ void GLGizmosManager::render_current_gizmo_for_picking_pass() const void GLGizmosManager::render_overlay() const { - if (!m_enabled || (m_parent == nullptr)) + if (!m_enabled) return; if (m_icons_texture_dirty) @@ -494,9 +489,6 @@ void GLGizmosManager::render_overlay() const bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt) { - if (m_parent == nullptr) - return false; - bool processed = false; if (m_current == SlaSupports) { @@ -510,13 +502,10 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt) bool GLGizmosManager::on_mouse(wxMouseEvent& evt) { - if (m_parent == nullptr) - return false; - Point pos(evt.GetX(), evt.GetY()); Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY()); - Selection& selection = m_parent->get_selection(); + Selection& selection = m_parent.get_selection(); int selected_object_idx = selection.get_object_idx(); bool processed = false; @@ -562,33 +551,33 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) if (m_current == Flatten) { // Rotate the object so the normal points downward: - m_parent->do_flatten(get_flattening_normal(), "Gizmo - Place on Face"); + m_parent.do_flatten(get_flattening_normal(), "Gizmo-Place on Face"); wxGetApp().obj_manipul()->set_dirty(); } - m_parent->set_as_dirty(); + m_parent.set_as_dirty(); processed = true; } } else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::RightDown)) // event was taken care of by the SlaSupports gizmo processed = true; - else if (evt.Dragging() && (m_parent->get_move_volume_id() != -1) && (m_current == SlaSupports)) - // don't allow dragging objects with the Sla gizmo on + else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports)) + // don't allow dragging objects with the Sla gizmo on processed = true; else if (evt.Dragging() && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown())) { // the gizmo got the event and took some action, no need to do anything more here - m_parent->set_as_dirty(); + m_parent.set_as_dirty(); processed = true; } else if (evt.Dragging() && is_dragging()) { - if (!m_parent->get_wxglcanvas()->HasCapture()) - m_parent->get_wxglcanvas()->CaptureMouse(); + if (!m_parent.get_wxglcanvas()->HasCapture()) + m_parent.get_wxglcanvas()->CaptureMouse(); - m_parent->set_mouse_as_dragging(); - update(m_parent->mouse_ray(pos), pos); + m_parent.set_mouse_as_dragging(); + update(m_parent.mouse_ray(pos), pos); switch (m_current) { @@ -625,7 +614,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) break; } - m_parent->set_as_dirty(); + m_parent.set_as_dirty(); processed = true; } else if (evt.LeftUp() && is_dragging()) @@ -634,18 +623,18 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) { case Move: { - m_parent->disable_regenerate_volumes(); - m_parent->do_move("Gizmo-Move Object"); + m_parent.disable_regenerate_volumes(); + m_parent.do_move("Gizmo-Move"); break; } case Scale: { - m_parent->do_scale("Gizmo-Scale Object"); + m_parent.do_scale("Gizmo-Scale"); break; } case Rotate: { - m_parent->do_rotate("Gizmo-Rotate Object"); + m_parent.do_rotate("Gizmo-Rotate"); break; } default: @@ -658,20 +647,20 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) wxGetApp().obj_manipul()->set_dirty(); // Let the platter know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. - m_parent->post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); // updates camera target constraints - m_parent->refresh_camera_scene_box(); + m_parent.refresh_camera_scene_box(); processed = true; } - else if (evt.LeftUp() && (m_current == SlaSupports) && !m_parent->is_mouse_dragging()) + else if (evt.LeftUp() && (m_current == SlaSupports) && !m_parent.is_mouse_dragging()) { // in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither // object moving or selecting is suppressed in that case gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()); processed = true; } - else if (evt.LeftUp() && (m_current == Flatten) && ((m_parent->get_first_hover_volume_idx() != -1) || grabber_contains_mouse())) + else if (evt.LeftUp() && (m_current == Flatten) && ((m_parent.get_first_hover_volume_idx() != -1) || grabber_contains_mouse())) { // to avoid to loose the selection when user clicks an object while the Flatten gizmo is active processed = true; @@ -683,24 +672,24 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) if (evt.LeftDown() || evt.LeftDClick()) { m_mouse_capture.left = true; - m_mouse_capture.parent = m_parent; + m_mouse_capture.parent = &m_parent; processed = true; if (!selection.is_empty()) { update_on_off_state(mouse_pos); update_data(); - m_parent->set_as_dirty(); + m_parent.set_as_dirty(); } } else if (evt.MiddleDown()) { m_mouse_capture.middle = true; - m_mouse_capture.parent = m_parent; + m_mouse_capture.parent = &m_parent; } else if (evt.RightDown()) { m_mouse_capture.right = true; - m_mouse_capture.parent = m_parent; + m_mouse_capture.parent = &m_parent; } else if (evt.LeftUp()) processed = true; @@ -711,9 +700,6 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) bool GLGizmosManager::on_char(wxKeyEvent& evt) { - if (m_parent == nullptr) - return false; - // see include/wx/defs.h enum wxKeyCode int keyCode = evt.GetKeyCode(); int ctrlMask = wxMOD_CONTROL; @@ -828,16 +814,13 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) } if (processed) - m_parent->set_as_dirty(); + m_parent.set_as_dirty(); return processed; } bool GLGizmosManager::on_key(wxKeyEvent& evt) { - if (m_parent == nullptr) - return false; - const int keyCode = evt.GetKeyCode(); bool processed = false; @@ -862,19 +845,19 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) } // if (processed) -// m_parent->set_cursor(GLCanvas3D::Standard); +// m_parent.set_cursor(GLCanvas3D::Standard); } else if (evt.GetEventType() == wxEVT_KEY_DOWN) { if ((m_current == SlaSupports) && ((keyCode == WXK_SHIFT) || (keyCode == WXK_ALT)) && reinterpret_cast(get_current())->is_in_editing_mode()) { -// m_parent->set_cursor(GLCanvas3D::Cross); +// m_parent.set_cursor(GLCanvas3D::Cross); processed = true; } } if (processed) - m_parent->set_as_dirty(); + m_parent.set_as_dirty(); return processed; } @@ -898,12 +881,12 @@ void GLGizmosManager::reset() void GLGizmosManager::do_render_overlay() const { - if ((m_parent == nullptr) || m_gizmos.empty()) + if (m_gizmos.empty()) return; - float cnv_w = (float)m_parent->get_canvas_size().get_width(); - float cnv_h = (float)m_parent->get_canvas_size().get_height(); - float zoom = (float)m_parent->get_camera().get_zoom(); + float cnv_w = (float)m_parent.get_canvas_size().get_width(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); + float zoom = (float)m_parent.get_camera().get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float height = get_total_overlay_height(); @@ -1016,7 +999,7 @@ void GLGizmosManager::do_render_overlay() const GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + scaled_icons_size, top_y - scaled_icons_size, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); if (it->second->get_state() == GLGizmoBase::On) { - float toolbar_top = (float)cnv_h - m_parent->get_view_toolbar_height(); + float toolbar_top = (float)cnv_h - m_parent.get_view_toolbar_height(); it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top); } top_y -= scaled_stride_y; @@ -1081,10 +1064,10 @@ bool GLGizmosManager::generate_icons_texture() const void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos) { - if (!m_enabled || (m_parent == nullptr)) + if (!m_enabled) return; - float cnv_h = (float)m_parent->get_canvas_size().get_height(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); float height = get_total_overlay_height(); float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; @@ -1127,10 +1110,10 @@ std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos) { std::string name = ""; - if (!m_enabled || (m_parent == nullptr)) + if (!m_enabled) return name; - float cnv_h = (float)m_parent->get_canvas_size().get_height(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); float height = get_total_overlay_height(); float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; float scaled_border = m_overlay_border * m_overlay_scale; @@ -1158,10 +1141,10 @@ std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos) bool GLGizmosManager::overlay_contains_mouse(const Vec2d& mouse_pos) const { - if (!m_enabled || (m_parent == nullptr)) + if (!m_enabled) return false; - float cnv_h = (float)m_parent->get_canvas_size().get_height(); + float cnv_h = (float)m_parent.get_canvas_size().get_height(); float height = get_total_overlay_height(); float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index f41bed1e63..803613ec74 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -60,7 +60,7 @@ public: }; private: - GLCanvas3D* m_parent; + GLCanvas3D& m_parent; bool m_enabled; typedef std::map GizmosMap; GizmosMap m_gizmos; @@ -92,10 +92,10 @@ private: bool m_serializing; public: - GLGizmosManager(); + explicit GLGizmosManager(GLCanvas3D& parent); ~GLGizmosManager(); - bool init(GLCanvas3D& parent); + bool init(); template void load(Archive& ar) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c5a4cceadf..8fefca00c1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3885,7 +3885,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe return; } - this->take_snapshot(_(L("Gizmo - Cut"))); + this->take_snapshot(_(L("Gizmo-Cut"))); wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); From 2728f411238049668938e5529e36fc03c81eb203 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 17 Jul 2019 13:32:31 +0200 Subject: [PATCH 330/627] Solved issue with virtual bed management. libnest2d: Fix for unclosed polygons after merge. --- .../libnest2d/backends/clipper/geometries.hpp | 18 +++-- src/libslic3r/Model.cpp | 25 ++++--- src/libslic3r/Model.hpp | 15 ++-- src/slic3r/GUI/GLCanvas3D.hpp | 1 - src/slic3r/GUI/Plater.cpp | 72 +++++++++---------- 5 files changed, 64 insertions(+), 67 deletions(-) diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index c51e0f5c94..2ca4f5d50e 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -259,10 +259,12 @@ inline TMultiShape clipper_execute( poly.Contour.swap(pptr->Contour); assert(!pptr->IsHole()); - - if(pptr->IsOpen()) { + + if(!poly.Contour.empty() ) { auto front_p = poly.Contour.front(); - poly.Contour.emplace_back(front_p); + auto &back_p = poly.Contour.back(); + if(front_p.X != back_p.X || front_p.Y != back_p.X) + poly.Contour.emplace_back(front_p); } for(auto h : pptr->Childs) { processHole(h, poly); } @@ -274,10 +276,12 @@ inline TMultiShape clipper_execute( poly.Holes.emplace_back(std::move(pptr->Contour)); assert(pptr->IsHole()); - - if(pptr->IsOpen()) { - auto front_p = poly.Holes.back().front(); - poly.Holes.back().emplace_back(front_p); + + if(!poly.Contour.empty() ) { + auto front_p = poly.Contour.front(); + auto &back_p = poly.Contour.back(); + if(front_p.X != back_p.X || front_p.Y != back_p.X) + poly.Contour.emplace_back(front_p); } for(auto c : pptr->Childs) processPoly(c); diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index cf0f7c8556..abe8af16ae 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -402,9 +402,11 @@ bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) for(size_t i = 0; i < input.size(); ++i) { if (input[i].bed_idx != 0) ret = false; - if (input[i].bed_idx >= 0) - instances[i]->apply_arrange_result(input[i], - {input[i].bed_idx * stride, 0}); + if (input[i].bed_idx >= 0) { + input[i].translation += Vec2crd{input[i].bed_idx * stride, 0}; + instances[i]->apply_arrange_result(input[i].translation, + input[i].rotation); + } } return ret; @@ -1826,23 +1828,20 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const // this may happen for malformed models, see: // https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (p.points.empty()) return {}; - - Polygons pp{p}; - pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); - if (!pp.empty()) p = pp.front(); + if (!p.points.empty()) { + Polygons pp{p}; + pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); + if (!pp.empty()) p = pp.front(); + } + m_arrange_cache.poly.contour = std::move(p); - m_arrange_cache.bed_origin = {0, 0}; - m_arrange_cache.bed_idx = arrangement::UNARRANGED; m_arrange_cache.valid = true; } arrangement::ArrangePolygon ret; ret.poly = m_arrange_cache.poly; - ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))} - - m_arrange_cache.bed_origin; + ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}; ret.rotation = get_rotation(Z); - ret.bed_idx = m_arrange_cache.bed_idx; return ret; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 548e0f015d..5a0dc98635 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -516,7 +516,7 @@ public: const Vec3d& get_offset() const { return m_transformation.get_offset(); } double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } - + void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } @@ -558,15 +558,12 @@ public: arrangement::ArrangePolygon get_arrange_polygon() const; // Apply the arrange result on the ModelInstance - void apply_arrange_result(const arrangement::ArrangePolygon& ap, - const Vec2crd& bed_origin = {0, 0}) + void apply_arrange_result(const Vec2crd& offs, double rotation) { // write the transformation data into the model instance - set_rotation(Z, ap.rotation); - set_offset(X, unscale(ap.translation(X) + bed_origin.x())); - set_offset(Y, unscale(ap.translation(Y) + bed_origin.y())); - m_arrange_cache.bed_origin = bed_origin; - m_arrange_cache.bed_idx = ap.bed_idx; + set_rotation(Z, rotation); + set_offset(X, unscale(offs(X))); + set_offset(Y, unscale(offs(Y))); } protected: @@ -601,9 +598,7 @@ private: // Warning! This object is not guarded against concurrency. mutable struct ArrangeCache { bool valid = false; - Vec2crd bed_origin {0, 0}; ExPolygon poly; - int bed_idx = arrangement::UNARRANGED; } m_arrange_cache; }; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 6c5e6475c5..36d16035e6 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -4,7 +4,6 @@ #include #include -#include "libslic3r/Arrange.hpp" #include "3DScene.hpp" #include "GLToolbar.hpp" #include "Event.hpp" diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a6360a0205..1516426139 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1264,21 +1264,18 @@ struct Plater::priv // Cache the wti info class WipeTower: public GLCanvas3D::WipeTowerInfo { - Vec2d m_bed_origin = {0., 0.}; - int m_bed_idx = arrangement::UNARRANGED; + using ArrangePolygon = arrangement::ArrangePolygon; + static const constexpr auto UNARRANGED = arrangement::UNARRANGED; friend priv; public: - void apply_arrange_result(const arrangement::ArrangePolygon& ap, - const Vec2crd& bedc) { - m_bed_origin = unscaled(bedc); - m_pos = unscaled(ap.translation) + m_bed_origin; - m_rotation = ap.rotation; - m_bed_idx = ap.bed_idx; + void apply_arrange_result(const Vec2crd& tr, double rotation) + { + m_pos = unscaled(tr); m_rotation = rotation; apply_wipe_tower(); } - arrangement::ArrangePolygon get_arrange_polygon() const + ArrangePolygon get_arrange_polygon() const { Polygon p({ {coord_t(0), coord_t(0)}, @@ -1288,28 +1285,20 @@ struct Plater::priv {coord_t(0), coord_t(0)}, }); - arrangement::ArrangePolygon ret; + ArrangePolygon ret; ret.poly.contour = std::move(p); - ret.translation = scaled(m_pos) - scaled(m_bed_origin); + ret.translation = scaled(m_pos); ret.rotation = m_rotation; - ret.bed_idx = m_bed_idx; return ret; } - - // For future use - int bed_index() const { return m_bed_idx; } - }; + } wipetower; -private: - WipeTower m_wipetower; - -public: - WipeTower& wipe_tower() { + WipeTower& updated_wipe_tower() { auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); - m_wipetower.m_pos = wti.pos(); - m_wipetower.m_rotation = wti.rotation(); - m_wipetower.m_bb_size = wti.bb_size(); - return m_wipetower; + 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. @@ -1506,26 +1495,31 @@ public: ModelInstance *mi = mo->instances[i]; ArrangePolygon ap = mi->get_arrange_polygon(); ap.priority = 0; - ap.setter = [mi, stride](const ArrangePolygon &p) { - if (p.bed_idx != UNARRANGED) - mi->apply_arrange_result(p, {p.bed_idx * stride, 0}); - }; + ap.bed_idx = ap.translation.x() / stride; + ap.setter = [mi, stride](const ArrangePolygon &p) { + if (p.bed_idx != UNARRANGED) { + auto t = p.translation; t.x() += p.bed_idx * stride; + mi->apply_arrange_result(t, p.rotation); + } + }; + inst_sel[i] ? m_selected.emplace_back(std::move(ap)) : m_unselected.emplace_back(std::move(ap)); } } - auto& wti = plater().wipe_tower(); + auto& wti = plater().updated_wipe_tower(); if (wti) { ArrangePolygon ap = wti.get_arrange_polygon(); + ap.bed_idx = ap.translation.x() / stride; + ap.priority = 1; // Wipe tower should be on physical bed ap.setter = [&wti, stride](const ArrangePolygon &p) { - if (p.bed_idx != UNARRANGED) - wti.apply_arrange_result(p, {p.bed_idx * stride, 0}); + auto t = p.translation; t.x() += p.bed_idx * stride; + wti.apply_arrange_result(t, p.rotation); }; - ap.priority = 1; sel.is_wipe_tower() ? m_selected.emplace_back(std::move(ap)) : m_unselected.emplace_back(std::move(ap)); @@ -1533,6 +1527,11 @@ public: // 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 it bed_idx is ignored and the + // translation is irrelevant. + for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride; } public: @@ -2553,14 +2552,15 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances, movable.emplace_back(std::move(arrpoly)); } - if (wipe_tower()) - fixed.emplace_back(m_wipetower.get_arrange_polygon()); + if (updated_wipe_tower()) + fixed.emplace_back(wipetower.get_arrange_polygon()); arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint()); for (size_t i = 0; i < instances.size(); ++i) if (movable[i].bed_idx == 0) - instances[i]->apply_arrange_result(movable[i]); + instances[i]->apply_arrange_result(movable[i].translation, + movable[i].rotation); } void Plater::priv::ArrangeJob::process() { From f97a61cdcfaabf573feafbb4a0d1a7b36c2bf5be Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 17 Jul 2019 14:13:50 +0200 Subject: [PATCH 331/627] Fixed use of translate macros --- src/slic3r/GUI/GLCanvas3D.cpp | 16 ++++++++-------- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 10 +++++----- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 8 ++++---- src/slic3r/GUI/Selection.cpp | 4 ++-- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 192112ba75..31372659f1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1795,7 +1795,7 @@ std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) void GLCanvas3D::mirror_selection(Axis axis) { m_selection.mirror(axis); - do_mirror("Mirror Object"); + do_mirror(L("Mirror Object")); wxGetApp().obj_manipul()->set_dirty(); } @@ -2950,7 +2950,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging) { m_regenerate_volumes = false; - do_move("Move Object"); + do_move(L("Move Object")); wxGetApp().obj_manipul()->set_dirty(); // Let the plater know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. @@ -3115,7 +3115,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) return; if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(L(snapshot_type))); + wxGetApp().plater()->take_snapshot(_(snapshot_type)); std::set> done; // keeps track of modified instances bool object_moved = false; @@ -3177,7 +3177,7 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) return; if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(L(snapshot_type))); + wxGetApp().plater()->take_snapshot(_(snapshot_type)); std::set> done; // keeps track of modified instances @@ -3237,7 +3237,7 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) return; if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(L(snapshot_type))); + wxGetApp().plater()->take_snapshot(_(snapshot_type)); std::set> done; // keeps track of modified instances @@ -3291,10 +3291,10 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) void GLCanvas3D::do_flatten(const Vec3d& normal, const std::string& snapshot_type) { if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(L(snapshot_type))); + wxGetApp().plater()->take_snapshot(_(snapshot_type)); m_selection.flattening_rotate(normal); - do_rotate(""); // avoid taking another snapshot + do_rotate(L("")); // avoid taking another snapshot } void GLCanvas3D::do_mirror(const std::string& snapshot_type) @@ -3303,7 +3303,7 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) return; if (!snapshot_type.empty()) - wxGetApp().plater()->take_snapshot(_(L(snapshot_type))); + wxGetApp().plater()->take_snapshot(_(snapshot_type)); std::set> done; // keeps track of modified instances diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 1141ad907a..fcb047139c 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -242,7 +242,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - canvas->do_mirror("Set Mirror"); + canvas->do_mirror(L("Set Mirror")); UpdateAndShow(true); }); return sizer; @@ -323,7 +323,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - canvas->do_rotate("Set Rotation"); + canvas->do_rotate(L("Set Rotation")); UpdateAndShow(true); }); @@ -709,7 +709,7 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.start_dragging(); selection.translate(position - m_cache.position, selection.requires_local_axes()); - canvas->do_move("Set Position"); + canvas->do_move(L("Set Position")); m_cache.position = position; m_cache.position_rounded(axis) = DBL_MAX; @@ -740,7 +740,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value) selection.rotate( (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), transformation_type); - canvas->do_rotate("Set Orientation"); + canvas->do_rotate(L("Set Orientation")); m_cache.rotation = rotation; m_cache.rotation_rounded(axis) = DBL_MAX; @@ -805,7 +805,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const selection.start_dragging(); selection.scale(scaling_factor * 0.01, transformation_type); - wxGetApp().plater()->canvas3D()->do_scale("Set Scale"); + wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); } void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any& value) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 622fdb2d8f..df53b40aa9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -551,7 +551,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) if (m_current == Flatten) { // Rotate the object so the normal points downward: - m_parent.do_flatten(get_flattening_normal(), "Gizmo-Place on Face"); + m_parent.do_flatten(get_flattening_normal(), L("Gizmo-Place on Face")); wxGetApp().obj_manipul()->set_dirty(); } @@ -624,17 +624,17 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) case Move: { m_parent.disable_regenerate_volumes(); - m_parent.do_move("Gizmo-Move"); + m_parent.do_move(L("Gizmo-Move")); break; } case Scale: { - m_parent.do_scale("Gizmo-Scale"); + m_parent.do_scale(L("Gizmo-Scale")); break; } case Rotate: { - m_parent.do_rotate("Gizmo-Rotate"); + m_parent.do_rotate(L("Gizmo-Rotate")); break; } default: diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index b960f8369c..b990f28b89 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -816,12 +816,12 @@ void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) // apply scale start_dragging(); scale(s * Vec3d::Ones(), type); - wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot + wxGetApp().plater()->canvas3D()->do_scale(L("")); // avoid storing another snapshot // center selection on print bed start_dragging(); translate(print_volume.center() - get_bounding_box().center()); - wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot + wxGetApp().plater()->canvas3D()->do_move(L("")); // avoid storing another snapshot wxGetApp().obj_manipul()->set_dirty(); } From c74e6513d9358796f98a4d2137c8b665f167e2df Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 17 Jul 2019 14:34:28 +0200 Subject: [PATCH 332/627] Fix for msvc build. --- src/libslic3r/Arrange.cpp | 12 +++++++----- src/libslic3r/MTUtils.hpp | 5 ++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 8086dc3fd6..63398fd0f1 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -108,7 +108,7 @@ double fixed_overfit(const std::tuple& result, const Box &binbb) double score = std::get<0>(result); Box pilebb = std::get<1>(result); Box fullbb = sl::boundingBox(pilebb, binbb); - double diff = fullbb.area() - binbb.area(); + auto diff = double(fullbb.area()) - binbb.area(); if(diff > 0) score += diff; return score; @@ -138,6 +138,11 @@ protected: Box m_pilebb; // The bounding box of the merged pile. ItemGroup m_remaining; // Remaining items (m_items at the beginning) ItemGroup m_items; // The items to be packed + + template> double norm(T val) + { + return double(val) / m_norm; + } // This is "the" object function which is evaluated many times for each // vertex (decimated with the accuracy parameter) of each object. @@ -178,9 +183,6 @@ protected: // Density is the pack density: how big is the arranged pile double density = 0; - const double N = m_norm; - auto norm = [N](double val) { return val / N; }; - // Distinction of cases for the arrangement scene enum e_cases { // This branch is for big items in a mixed (big and small) scene @@ -593,7 +595,7 @@ void arrange(ArrangePolygons & arrangables, for (const ArrangePolygon &fixed: excludes) process_arrangeable(fixed, fixeditems); - for (Item &itm : fixeditems) itm.inflate(-2 * SCALED_EPSILON); + for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON)); // Integer ceiling the min distance from the bed perimeters coord_t md = min_obj_dist - 2 * scaled(0.1 + EPSILON); diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 01f0095bf4..24542558ce 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -281,9 +281,8 @@ using EigenVec = Eigen::Matrix; // Conversion definition from unscaled to floating point scaled template, - class = FloatingOnly> -inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_NOEXCEPT + class = FloatingOnly> +inline SLIC3R_CONSTEXPR FloatingOnly scaled(const Tin &v) SLIC3R_NOEXCEPT { return Tout(v / Tin(SCALING_FACTOR)); } From 75c53a53b653427de63f9b5f24637895c0562a01 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 17 Jul 2019 14:53:02 +0200 Subject: [PATCH 333/627] Refactoring of BedShapePanel and BedShapeDialog --- src/slic3r/GUI/BedShapeDialog.cpp | 28 ++++++++++++++-------------- src/slic3r/GUI/BedShapeDialog.hpp | 14 +++++++------- src/slic3r/GUI/ConfigWizard.cpp | 7 +++---- src/slic3r/GUI/Tab.cpp | 8 ++++---- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 166127b683..98a5178df2 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -16,7 +16,7 @@ namespace Slic3r { namespace GUI { -void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt) +void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt) { SetFont(wxGetApp().normal_font()); m_panel = new BedShapePanel(this); @@ -51,7 +51,7 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) Refresh(); } -void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) +void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt) { auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font()); @@ -113,7 +113,7 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) // right pane with preview canvas m_canvas = new Bed_2D(this); - m_canvas->m_bed_shape = default_pt->values; + m_canvas->m_bed_shape = default_pt.values; // main sizer auto top_sizer = new wxBoxSizer(wxHORIZONTAL); @@ -155,20 +155,20 @@ ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& tit // Deduce the bed shape type(rect, circle, custom) // This routine shall be smart enough if the user messes up // with the list of points in the ini file directly. -void BedShapePanel::set_shape(ConfigOptionPoints* points) +void BedShapePanel::set_shape(const ConfigOptionPoints& points) { - auto polygon = Polygon::new_scale(points->values); + auto polygon = Polygon::new_scale(points.values); // is this a rectangle ? - if (points->size() == 4) { - auto lines = polygon.lines(); + if (points.size() == 4) { + auto lines = polygon.lines(); if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { // okay, it's a rectangle // find origin coordf_t x_min, x_max, y_min, y_max; - x_max = x_min = points->values[0](0); - y_max = y_min = points->values[0](1); - for (auto pt : points->values) + x_max = x_min = points.values[0](0); + y_max = y_min = points.values[0](1); + for (auto pt : points.values) { x_min = std::min(x_min, pt(0)); x_max = std::max(x_max, pt(0)); @@ -219,8 +219,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points) } } - if (points->size() < 3) { - // Invalid polygon.Revert to default bed dimensions. + if (points.size() < 3) { + // Invalid polygon.Revert to default bed dimensions. m_shape_options_book->SetSelection(SHAPE_RECTANGULAR); auto optgroup = m_optgroups[SHAPE_RECTANGULAR]; optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) }); @@ -232,7 +232,7 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points) // This is a custom bed shape, use the polygon provided. m_shape_options_book->SetSelection(SHAPE_CUSTOM); // Copy the polygon to the canvas, make a copy of the array. - m_loaded_bed_shape = points->values; + m_loaded_bed_shape = points.values; update_shape(); } @@ -324,7 +324,7 @@ void BedShapePanel::load_stl() try { model = Model::read_from_file(file_name); } - catch (std::exception &e) { + catch (std::exception &) { show_error(this, _(L("Error! Invalid model"))); return; } diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index d2c67a7c72..81d47320da 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -23,15 +23,15 @@ public: BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY) {} ~BedShapePanel() {} - void build_panel(ConfigOptionPoints* default_pt); - + void build_panel(const ConfigOptionPoints& default_pt); + // Returns the resulting bed shape polygon. This value will be stored to the ini file. - std::vector GetValue() { return m_canvas->m_bed_shape; } + std::vector get_bed_shape() { return m_canvas->m_bed_shape; } private: ConfigOptionsGroupShp init_shape_options_page(const wxString& title); - void set_shape(ConfigOptionPoints* points); - void update_preview(); + void set_shape(const ConfigOptionPoints& points); + void update_preview(); void update_shape(); void load_stl(); @@ -49,8 +49,8 @@ public: wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {} ~BedShapeDialog() {} - void build_dialog(ConfigOptionPoints* default_pt); - std::vector GetValue() { return m_panel->GetValue(); } + void build_dialog(const ConfigOptionPoints& default_pt); + std::vector get_bed_shape() { return m_panel->get_bed_shape(); } protected: void on_dpi_changed(const wxRect &suggested_rect) override; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 8b08f6f7fa..59ba936774 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -532,15 +532,14 @@ PageBedShape::PageBedShape(ConfigWizard *parent) { append_text(_(L("Set the shape of your printer's bed."))); - shape_panel->build_panel(wizard_p()->custom_config->option("bed_shape")); + shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape")); append(shape_panel); } void PageBedShape::apply_custom_config(DynamicPrintConfig &config) { - const auto points(shape_panel->GetValue()); - auto *opt = new ConfigOptionPoints(points); - config.set_key_value("bed_shape", opt); + const auto points(shape_panel->get_bed_shape()); + config.set_key_value("bed_shape", new ConfigOptionPoints(points)); } PageDiameters::PageDiameters(ConfigWizard *parent) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 7ab564beb0..a307362203 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1856,9 +1856,9 @@ void TabPrinter::build_fff() btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { BedShapeDialog dlg(this); - dlg.build_dialog(m_config->option("bed_shape")); + dlg.build_dialog(*m_config->option("bed_shape")); if (dlg.ShowModal() == wxID_OK) { - std::vector shape = dlg.GetValue(); + std::vector shape = dlg.get_bed_shape(); if (!shape.empty()) { load_key_value("bed_shape", shape); @@ -2062,9 +2062,9 @@ void TabPrinter::build_sla() btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { BedShapeDialog dlg(this); - dlg.build_dialog(m_config->option("bed_shape")); + dlg.build_dialog(*m_config->option("bed_shape")); if (dlg.ShowModal() == wxID_OK) { - std::vector shape = dlg.GetValue(); + std::vector shape = dlg.get_bed_shape(); if (!shape.empty()) { load_key_value("bed_shape", shape); From 81dde630ea6d4ed353f0e01b19ab9996b4549edc Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 17 Jul 2019 15:38:19 +0200 Subject: [PATCH 334/627] SLA support points edits are now pushed onto undo/redo stack --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 142 +++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 13 ++ 2 files changed, 110 insertions(+), 45 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 3e6530d30f..833ba3ee24 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -106,6 +106,9 @@ void GLGizmoSlaSupports::on_render() const return; } + if (! m_its || ! m_mesh) + const_cast(this)->update_mesh(); + glsafe(::glEnable(GL_BLEND)); glsafe(::glEnable(GL_DEPTH_TEST)); @@ -387,18 +390,26 @@ bool GLGizmoSlaSupports::is_mesh_update_necessary() const void GLGizmoSlaSupports::update_mesh() { + if (! m_model_object) + return; + wxBusyCursor wait; // this way we can use that mesh directly. // This mesh does not account for the possible Z up SLA offset. m_mesh = &m_model_object->volumes.front()->mesh(); m_its = &m_mesh->its; + + // If this is different mesh than last time or if the AABB tree is uninitialized, recalculate it. + if (m_current_mesh_object_id != m_model_object->id() || (m_AABB.m_left == NULL && m_AABB.m_right == NULL)) + { + m_AABB.deinit(); + m_AABB.init( + MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), + MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3)); + } + m_current_mesh_object_id = m_model_object->id(); m_editing_mode = false; - - m_AABB.deinit(); - m_AABB.init( - MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), - MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3)); } // Unprojects the mouse position on the mesh and return the hit point and normal of the facet. @@ -1034,48 +1045,67 @@ std::string GLGizmoSlaSupports::on_get_name() const void GLGizmoSlaSupports::on_set_state() { - if (m_state == On && m_old_state != On) { // the gizmo was just turned on - if (is_mesh_update_necessary()) - update_mesh(); - - // we'll now reload support points: - if (m_model_object) - editing_mode_reload_cache(); - - m_parent.toggle_model_objects_visibility(false); - if (m_model_object) - m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); - - // Set default head diameter from config. - const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; + // m_model_object pointer can be invalid (for instance because of undo/redo action), + // we should recover it from the object id + const ModelObject* old_model_object = m_model_object; + m_model_object = nullptr; + for (const auto mo : *wxGetApp().model_objects()) { + if (mo->id() == m_current_mesh_object_id) { + m_model_object = mo; + break; } - if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - wxGetApp().CallAfter([this]() { - // Following is called through CallAfter, because otherwise there was a problem - // on OSX with the wxMessageDialog being shown several times when clicked into. - if (m_model_object) { - if (m_unsaved_changes) { - wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points?")) + "\n", - _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); - if (dlg.ShowModal() == wxID_YES) - editing_mode_apply_changes(); - else - editing_mode_discard_changes(); - } + } + + // If ModelObject pointer really changed, invalidate mesh and do everything + // as if the gizmo was switched from Off state + if (m_model_object == nullptr || old_model_object != m_model_object) { + m_mesh = nullptr; + m_its = nullptr; + m_old_state = Off; + } + + if (m_state == On && m_old_state != On) { // the gizmo was just turned on + if (is_mesh_update_necessary()) + update_mesh(); + + // we'll now reload support points: + if (m_model_object) + editing_mode_reload_cache(); + + m_parent.toggle_model_objects_visibility(false); + if (m_model_object) + m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); + + // Set default head diameter from config. + const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; + } + if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off + wxGetApp().CallAfter([this]() { + // Following is called through CallAfter, because otherwise there was a problem + // on OSX with the wxMessageDialog being shown several times when clicked into. + if (m_model_object) { + if (m_unsaved_changes) { + wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points?")) + "\n", + _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) + editing_mode_apply_changes(); + else + editing_mode_discard_changes(); } - m_parent.toggle_model_objects_visibility(true); - m_editing_mode = false; // so it is not active next time the gizmo opens - m_editing_mode_cache.clear(); - m_clipping_plane_distance = 0.f; - // Release triangle mesh slicer and the AABB spatial search structure. - m_AABB.deinit(); - m_its = nullptr; - m_tms.reset(); - m_supports_tms.reset(); - }); - } - m_old_state = m_state; + } + m_parent.toggle_model_objects_visibility(true); + m_editing_mode = false; // so it is not active next time the gizmo opens + m_editing_mode_cache.clear(); + m_clipping_plane_distance = 0.f; + // Release triangle mesh slicer and the AABB spatial search structure. + m_AABB.deinit(); + m_its = nullptr; + m_tms.reset(); + m_supports_tms.reset(); + }); + } + m_old_state = m_state; } @@ -1090,6 +1120,26 @@ void GLGizmoSlaSupports::on_start_dragging() +void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) +{ + ar(m_clipping_plane_distance, + m_clipping_plane_normal, + m_current_mesh_object_id + ); +} + + + +void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const +{ + ar(m_clipping_plane_distance, + m_clipping_plane_normal, + m_current_mesh_object_id + ); +} + + + void GLGizmoSlaSupports::select_point(int i) { if (i == AllPoints || i == NoPoints) { @@ -1146,6 +1196,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() // If there are no changes, don't touch the front-end. The data in the cache could have been // taken from the backend and copying them to ModelObject would needlessly invalidate them. if (m_unsaved_changes) { + wxGetApp().plater()->take_snapshot(_(L("Support points edit"))); m_model_object->sla_points_status = sla::PointsStatus::UserModified; m_model_object->sla_support_points.clear(); for (const CacheEntry& cache_entry : m_editing_mode_cache) @@ -1204,6 +1255,7 @@ void GLGizmoSlaSupports::auto_generate() )), _(L("Warning")), wxICON_WARNING | wxYES | wxNO); if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_editing_mode_cache.empty() || dlg.ShowModal() == wxID_YES) { + wxGetApp().plater()->take_snapshot(_(L("Autogenerate support points"))); m_model_object->sla_support_points.clear(); m_model_object->sla_points_status = sla::PointsStatus::Generating; m_editing_mode_cache.clear(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index b0a505b823..fbc438f1d6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -14,6 +14,8 @@ #include "libslic3r/SLAPrint.hpp" #include +#include + namespace Slic3r { namespace GUI { @@ -49,12 +51,21 @@ private: class CacheEntry { public: + CacheEntry() : + support_point(sla::SupportPoint()), selected(false), normal(Vec3f::Zero()) {} + CacheEntry(const sla::SupportPoint& point, bool sel, const Vec3f& norm = Vec3f::Zero()) : support_point(point), selected(sel), normal(norm) {} sla::SupportPoint support_point; bool selected; // whether the point is selected Vec3f normal; + + template + void serialize(Archive & ar) + { + ar(support_point, selected, normal); + } }; public: @@ -139,6 +150,8 @@ protected: virtual std::string on_get_name() const; virtual bool on_is_activable() const; virtual bool on_is_selectable() const; + virtual void on_load(cereal::BinaryInputArchive& ar) override; + virtual void on_save(cereal::BinaryOutputArchive& ar) const override; }; From 2b9d285a16302fd3555a3ead6293baa3d3b4c769 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 17 Jul 2019 15:39:10 +0200 Subject: [PATCH 335/627] 'Place on face' gizmo fix (it used invalid pointer after undo/redo was implemented) --- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 18 ++++++++++++++++++ src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp | 7 ++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 78f5da58b9..cb996a1041 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -1,6 +1,7 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoFlatten.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" #include @@ -23,6 +24,22 @@ bool GLGizmoFlatten::on_init() return true; } +void GLGizmoFlatten::on_set_state() +{ + // m_model_object pointer can be invalid (for instance because of undo/redo action), + // we should recover it from the object id + m_model_object = nullptr; + for (const auto mo : *wxGetApp().model_objects()) { + if (mo->id() == m_model_object_id) { + m_model_object = mo; + break; + } + } + + if (m_state == On && is_plane_update_necessary()) + update_planes(); +} + std::string GLGizmoFlatten::on_get_name() const { return (_(L("Place on face")) + " [F]").ToUTF8().data(); @@ -120,6 +137,7 @@ void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) m_planes_valid = false; } m_model_object = model_object; + m_model_object_id = model_object ? model_object->id() : 0; } void GLGizmoFlatten::update_planes() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp index c8bd056bc1..c69d641343 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -31,6 +31,7 @@ private: bool m_planes_valid = false; mutable Vec3d m_starting_center; const ModelObject* m_model_object = nullptr; + ObjectID m_model_object_id = 0; std::vector instances_matrices; void update_planes(); @@ -49,11 +50,7 @@ protected: virtual void on_start_dragging(); virtual void on_render() const; virtual void on_render_for_picking() const; - virtual void on_set_state() - { - if (m_state == On && is_plane_update_necessary()) - update_planes(); - } + virtual void on_set_state() override; }; } // namespace GUI From 3a74e7ab69fab1080f4245bfbf972cfe0b851129 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 17 Jul 2019 15:48:53 +0200 Subject: [PATCH 336/627] WIP: Undo / Redo memory limiting by releasing the least recently used snapshots. Memory limit set to 10% of physical system memory. --- src/admesh/stl.h | 8 ++ src/libslic3r/TriangleMesh.cpp | 6 + src/libslic3r/TriangleMesh.hpp | 2 + src/libslic3r/Utils.hpp | 2 + src/libslic3r/utils.cpp | 82 +++++++++++- src/slic3r/GUI/GUI_App.cpp | 4 +- src/slic3r/GUI/GUI_App.hpp | 4 +- src/slic3r/GUI/GUI_ObjectList.cpp | 16 +-- src/slic3r/GUI/Plater.cpp | 112 ++++++++++------ src/slic3r/GUI/Plater.hpp | 38 +++++- src/slic3r/Utils/UndoRedo.cpp | 207 ++++++++++++++++++++++++++---- src/slic3r/Utils/UndoRedo.hpp | 12 ++ 12 files changed, 411 insertions(+), 82 deletions(-) diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 2ac6c7fd28..9b1146f8da 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -127,6 +127,10 @@ struct stl_file { this->stats.reset(); } + size_t memsize() const { + return sizeof(*this) + sizeof(stl_facet) * facet_start.size() + sizeof(stl_neighbors) * neighbors_start.size(); + } + std::vector facet_start; std::vector neighbors_start; // Statistics @@ -139,6 +143,10 @@ struct indexed_triangle_set void clear() { indices.clear(); vertices.clear(); } + size_t memsize() const { + return sizeof(*this) + sizeof(stl_triangle_vertex_indices) * indices.size() + sizeof(stl_vertex) * vertices.size(); + } + std::vector indices; std::vector vertices; //FIXME add normals once we get rid of the stl_file from TriangleMesh completely. diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index fbfff90fb6..d782bc5ace 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -607,6 +607,12 @@ void TriangleMesh::require_shared_vertices() BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; } +size_t TriangleMesh::memsize() const +{ + size_t memsize = 8 + this->stl.memsize() + this->its.memsize(); + return memsize; +} + void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel) { mesh = _mesh; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 5dd2597a5c..d2b6886387 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -67,6 +67,8 @@ public: size_t facets_count() const { return this->stl.stats.number_of_facets; } bool empty() const { return this->facets_count() == 0; } bool is_splittable() const; + // Estimate of the memory occupied by this structure. + size_t memsize() const; stl_file stl; indexed_triangle_set its; diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 3b30e981cb..09e24a475a 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -21,6 +21,8 @@ extern std::string format_memsize_MB(size_t n); // The string is non-empty only if the loglevel >= info (3). extern std::string log_memory_info(); extern void disable_multi_threading(); +// Returns the size of physical memory (RAM) in bytes. +extern size_t total_physical_memory(); // Set a path with GUI resource files. void set_var_dir(const std::string &path); diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 5197637310..f9a0443380 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -7,10 +7,15 @@ #include #ifdef WIN32 -#include -#include + #include + #include #else -#include + #include + #include + #include + #ifdef BSD + #include + #endif #endif #include @@ -467,4 +472,75 @@ std::string log_memory_info() } #endif +// Returns the size of physical memory (RAM) in bytes. +// http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system +size_t total_physical_memory() +{ +#if defined(_WIN32) && (defined(__CYGWIN__) || defined(__CYGWIN32__)) + // Cygwin under Windows. ------------------------------------ + // New 64-bit MEMORYSTATUSEX isn't available. Use old 32.bit + MEMORYSTATUS status; + status.dwLength = sizeof(status); + GlobalMemoryStatus( &status ); + return (size_t)status.dwTotalPhys; +#elif defined(_WIN32) + // Windows. ------------------------------------------------- + // Use new 64-bit MEMORYSTATUSEX, not old 32-bit MEMORYSTATUS + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx( &status ); + return (size_t)status.ullTotalPhys; +#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__)) + // UNIX variants. ------------------------------------------- + // Prefer sysctl() over sysconf() except sysctl() HW_REALMEM and HW_PHYSMEM + +#if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64)) + int mib[2]; + mib[0] = CTL_HW; +#if defined(HW_MEMSIZE) + mib[1] = HW_MEMSIZE; // OSX. --------------------- +#elif defined(HW_PHYSMEM64) + mib[1] = HW_PHYSMEM64; // NetBSD, OpenBSD. --------- +#endif + int64_t size = 0; // 64-bit + size_t len = sizeof( size ); + if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 ) + return (size_t)size; + return 0L; // Failed? + +#elif defined(_SC_AIX_REALMEM) + // AIX. ----------------------------------------------------- + return (size_t)sysconf( _SC_AIX_REALMEM ) * (size_t)1024L; + +#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE) + // FreeBSD, Linux, OpenBSD, and Solaris. -------------------- + return (size_t)sysconf( _SC_PHYS_PAGES ) * + (size_t)sysconf( _SC_PAGESIZE ); + +#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGE_SIZE) + // Legacy. -------------------------------------------------- + return (size_t)sysconf( _SC_PHYS_PAGES ) * + (size_t)sysconf( _SC_PAGE_SIZE ); + +#elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM)) + // DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. -------- + int mib[2]; + mib[0] = CTL_HW; +#if defined(HW_REALMEM) + mib[1] = HW_REALMEM; // FreeBSD. ----------------- +#elif defined(HW_PYSMEM) + mib[1] = HW_PHYSMEM; // Others. ------------------ +#endif + unsigned int size = 0; // 32-bit + size_t len = sizeof( size ); + if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 ) + return (size_t)size; + return 0L; // Failed? +#endif // sysctl and sysconf variants + +#else + return 0L; // Unknown OS. +#endif +} + }; // namespace Slic3r diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 8a376c3a37..b4e7bc2b2a 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -534,7 +534,7 @@ void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_max }); } -void GUI_App::load_project(wxWindow *parent, wxString& input_file) +void GUI_App::load_project(wxWindow *parent, wxString& input_file) const { input_file.Clear(); wxFileDialog dialog(parent ? parent : GetTopWindow(), @@ -546,7 +546,7 @@ void GUI_App::load_project(wxWindow *parent, wxString& input_file) input_file = dialog.GetPath(); } -void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) +void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const { input_files.Clear(); wxFileDialog dialog(parent ? parent : GetTopWindow(), diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index e69503ff8a..2f0e8bcf3b 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -121,8 +121,8 @@ public: void recreate_GUI(); void system_info(); void keyboard_shortcuts(); - void load_project(wxWindow *parent, wxString& input_file); - void import_model(wxWindow *parent, wxArrayString& input_files); + void load_project(wxWindow *parent, wxString& input_file) const; + void import_model(wxWindow *parent, wxArrayString& input_files) const; static bool catch_error(std::function cb, const std::string& err); void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 9e681257ca..3f3256c420 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -71,9 +71,6 @@ static void take_snapshot(const wxString& snapshot_name) wxGetApp().plater()->take_snapshot(snapshot_name); } -static void suppress_snapshots(){ wxGetApp().plater()->suppress_snapshots(); } -static void allow_snapshots() { wxGetApp().plater()->allow_snapshots(); } - ObjectList::ObjectList(wxWindow* parent) : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE), m_parent(parent) @@ -2406,8 +2403,7 @@ void ObjectList::remove() wxDataViewItem parent = wxDataViewItem(0); - take_snapshot(_(L("Delete Selected"))); - suppress_snapshots(); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete Selected"))); for (auto& item : sels) { @@ -2429,8 +2425,6 @@ void ObjectList::remove() if (parent) select_item(parent); - - allow_snapshots(); } void ObjectList::del_layer_range(const t_layer_height_range& range) @@ -2505,8 +2499,7 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre t_layer_height_range new_range = { midl_layer, next_range.second }; - take_snapshot(_(L("Add New Layers Range"))); - suppress_snapshots(); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add New Layers Range"))); // create new 2 layers instead of deleted one @@ -2521,7 +2514,6 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre new_range = { current_range.second, midl_layer }; ranges[new_range] = get_default_layer_config(obj_idx); add_layer_item(new_range, layers_item, layer_idx); - allow_snapshots(); } else { @@ -3531,7 +3523,7 @@ void ObjectList::update_after_undo_redo() m_prevent_list_events = true; m_prevent_canvas_selection_update = true; - suppress_snapshots(); + Plater::SuppressSnapshots suppress(wxGetApp().plater()); // Unselect all objects before deleting them, so that no change of selection is emitted during deletion. this->UnselectAll(); @@ -3543,8 +3535,6 @@ void ObjectList::update_after_undo_redo() ++obj_idx; } - allow_snapshots(); - #ifndef __WXOSX__ selection_changed(); #endif /* __WXOSX__ */ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f5f2454717..11055af11a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1255,13 +1255,9 @@ const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames) { - plater->take_snapshot(_(L("Load Files"))); - std::vector paths; - for (const auto &filename : filenames) { fs::path path(into_path(filename)); - if (std::regex_match(path.string(), pattern_drop)) { paths.push_back(std::move(path)); } else { @@ -1269,6 +1265,23 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi } } + wxString snapshot_label; + assert(! paths.empty()); + if (paths.size() == 1) { + snapshot_label = _(L("Load File")); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + } else { + snapshot_label = _(L("Load Files")); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + for (size_t i = 1; i < paths.size(); ++ i) { + snapshot_label += ", "; + snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); + } + } + Plater::TakeSnapshot snapshot(plater, snapshot_label); + // FIXME: when drag and drop is done on a .3mf or a .amf file we should clear the plater for consistence with the open project command // (the following call to plater->load_files() will load the config data, if present) @@ -1595,7 +1608,7 @@ struct Plater::priv priv(Plater *q, MainFrame *main_frame); ~priv(); - void update(bool force_full_scene_refresh = false); + void update(bool force_full_scene_refresh = false, bool force_background_processing_update = false); void select_view(const std::string& direction); void select_view_3D(const std::string& name); void select_next_view_3D(); @@ -1634,6 +1647,8 @@ struct Plater::priv return; assert(this->m_prevent_snapshots >= 0); this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection()); + this->undo_redo_stack.release_least_recently_used(); + BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info(); } void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } int get_active_snapshot_index(); @@ -1734,7 +1749,7 @@ private: void update_fff_scene(); void update_sla_scene(); - void update_after_undo_redo(); + void update_after_undo_redo(bool temp_snapshot_was_taken = false); // path to project file stored with no extension wxString m_project_filename; @@ -1888,7 +1903,7 @@ Plater::priv::~priv() delete config; } -void Plater::priv::update(bool force_full_scene_refresh) +void Plater::priv::update(bool force_full_scene_refresh, bool force_background_processing_update) { // the following line, when enabled, causes flickering on NVIDIA graphics cards // wxWindowUpdateLocker freeze_guard(q); @@ -1901,7 +1916,7 @@ void Plater::priv::update(bool force_full_scene_refresh) } unsigned int update_status = 0; - if (this->printer_technology == ptSLA) + if (this->printer_technology == ptSLA || force_background_processing_update) // Update the SLAPrint from the current Model, so that the reload_scene() // pulls the correct data. update_status = this->update_background_process(false); @@ -2459,7 +2474,10 @@ void Plater::priv::remove(size_t obj_idx) void Plater::priv::delete_object_from_model(size_t obj_idx) { - this->take_snapshot(_(L("Delete Object"))); + wxString snapshot_label = _(L("Delete Object")); + if (! model.objects[obj_idx]->name.empty()) + snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str()); + Plater::TakeSnapshot snapshot(q, snapshot_label); model.delete_object(obj_idx); update(); object_list_changed(); @@ -2467,7 +2485,7 @@ void Plater::priv::delete_object_from_model(size_t obj_idx) void Plater::priv::reset() { - this->take_snapshot(_(L("Reset Project"))); + Plater::TakeSnapshot snapshot(q, _(L("Reset Project"))); set_project_filename(wxEmptyString); @@ -2663,7 +2681,7 @@ void Plater::priv::split_object() Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part."))); else { - this->take_snapshot(_(L("Split to Objects"))); + Plater::TakeSnapshot snapshot(q, _(L("Split to Objects"))); unsigned int counter = 1; for (ModelObject* m : new_objects) @@ -2903,7 +2921,7 @@ void Plater::priv::update_sla_scene() void Plater::priv::reload_from_disk() { - this->take_snapshot(_(L("Reload from Disk"))); + Plater::TakeSnapshot snapshot(q, _(L("Reload from Disk"))); const auto &selection = get_selection(); const auto obj_orig_idx = selection.get_object_idx(); @@ -2939,7 +2957,7 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = if (obj_idx < 0) return; - this->take_snapshot(_(L("Fix Throught NetFabb"))); + Plater::TakeSnapshot snapshot(q, _(L("Fix Throught NetFabb"))); fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx); this->update(); @@ -3603,7 +3621,7 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const int Plater::priv::get_active_snapshot_index() { - const size_t& active_snapshot_time = this->undo_redo_stack.active_snapshot_time(); + const size_t active_snapshot_time = this->undo_redo_stack.active_snapshot_time(); const std::vector& ss_stack = this->undo_redo_stack.snapshots(); const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time)); return it - ss_stack.begin(); @@ -3611,8 +3629,9 @@ int Plater::priv::get_active_snapshot_index() void Plater::priv::undo() { + bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active(); if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection())) - this->update_after_undo_redo(); + this->update_after_undo_redo(temp_snapshot_was_taken); } void Plater::priv::redo() @@ -3623,8 +3642,9 @@ void Plater::priv::redo() void Plater::priv::undo_to(size_t time_to_load) { + bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active(); if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), time_to_load)) - this->update_after_undo_redo(); + this->update_after_undo_redo(temp_snapshot_was_taken); } void Plater::priv::redo_to(size_t time_to_load) @@ -3633,10 +3653,16 @@ void Plater::priv::redo_to(size_t time_to_load) this->update_after_undo_redo(); } -void Plater::priv::update_after_undo_redo() +void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */) { this->view3D->get_canvas3d()->get_selection().clear(); - this->update(false); // update volumes from the deserializd model + // Update volumes from the deserializd model, always stop / update the background processing (for both the SLA and FFF technologies). + this->update(false, true); + // Release old snapshots if the memory allocated is excessive. This may remove the top most snapshot if jumping to the very first snapshot. + //if (temp_snapshot_was_taken) + // Release the old snapshots always, as it may have happened, that some of the triangle meshes got deserialized from the snapshot, while some + // triangle meshes may have gotten released from the scene or the background processing, therefore now being calculated into the Undo / Redo stack size. + this->undo_redo_stack.release_least_recently_used(); //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time) this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances); @@ -3644,6 +3670,8 @@ void Plater::priv::update_after_undo_redo() //FIXME what about the state of the manipulators? //FIXME what about the focus? Cursor in the side panel? + + BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info(); } void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const @@ -3683,10 +3711,12 @@ void Plater::new_project() void Plater::load_project() { - this->take_snapshot(_(L("Load Project"))); - + // Ask user for a project file name. wxString input_file; wxGetApp().load_project(this, input_file); + // Take the Undo / Redo snapshot. + Plater::TakeSnapshot snapshot(this, _(L("Load Project")) + ": " + wxString::FromUTF8(into_path(input_file).stem().string().c_str())); + // And finally load the new project. load_project(input_file); } @@ -3710,13 +3740,28 @@ void Plater::add_model() if (input_files.empty()) return; - this->take_snapshot(_(L("Add object(s)"))); + std::vector paths; + for (const auto &file : input_files) + paths.push_back(into_path(file)); - std::vector input_paths; - for (const auto &file : input_files) { - input_paths.push_back(into_path(file)); - } - load_files(input_paths, true, false); + wxString snapshot_label; + assert(! paths.empty()); + if (paths.size() == 1) { + snapshot_label = _(L("Import Object")); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + } else { + snapshot_label = _(L("Import Objects")); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + for (size_t i = 1; i < paths.size(); ++ i) { + snapshot_label += ", "; + snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); + } + } + + Plater::TakeSnapshot snapshot(this, snapshot_label); + load_files(paths, true, false); } void Plater::extract_config_from_project() @@ -3769,17 +3814,15 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo void Plater::remove_selected() { - this->take_snapshot(_(L("Delete Selected Objects"))); - this->suppress_snapshots(); + Plater::TakeSnapshot snapshot(this, _(L("Delete Selected Objects"))); this->p->view3D->delete_selected(); - this->allow_snapshots(); } void Plater::increase_instances(size_t num) { if (! can_increase_instances()) { return; } - this->take_snapshot(_(L("Increase Instances"))); + Plater::TakeSnapshot snapshot(this, _(L("Increase Instances"))); int obj_idx = p->get_selected_object_idx(); @@ -3815,7 +3858,7 @@ void Plater::decrease_instances(size_t num) { if (! can_decrease_instances()) { return; } - this->take_snapshot(_(L("Decrease Instances"))); + Plater::TakeSnapshot snapshot(this, _(L("Decrease Instances"))); int obj_idx = p->get_selected_object_idx(); @@ -3851,16 +3894,13 @@ void Plater::set_number_of_copies(/*size_t num*/) if (num < 0) return; - this->take_snapshot(wxString::Format(_(L("Set numbers of copies to %d")), num)); - this->suppress_snapshots(); + Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num)); int diff = (int)num - (int)model_object->instances.size(); if (diff > 0) increase_instances(diff); else if (diff < 0) decrease_instances(-diff); - - this->allow_snapshots(); } bool Plater::is_selection_empty() const @@ -3884,7 +3924,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe return; } - this->take_snapshot(_(L("Cut"))); + Plater::TakeSnapshot snapshot(this, _(L("Cut"))); wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index e73f88ef35..a4ced64a08 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -186,8 +186,6 @@ public: void take_snapshot(const std::string &snapshot_name); void take_snapshot(const wxString &snapshot_name); - void suppress_snapshots(); - void allow_snapshots(); void undo(); void redo(); void undo_to(int selection); @@ -235,10 +233,46 @@ public: const Camera& get_camera() const; + // ROII wrapper for suppressing the Undo / Redo snapshot to be taken. + class SuppressSnapshots + { + public: + SuppressSnapshots(Plater *plater) : m_plater(plater) + { + m_plater->suppress_snapshots(); + } + ~SuppressSnapshots() + { + m_plater->allow_snapshots(); + } + private: + Plater *m_plater; + }; + + // ROII wrapper for taking an Undo / Redo snapshot while disabling the snapshot taking by the methods called from inside this snapshot. + class TakeSnapshot + { + public: + TakeSnapshot(Plater *plater, const wxString &snapshot_name) : m_plater(plater) + { + m_plater->take_snapshot(snapshot_name); + m_plater->suppress_snapshots(); + } + ~TakeSnapshot() + { + m_plater->allow_snapshots(); + } + private: + Plater *m_plater; + }; + private: struct priv; std::unique_ptr p; + void suppress_snapshots(); + void allow_snapshots(); + friend class SuppressBackgroundProcessingUpdate; }; diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 058062502d..6910fa87b4 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -18,6 +18,7 @@ #include #include +#include #include @@ -28,11 +29,11 @@ namespace Slic3r { namespace UndoRedo { -static std::string topmost_snapsnot_name = "@@@ Topmost @@@"; +static std::string topmost_snapshot_name = "@@@ Topmost @@@"; bool Snapshot::is_topmost() const { - return this->name == topmost_snapsnot_name; + return this->name == topmost_snapshot_name; } // Time interval, start is closed, end is open. @@ -53,9 +54,12 @@ public: bool operator<(const Interval &rhs) const { return (m_begin < rhs.m_begin) || (m_begin == rhs.m_begin && m_end < rhs.m_end); } bool operator==(const Interval &rhs) const { return m_begin == rhs.m_begin && m_end == rhs.m_end; } + void trim_begin(size_t new_begin) { m_begin = std::max(m_begin, new_begin); } void trim_end(size_t new_end) { m_end = std::min(m_end, new_end); } void extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; } + size_t memsize() const { return sizeof(this); } + private: size_t m_begin; size_t m_end; @@ -75,8 +79,15 @@ public: // If the history is empty, the ObjectHistory object could be released. virtual bool empty() = 0; + // Release all data before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. + // Return the amount of memory released. + virtual size_t release_before_timestamp(size_t timestamp) = 0; // Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. - virtual void release_after_timestamp(size_t timestamp) = 0; + // Return the amount of memory released. + virtual size_t release_after_timestamp(size_t timestamp) = 0; + + // Estimated size in memory, to be used to drop least recently used snapshots. + virtual size_t memsize() const = 0; #ifdef SLIC3R_UNDOREDO_DEBUG // Human readable debug information. @@ -96,21 +107,54 @@ public: // If the history is empty, the ObjectHistory object could be released. bool empty() override { return m_history.empty(); } - // Release all data after the given timestamp. The shared pointer is NOT released. - void release_after_timestamp(size_t timestamp) override { - assert(! m_history.empty()); - assert(this->valid()); - // it points to an interval which either starts with timestamp, or follows the timestamp. - auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp)); - if (it != m_history.begin()) { - auto it_prev = it; - -- it_prev; - assert(it_prev->begin() < timestamp); - // Trim the last interval with timestamp. - it_prev->trim_end(timestamp); + // Release all data before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. + size_t release_before_timestamp(size_t timestamp) override { + size_t mem_released = 0; + if (! m_history.empty()) { + assert(this->valid()); + // it points to an interval which either starts with timestamp, or follows the timestamp. + auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp)); + // Find the first iterator with begin() < timestamp. + if (it == m_history.end()) + -- it; + while (it != m_history.begin() && it->begin() >= timestamp) + -- it; + if (it->begin() < timestamp && it->end() > timestamp) { + it->trim_begin(timestamp); + if (it != m_history.begin()) + -- it; + } + if (it->end() <= timestamp) { + auto it_end = ++ it; + for (it = m_history.begin(); it != it_end; ++ it) + mem_released += it->memsize(); + m_history.erase(m_history.begin(), it_end); + } + assert(this->valid()); } - m_history.erase(it, m_history.end()); - assert(this->valid()); + return mem_released; + } + + // Release all data after the given timestamp. The shared pointer is NOT released. + size_t release_after_timestamp(size_t timestamp) override { + size_t mem_released = 0; + if (! m_history.empty()) { + assert(this->valid()); + // it points to an interval which either starts with timestamp, or follows the timestamp. + auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp)); + if (it != m_history.begin()) { + auto it_prev = it; + -- it_prev; + assert(it_prev->begin() < timestamp); + // Trim the last interval with timestamp. + it_prev->trim_end(timestamp); + } + for (auto it2 = it; it2 != m_history.end(); ++ it2) + mem_released += it2->memsize(); + m_history.erase(it, m_history.end()); + assert(this->valid()); + } + return mem_released; } protected: @@ -138,6 +182,18 @@ public: bool is_immutable() const override { return true; } const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); } + // Estimated size in memory, to be used to drop least recently used snapshots. + size_t memsize() const override { + size_t memsize = sizeof(*this); + if (this->is_serialized()) + memsize += m_serialized.size(); + else if (m_shared_object.use_count() == 1) + // Only count the shared object's memsize into the total Undo / Redo stack memsize if it is referenced from the Undo / Redo stack only. + memsize += m_shared_object->memsize(); + memsize += m_history.size() * sizeof(Interval); + return memsize; + } + void save(size_t active_snapshot_time, size_t current_time) { assert(m_history.empty() || m_history.back().end() <= active_snapshot_time || // The snapshot of an immutable object may have already been taken from another mutable object. @@ -230,7 +286,8 @@ public: const Interval& interval() const { return m_interval; } size_t begin() const { return m_interval.begin(); } size_t end() const { return m_interval.end(); } - void trim_end(size_t timestamp) { m_interval.trim_end(timestamp); } + void trim_begin(size_t timestamp) { m_interval.trim_begin(timestamp); } + void trim_end (size_t timestamp) { m_interval.trim_end(timestamp); } void extend_end(size_t timestamp) { m_interval.extend_end(timestamp); } bool operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; } @@ -240,6 +297,13 @@ public: size_t size() const { return m_data->size; } size_t refcnt() const { return m_data->refcnt; } bool matches(const std::string& data) { return m_data->matches(data); } + size_t memsize() const { + return m_data->refcnt == 1 ? + // Count just the size of the snapshot data. + m_data->size : + // Count the size of the snapshot data divided by the number of references, rounded up. + (m_data->size + m_data->refcnt - 1) / m_data->refcnt; + } private: MutableHistoryInterval(const MutableHistoryInterval &rhs); @@ -270,6 +334,15 @@ public: bool is_mutable() const override { return true; } bool is_immutable() const override { return false; } + // Estimated size in memory, to be used to drop least recently used snapshots. + size_t memsize() const override { + size_t memsize = sizeof(*this); + memsize += m_history.size() * sizeof(MutableHistoryInterval); + for (const MutableHistoryInterval &interval : m_history) + memsize += interval.memsize(); + return memsize; + } + void save(size_t active_snapshot_time, size_t current_time, const std::string &data) { assert(m_history.empty() || m_history.back().end() <= active_snapshot_time); if (m_history.empty() || m_history.back().end() < active_snapshot_time) { @@ -356,7 +429,17 @@ class StackImpl { public: // Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning. - StackImpl() : m_active_snapshot_time(0), m_current_time(0) {} + // Initially enable Undo / Redo stack to occupy maximum 10% of the total system physical memory. + StackImpl() : m_memory_limit(std::min(Slic3r::total_physical_memory() / 10, size_t(1 * 16384 * 65536))), m_active_snapshot_time(0), m_current_time(0) {} + + void set_memory_limit(size_t memsize) { m_memory_limit = memsize; } + + size_t memsize() const { + size_t memsize = 0; + for (const auto &object : m_objects) + memsize += object.second->memsize(); + return memsize; + } // The Undo / Redo stack is being initialized with an empty model and an empty selection. // The first snapshot cannot be removed. @@ -370,13 +453,15 @@ public: bool has_redo_snapshot() const; bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t jump_to_time); bool redo(Slic3r::Model &model, size_t jump_to_time); + void release_least_recently_used(); // Snapshot history (names with timestamps). const std::vector& snapshots() const { return m_snapshots; } // Timestamp of the active snapshot. size_t active_snapshot_time() const { return m_active_snapshot_time; } + bool temp_snapshot_active() const { return m_snapshots.back().timestamp == m_active_snapshot_time && ! m_snapshots.back().is_topmost_captured(); } - const Selection& selection_deserialized() const { return m_selection; } + const Selection& selection_deserialized() const { return m_selection; } //protected: template ObjectID save_mutable_object(const T &object); @@ -400,6 +485,7 @@ public: if (m_active_snapshot_time > m_snapshots.back().timestamp) out += ">>>\n"; out += "Current time: " + std::to_string(m_current_time) + "\n"; + out += "Total memory occupied: " + std::to_string(this->memsize()) + "\n"; return out; } void print() const { @@ -412,9 +498,10 @@ public: #ifndef NDEBUG bool valid() const { assert(! m_snapshots.empty()); + assert(m_snapshots.back().is_topmost()); auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time)); assert(it != m_snapshots.begin() && it != m_snapshots.end() && it->timestamp == m_active_snapshot_time); - assert(m_active_snapshot_time < m_snapshots.back().timestamp || m_snapshots.back().is_topmost()); + assert(m_active_snapshot_time <= m_snapshots.back().timestamp); for (auto it = m_objects.begin(); it != m_objects.end(); ++ it) assert(it->second->valid()); return true; @@ -436,6 +523,9 @@ private: } void collect_garbage(); + // Maximum memory allowed to be occupied by the Undo / Redo stack. If the limit is exceeded, + // least recently used snapshots will be released. + size_t m_memory_limit; // Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh) // is stored with its own history, referenced by the ObjectID. Immutable objects do not provide // their own IDs, therefore there are temporary IDs generated for them and stored to m_shared_ptr_to_object_id. @@ -655,7 +745,7 @@ void StackImpl::take_snapshot(const std::string &snapshot_name, const Slic3r::Mo m_active_snapshot_time = m_current_time; // Save snapshot info of the last "current" aka "top most" state, that is only being serialized // if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet. - m_snapshots.emplace_back(topmost_snapsnot_name, m_active_snapshot_time, 0); + m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0); // Release empty objects from the history. this->collect_garbage(); assert(this->valid()); @@ -710,9 +800,10 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti } assert(time_to_load < m_active_snapshot_time); assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load))); + bool new_snapshot_taken = false; if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { // The current state is temporary. The current state needs to be captured to be redoable. - this->take_snapshot(topmost_snapsnot_name, model, selection); + this->take_snapshot(topmost_snapshot_name, model, selection); // The line above entered another topmost_snapshot_name. assert(m_snapshots.back().is_topmost()); assert(! m_snapshots.back().is_topmost_captured()); @@ -722,8 +813,15 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti //-- m_current_time; assert(m_snapshots.back().is_topmost()); assert(m_snapshots.back().is_topmost_captured()); + new_snapshot_taken = true; } this->load_snapshot(time_to_load, model); + if (new_snapshot_taken) { + // Release old snapshots if the memory allocated due to capturing the top most state is excessive. + // Don't release the snapshots here, release them first after the scene and background processing gets updated, as this will release some references + // to the shared TriangleMeshes. + //this->release_least_recently_used(); + } #ifdef SLIC3R_UNDOREDO_DEBUG std::cout << "After undo" << std::endl; this->print(); @@ -764,10 +862,70 @@ void StackImpl::collect_garbage() } } +void StackImpl::release_least_recently_used() +{ + assert(this->valid()); + size_t current_memsize = this->memsize(); +#ifdef SLIC3R_UNDOREDO_DEBUG + bool released = false; +#endif + while (current_memsize > m_memory_limit && m_snapshots.size() >= 3) { + // From which side to remove a snapshot? + assert(m_snapshots.front().timestamp < m_active_snapshot_time); + size_t mem_released = 0; + if (m_snapshots[1].timestamp == m_active_snapshot_time) { + // Remove the last snapshot. + for (auto it = m_objects.begin(); it != m_objects.end();) { + mem_released += it->second->release_after_timestamp(m_snapshots.back().timestamp); + if (it->second->empty()) { + if (it->second->immutable_object_ptr() != nullptr) + // Release the immutable object from the ptr to ObjectID map. + m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr()); + mem_released += it->second->memsize(); + it = m_objects.erase(it); + } else + ++ it; + } + m_snapshots.pop_back(); + m_snapshots.back().name = topmost_snapshot_name; + } else { + // Remove the first snapshot. + for (auto it = m_objects.begin(); it != m_objects.end();) { + mem_released += it->second->release_before_timestamp(m_snapshots[1].timestamp); + if (it->second->empty()) { + if (it->second->immutable_object_ptr() != nullptr) + // Release the immutable object from the ptr to ObjectID map. + m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr()); + mem_released += it->second->memsize(); + it = m_objects.erase(it); + } else + ++ it; + } + m_snapshots.erase(m_snapshots.begin()); + } + assert(current_memsize >= mem_released); + if (current_memsize >= mem_released) + current_memsize -= mem_released; + else + current_memsize = 0; +#ifdef SLIC3R_UNDOREDO_DEBUG + released = true; +#endif + } + assert(this->valid()); +#ifdef SLIC3R_UNDOREDO_DEBUG + std::cout << "After release_least_recently_used" << std::endl; + this->print(); +#endif /* SLIC3R_UNDOREDO_DEBUG */ +} + // Wrappers of the private implementation. Stack::Stack() : pimpl(new StackImpl()) {} Stack::~Stack() {} -void Stack::take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection) { pimpl->take_snapshot(snapshot_name, model, selection); } +void Stack::set_memory_limit(size_t memsize) { pimpl->set_memory_limit(memsize); } +size_t Stack::memsize() const { return pimpl->memsize(); } +void Stack::release_least_recently_used() { pimpl->release_least_recently_used(); } +void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection) { pimpl->take_snapshot(snapshot_name, model, selection); } bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); } bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); } bool Stack::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, size_t time_to_load) { return pimpl->undo(model, selection, time_to_load); } @@ -776,6 +934,7 @@ const Selection& Stack::selection_deserialized() const { return pimpl->selection const std::vector& Stack::snapshots() const { return pimpl->snapshots(); } size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); } +bool Stack::temp_snapshot_active() const { return pimpl->temp_snapshot_active(); } } // namespace UndoRedo } // namespace Slic3r diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index f8bfda08cf..606b8de743 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -55,6 +55,15 @@ public: Stack(); ~Stack(); + // Set maximum memory threshold. If the threshold is exceeded, least recently used snapshots are released. + void set_memory_limit(size_t memsize); + + // Estimate size of the RAM consumed by the Undo / Redo stack. + size_t memsize() const; + + // Release least recently used snapshots up to the memory limit set above. + void release_least_recently_used(); + // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. void take_snapshot(const std::string &snapshot_name, const Slic3r::Model &model, const Slic3r::GUI::Selection &selection); @@ -78,6 +87,9 @@ public: // The snapshot time indicates start of an operation, which is finished at the time of the following snapshot, therefore // the active snapshot is the successive snapshot. The same logic applies to the time_to_load parameter of undo() and redo() operations. size_t active_snapshot_time() const; + // Temporary snapshot is active if the topmost snapshot is active and it has not been captured yet. + // In that case the Undo action will capture the last snapshot. + bool temp_snapshot_active() const; // After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted // into the list of GLVolume pointers once the 3D scene is updated. From cc1338ce6ab60470586626660240667965cb9523 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 17 Jul 2019 16:00:09 +0200 Subject: [PATCH 337/627] Fix after merge --- src/slic3r/Utils/UndoRedo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 3ed7bd05ee..51a8113503 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -803,7 +803,7 @@ bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selecti bool new_snapshot_taken = false; if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { // The current state is temporary. The current state needs to be captured to be redoable. - this->take_snapshot(topmost_snapsnot_name, model, selection, gizmos); + this->take_snapshot(topmost_snapshot_name, model, selection, gizmos); // The line above entered another topmost_snapshot_name. assert(m_snapshots.back().is_topmost()); assert(! m_snapshots.back().is_topmost_captured()); From aff1863aed6450c05712b59847571052cdff8c1e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 17 Jul 2019 16:47:09 +0200 Subject: [PATCH 338/627] Really fix build for msvc --- src/libslic3r/Arrange.cpp | 9 +++++---- src/libslic3r/MTUtils.hpp | 38 ++++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 63398fd0f1..bc3eedb63c 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -46,11 +46,12 @@ template struct NfpImpl namespace Slic3r { -template> -inline SLIC3R_CONSTEXPR EigenVec unscaled( +template, int...EigenArgs> +inline SLIC3R_CONSTEXPR Eigen::Matrix unscaled( const ClipperLib::IntPoint &v) SLIC3R_NOEXCEPT { - return EigenVec{unscaled(v.X), unscaled(v.Y)}; + return Eigen::Matrix{unscaled(v.X), + unscaled(v.Y)}; } namespace arrangement { @@ -139,7 +140,7 @@ protected: ItemGroup m_remaining; // Remaining items (m_items at the beginning) ItemGroup m_items; // The items to be packed - template> double norm(T val) + template ArithmeticOnly norm(T val) { return double(val) / m_norm; } diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 24542558ce..a9f2c0274c 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -260,18 +260,14 @@ template struct is_scaled_coord }; // Meta predicates for floating, 'scaled coord' and generic arithmetic types -template -using FloatingOnly = enable_if_t::value, T>; +template +using FloatingOnly = enable_if_t::value, O>; -template -using ScaledCoordOnly = enable_if_t::value, T>; +template +using ScaledCoordOnly = enable_if_t::value, O>; -template -using ArithmeticOnly = enable_if_t::value, T>; - -// A shorter form for a generic Eigen vector which is widely used in PrusaSlicer -template -using EigenVec = Eigen::Matrix; +template +using ArithmeticOnly = enable_if_t::value, O>; // Semantics are the following: // Upscaling (scaled()): only from floating point types (or Vec) to either @@ -282,7 +278,7 @@ using EigenVec = Eigen::Matrix; template> -inline SLIC3R_CONSTEXPR FloatingOnly scaled(const Tin &v) SLIC3R_NOEXCEPT +inline constexpr FloatingOnly scaled(const Tin &v) noexcept { return Tout(v / Tin(SCALING_FACTOR)); } @@ -292,15 +288,20 @@ inline SLIC3R_CONSTEXPR FloatingOnly scaled(const Tin &v) SLIC3R_NOEXCEPT // it can be different for integers but it does not have to be. Using // std::round means loosing noexcept and constexpr modifiers template> -inline SLIC3R_CONSTEXPR ScaledCoordOnly scaled(const Tin &v) SLIC3R_NOEXCEPT +inline constexpr ScaledCoordOnly scaled(const Tin &v) noexcept { //return static_cast(std::round(v / SCALING_FACTOR)); return Tout(v / Tin(SCALING_FACTOR)); } // Conversion for Eigen vectors (N dimensional points) -template> -inline EigenVec, N> scaled(const EigenVec &v) +template, + int...EigenArgs> +inline Eigen::Matrix, N, EigenArgs...> +scaled(const Eigen::Matrix &v) { return (v / SCALING_FACTOR).template cast(); } @@ -310,7 +311,7 @@ template, class = FloatingOnly> -inline SLIC3R_CONSTEXPR Tout unscaled(const Tin &v) SLIC3R_NOEXCEPT +inline constexpr Tout unscaled(const Tin &v) noexcept { return Tout(v * Tout(SCALING_FACTOR)); } @@ -321,9 +322,10 @@ template, - class = FloatingOnly> -inline SLIC3R_CONSTEXPR EigenVec unscaled( - const EigenVec &v) SLIC3R_NOEXCEPT + class = FloatingOnly, + int...EigenArgs> +inline constexpr Eigen::Matrix +unscaled(const Eigen::Matrix &v) noexcept { return v.template cast() * SCALING_FACTOR; } From c82fb9c84fa9a72b41e8cad8445f65b4b4038b06 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 17 Jul 2019 17:19:42 +0200 Subject: [PATCH 339/627] libnest2d: Remove old preload method from selector interface --- src/libnest2d/include/libnest2d/libnest2d.hpp | 11 ----------- .../libnest2d/selections/selection_boilerplate.hpp | 2 -- 2 files changed, 13 deletions(-) diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index d91b3c8f9e..d0d91838f2 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -737,17 +737,6 @@ public: return impl_.getResult(); } - /** - * @brief Loading a group of already packed bins. It is best to use a result - * from a previous packing. The algorithm will consider this input as if the - * objects are already packed and not move them. If any of these items are - * outside the bin, it is up to the placer algorithm what will happen. - * Packing additional items can fail for the bottom-left and nfp placers. - * @param pckgrp A packgroup which is a vector of item vectors. Each item - * vector corresponds to a packed bin. - */ - inline void preload(const PackGroup& pckgrp) { impl_.preload(pckgrp); } - void clear() { impl_.clear(); } }; diff --git a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp index fd6577d978..9ae92fe290 100644 --- a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp @@ -22,8 +22,6 @@ public: inline void stopCondition(StopCondition cond) { stopcond_ = cond; } - inline void preload(const PackGroup& pckgrp) { packed_bins_ = pckgrp; } - inline void clear() { packed_bins_.clear(); } protected: From 6949543912ab3974bc65f3ece28f173f33052dd2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 17 Jul 2019 18:10:08 +0200 Subject: [PATCH 340/627] Fixes after merge with master. --- src/libslic3r/Model.cpp | 2 +- src/libslic3r/Model.hpp | 20 ++++++++++++++------ src/slic3r/GUI/GLCanvas3D.cpp | 4 ++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index ed1c1bc004..ed8be8ce53 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1992,4 +1992,4 @@ CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelObject) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelVolume) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelInstance) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::Model) -#endif \ No newline at end of file +#endif diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 463d7b58e6..f5786812f7 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -623,10 +623,18 @@ private: ModelObject* object; // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject *object) : object(object), print_volume_state(PVS_Inside) { assert(this->id().valid()); } + explicit ModelInstance(ModelObject *object) : print_volume_state(PVS_Inside), object(object) + { + assert(this->id().valid()); + get_arrange_polygon(); // initialize the cache + } // Constructor, which assigns a new unique ID. explicit ModelInstance(ModelObject *object, const ModelInstance &other) : - m_transformation(other.m_transformation), object(object), print_volume_state(PVS_Inside) { assert(this->id().valid() && this->id() != other.id()); } + m_transformation(other.m_transformation), print_volume_state(PVS_Inside), object(object) + { + assert(this->id().valid() && this->id() != other.id()); + get_arrange_polygon(); // initialize the cache + } explicit ModelInstance(ModelInstance &&rhs) = delete; ModelInstance& operator=(const ModelInstance &rhs) = delete; @@ -641,10 +649,10 @@ private: } // Warning! This object is not guarded against concurrency. - // mutable struct ArrangeCache { - // bool valid = false; - // ExPolygon poly; - // } m_arrange_cache; + mutable struct ArrangeCache { + bool valid = false; + ExPolygon poly; + } m_arrange_cache; }; // The print bed content. diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 77a7271081..3823f944a7 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3410,9 +3410,9 @@ GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const if (vol->is_wipe_tower) { wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"), m_config->opt_float("wipe_tower_y")); - wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); + wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); const BoundingBoxf3& bb = vol->bounding_box(); - wti.bb_size = Vec2d(bb.size()(0), bb.size()(1)); + wti.m_bb_size = Vec2d(bb.size().x(), bb.size().y()); break; } } From 08d37aad06ab8dea223ff5e7c588ae7bca74ef6e Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 18 Jul 2019 11:12:11 +0200 Subject: [PATCH 341/627] Added selection of custom bed texture to bed shape dialog --- src/libslic3r/PrintConfig.cpp | 5 + src/slic3r/GUI/2DBed.cpp | 22 ++-- src/slic3r/GUI/2DBed.hpp | 11 +- src/slic3r/GUI/BedShapeDialog.cpp | 166 ++++++++++++++++++++++++------ src/slic3r/GUI/BedShapeDialog.hpp | 26 +++-- src/slic3r/GUI/ConfigWizard.cpp | 10 +- src/slic3r/GUI/GUI_App.cpp | 9 +- src/slic3r/GUI/GUI_App.hpp | 3 + src/slic3r/GUI/Preset.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 14 ++- 10 files changed, 198 insertions(+), 70 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 7d451a4cbc..d6104d98cc 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -51,6 +51,11 @@ void PrintConfigDef::init_common_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) }); + def = this->add("bed_custom_texture", coString); + def->label = L("Bed custom texture"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + def = this->add("layer_height", coFloat); def->label = L("Layer height"); def->category = L("Layers and Perimeters"); diff --git a/src/slic3r/GUI/2DBed.cpp b/src/slic3r/GUI/2DBed.cpp index a339f3e66a..d2075f673d 100644 --- a/src/slic3r/GUI/2DBed.cpp +++ b/src/slic3r/GUI/2DBed.cpp @@ -18,10 +18,9 @@ wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(25 * wxGetApp().em_unit(), - #ifdef __APPLE__ m_user_drawn_background = false; #endif /*__APPLE__*/ - Bind(wxEVT_PAINT, ([this](wxPaintEvent &/* e */) { repaint(); })); - Bind(wxEVT_SIZE, ([this](wxSizeEvent & /* e */) { Refresh(); })); } -void Bed_2D::repaint() + +void Bed_2D::repaint(const std::vector& shape) { wxAutoBufferedPaintDC dc(this); auto cw = GetSize().GetWidth(); @@ -41,7 +40,7 @@ void Bed_2D::repaint() dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight()); } - if (m_bed_shape.empty()) + if (shape.empty()) return; // reduce size to have some space around the drawn shape @@ -52,10 +51,9 @@ void Bed_2D::repaint() auto ccenter = cbb.center(); // get bounding box of bed shape in G - code coordinates - auto bed_shape = m_bed_shape; - auto bed_polygon = Polygon::new_scale(m_bed_shape); - auto bb = BoundingBoxf(m_bed_shape); - bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area + auto bed_polygon = Polygon::new_scale(shape); + auto bb = BoundingBoxf(shape); + bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area auto bw = bb.size()(0); auto bh = bb.size()(1); auto bcenter = bb.center(); @@ -73,8 +71,8 @@ void Bed_2D::repaint() // draw bed fill dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID)); wxPointList pt_list; - for (auto pt: m_bed_shape) - { + for (auto pt : shape) + { Point pt_pix = to_pixels(pt, ch); pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1))); } @@ -155,13 +153,13 @@ void Bed_2D::repaint() // convert G - code coordinates into pixels -Point Bed_2D::to_pixels(Vec2d point, int height) +Point Bed_2D::to_pixels(const Vec2d& point, int height) { auto p = point * m_scale_factor + m_shift; return Point(p(0) + Border, height - p(1) + Border); } -void Bed_2D::set_pos(Vec2d pos) +void Bed_2D::set_pos(const Vec2d& pos) { m_pos = pos; Refresh(); diff --git a/src/slic3r/GUI/2DBed.hpp b/src/slic3r/GUI/2DBed.hpp index a61fb313d4..80926bea72 100644 --- a/src/slic3r/GUI/2DBed.hpp +++ b/src/slic3r/GUI/2DBed.hpp @@ -17,16 +17,13 @@ class Bed_2D : public wxPanel Vec2d m_shift = Vec2d::Zero(); Vec2d m_pos = Vec2d::Zero(); - Point to_pixels(Vec2d point, int height); - void repaint(); - void set_pos(Vec2d pos); + Point to_pixels(const Vec2d& point, int height); + void set_pos(const Vec2d& pos); public: - Bed_2D(wxWindow* parent); - ~Bed_2D() {} + explicit Bed_2D(wxWindow* parent); - std::vector m_bed_shape; - + void repaint(const std::vector& shape); }; diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 98a5178df2..1beda280b1 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -10,17 +10,18 @@ #include "libslic3r/Polygon.hpp" #include "boost/nowide/iostream.hpp" +#include #include namespace Slic3r { namespace GUI { -void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt) +void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture) { SetFont(wxGetApp().normal_font()); m_panel = new BedShapePanel(this); - m_panel->build_panel(default_pt); + m_panel->build_panel(default_pt, custom_texture); auto main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(m_panel, 1, wxEXPAND); @@ -51,14 +52,19 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) Refresh(); } -void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt) +const std::string BedShapePanel::NONE = "None"; +const std::string BedShapePanel::EMPTY_STRING = ""; + +void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture) { + m_shape = default_pt.values; + m_custom_texture = custom_texture.value.empty() ? NONE : custom_texture.value; + auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font()); // shape options - m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, - wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); + m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); sbsizer->Add(m_shape_options_book); auto optgroup = init_shape_options_page(_(L("Rectangular"))); @@ -106,6 +112,8 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt) }; optgroup->append_line(line); + wxPanel* texture_panel = init_texture_panel(); + Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) { update_shape(); @@ -113,13 +121,20 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt) // right pane with preview canvas m_canvas = new Bed_2D(this); - m_canvas->m_bed_shape = default_pt.values; - // main sizer - auto top_sizer = new wxBoxSizer(wxHORIZONTAL); - top_sizer->Add(sbsizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); - if (m_canvas) - top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ; + if (m_canvas != nullptr) + { + m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent& e) { m_canvas->repaint(m_shape); }); + m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { m_canvas->Refresh(); }); + } + + wxSizer* left_sizer = new wxBoxSizer(wxVERTICAL); + left_sizer->Add(sbsizer, 0, wxEXPAND); + left_sizer->Add(texture_panel, 1, wxEXPAND); + + wxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL); + top_sizer->Add(left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); + top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10); SetSizerAndFit(top_sizer); @@ -150,6 +165,66 @@ ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& tit return optgroup; } +wxPanel* BedShapePanel::init_texture_panel() +{ + wxPanel* panel = new wxPanel(this); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Texture"))); + + optgroup->label_width = 10; + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + update_shape(); + }; + + Line line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load..."))); + wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL); + load_sizer->Add(load_btn, 1, wxEXPAND); + + wxStaticText* filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE)); + wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL); + filename_sizer->Add(filename_lbl, 1, wxEXPAND); + + wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove"))); + wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL); + remove_sizer->Add(remove_btn, 1, wxEXPAND); + + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(filename_sizer, 1, wxEXPAND); + sizer->Add(load_sizer, 1, wxEXPAND); + sizer->Add(remove_sizer, 1, wxEXPAND); + + load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + load_texture(); + })); + + remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + m_custom_texture = NONE; + update_shape(); + })); + + filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) + { + e.SetText(_(boost::filesystem::path(m_custom_texture).filename().string())); + })); + + remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) + { + e.Enable(m_custom_texture != NONE); + })); + + return sizer; + }; + optgroup->append_line(line); + + panel->SetSizerAndFit(optgroup->sizer); + + return panel; +} + // Called from the constructor. // Set the initial bed shape from a list of points. // Deduce the bed shape type(rect, circle, custom) @@ -232,7 +307,7 @@ void BedShapePanel::set_shape(const ConfigOptionPoints& points) // This is a custom bed shape, use the polygon provided. m_shape_options_book->SetSelection(SHAPE_CUSTOM); // Copy the polygon to the canvas, make a copy of the array. - m_loaded_bed_shape = points.values; + m_loaded_shape = points.values; update_shape(); } @@ -277,11 +352,11 @@ void BedShapePanel::update_shape() x1 -= dx; y0 -= dy; y1 -= dy; - m_canvas->m_bed_shape = { Vec2d(x0, y0), - Vec2d(x1, y0), - Vec2d(x1, y1), - Vec2d(x0, y1)}; - } + m_shape = { Vec2d(x0, y0), + Vec2d(x1, y0), + Vec2d(x1, y1), + Vec2d(x0, y1) }; + } else if(page_idx == SHAPE_CIRCULAR) { double diameter; try{ @@ -293,16 +368,16 @@ void BedShapePanel::update_shape() if (diameter == 0.0) return ; auto r = diameter / 2; auto twopi = 2 * PI; - auto edges = 60; - std::vector points; - for (size_t i = 1; i <= 60; ++i) { - auto angle = i * twopi / edges; + auto edges = 72; + std::vector points; + for (size_t i = 1; i <= edges; ++i) { + auto angle = i * twopi / edges; points.push_back(Vec2d(r*cos(angle), r*sin(angle))); } - m_canvas->m_bed_shape = points; - } + m_shape = points; + } else if (page_idx == SHAPE_CUSTOM) - m_canvas->m_bed_shape = m_loaded_bed_shape; + m_shape = m_loaded_shape; update_preview(); } @@ -310,19 +385,23 @@ void BedShapePanel::update_shape() // Loads an stl file, projects it to the XY plane and calculates a polygon. void BedShapePanel::load_stl() { - wxFileDialog dialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "", - file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + wxFileDialog dialog(this, _(L("Choose an STL file to import bed shape from:")), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dialog.ShowModal() != wxID_OK) return; - wxArrayString input_file; - dialog.GetPaths(input_file); + std::string file_name = dialog.GetPath().ToUTF8().data(); - std::string file_name = input_file[0].ToUTF8().data(); + if (!boost::iequals(boost::filesystem::path(file_name).extension().string().c_str(), ".stl")) + { + show_error(this, _(L("Invalid file format."))); + return; + } + + wxBusyCursor wait; Model model; try { - model = Model::read_from_file(file_name); + model = Model::read_from_file(file_name); } catch (std::exception &) { show_error(this, _(L("Error! Invalid model"))); @@ -346,7 +425,32 @@ void BedShapePanel::load_stl() for (auto pt : polygon.points) points.push_back(unscale(pt)); - m_loaded_bed_shape = points; + m_loaded_shape = points; + update_shape(); +} + +void BedShapePanel::load_texture() +{ + wxFileDialog dialog(this, _(L("Choose a file to import bed texture from (PNG/SVG):")), "", "", + file_wildcards(FT_TEX), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() != wxID_OK) + return; + + m_custom_texture = NONE; + + std::string file_name = dialog.GetPath().ToUTF8().data(); + std::string file_ext = boost::filesystem::path(file_name).extension().string(); + + if (!boost::iequals(file_ext.c_str(), ".png") && !boost::iequals(file_ext.c_str(), ".svg")) + { + show_error(this, _(L("Invalid file format."))); + return; + } + + wxBusyCursor wait; + + m_custom_texture = file_name; update_shape(); } diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 81d47320da..3a8d1c82be 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -16,25 +16,32 @@ namespace GUI { using ConfigOptionsGroupShp = std::shared_ptr; class BedShapePanel : public wxPanel { + static const std::string NONE; + static const std::string EMPTY_STRING; + Bed_2D* m_canvas; - std::vector m_loaded_bed_shape; + std::vector m_shape; + std::vector m_loaded_shape; + std::string m_custom_texture; public: - BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY) {} - ~BedShapePanel() {} - - void build_panel(const ConfigOptionPoints& default_pt); + BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY), m_custom_texture(NONE) {} + ~BedShapePanel() {} + void build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture); // Returns the resulting bed shape polygon. This value will be stored to the ini file. - std::vector get_bed_shape() { return m_canvas->m_bed_shape; } + const std::vector& get_shape() const { return m_shape; } + const std::string& get_custom_texture() const { return (m_custom_texture != NONE) ? m_custom_texture : EMPTY_STRING; } private: ConfigOptionsGroupShp init_shape_options_page(const wxString& title); + wxPanel* init_texture_panel(); void set_shape(const ConfigOptionPoints& points); void update_preview(); void update_shape(); void load_stl(); - + void load_texture(); + wxChoicebook* m_shape_options_book; std::vector m_optgroups; @@ -49,8 +56,9 @@ public: wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {} ~BedShapeDialog() {} - void build_dialog(const ConfigOptionPoints& default_pt); - std::vector get_bed_shape() { return m_panel->get_bed_shape(); } + void build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture); + const std::vector& get_shape() const { return m_panel->get_shape(); } + const std::string& get_custom_texture() const { return m_panel->get_custom_texture(); } protected: void on_dpi_changed(const wxRect &suggested_rect) override; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 59ba936774..bdd4879312 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -532,14 +532,18 @@ PageBedShape::PageBedShape(ConfigWizard *parent) { append_text(_(L("Set the shape of your printer's bed."))); - shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape")); + shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"), + *wizard_p()->custom_config->option("bed_custom_texture")); + append(shape_panel); } void PageBedShape::apply_custom_config(DynamicPrintConfig &config) { - const auto points(shape_panel->get_bed_shape()); + const std::vector& points = shape_panel->get_shape(); + const std::string& custom_texture = shape_panel->get_custom_texture(); config.set_key_value("bed_shape", new ConfigOptionPoints(points)); + config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture)); } PageDiameters::PageDiameters(ConfigWizard *parent) @@ -1085,7 +1089,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) p->load_vendors(); p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ - "gcode_flavor", "bed_shape", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", + "gcode_flavor", "bed_shape", "bed_custom_texture", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", })); p->index = new ConfigWizardIndex(this); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 8a376c3a37..e43181dc08 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -67,9 +67,12 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) /* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA", /* FT_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF", - /* FT_INI */ "INI files (*.ini)|*.ini;*.INI", - /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", - /* FT_PNGZIP */"Masked SLA files (*.sl1)|*.sl1;*.SL1", + /* FT_INI */ "INI files (*.ini)|*.ini;*.INI", + /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", + + /* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG", + + /* FT_PNGZIP */ "Masked SLA files (*.sl1)|*.sl1;*.SL1", }; std::string out = defaults[file_type]; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index e69503ff8a..9c06ce8ca5 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -44,6 +44,9 @@ enum FileType FT_INI, FT_SVG, + + FT_TEX, + FT_PNGZIP, FT_SIZE, diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 3d467d7f8f..12fed23a0f 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -411,7 +411,7 @@ const std::vector& Preset::printer_options() if (s_opts.empty()) { s_opts = { "printer_technology", - "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", + "bed_shape", "bed_custom_texture", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "host_type", "print_host", "printhost_apikey", "printhost_cafile", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a307362203..20936ba3d4 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1856,12 +1856,15 @@ void TabPrinter::build_fff() btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { BedShapeDialog dlg(this); - dlg.build_dialog(*m_config->option("bed_shape")); + dlg.build_dialog(*m_config->option("bed_shape"), + *m_config->option("bed_custom_texture")); if (dlg.ShowModal() == wxID_OK) { - std::vector shape = dlg.get_bed_shape(); + const std::vector& shape = dlg.get_shape(); + const std::string& custom_texture = dlg.get_custom_texture(); if (!shape.empty()) { load_key_value("bed_shape", shape); + load_key_value("bed_custom_texture", custom_texture); update_changed_ui(); } } @@ -2062,12 +2065,15 @@ void TabPrinter::build_sla() btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { BedShapeDialog dlg(this); - dlg.build_dialog(*m_config->option("bed_shape")); + dlg.build_dialog(*m_config->option("bed_shape"), + *m_config->option("bed_custom_texture")); if (dlg.ShowModal() == wxID_OK) { - std::vector shape = dlg.get_bed_shape(); + const std::vector& shape = dlg.get_shape(); + const std::string& custom_texture = dlg.get_custom_texture(); if (!shape.empty()) { load_key_value("bed_shape", shape); + load_key_value("bed_custom_texture", custom_texture); update_changed_ui(); } } From 8febd88e8072bc71332099ac749a107e1eeb36ca Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 18 Jul 2019 11:36:17 +0200 Subject: [PATCH 342/627] Fixed rendering of toolbars' textures with prespective camera --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/GLTexture.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 31372659f1..47b9ad48a4 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3995,7 +3995,7 @@ void GLCanvas3D::_render_overlays() const glsafe(::glPushMatrix()); glsafe(::glLoadIdentity()); // ensure that the textures are renderered inside the frustrum - glsafe(::glTranslated(0.0, 0.0, -(m_camera.get_near_z() + 0.5))); + glsafe(::glTranslated(0.0, 0.0, -(m_camera.get_near_z() + 0.005))); // ensure that the overlay fits the frustrum near z plane double gui_scale = m_camera.get_gui_scale(); glsafe(::glScaled(gui_scale, gui_scale, 1.0)); diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 171a5c8851..7a29cc6cab 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -280,9 +280,9 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector Date: Thu, 18 Jul 2019 11:51:06 +0200 Subject: [PATCH 343/627] Undo / Redo memory conservation strategy: Release recoverable data starting from the objects of lowest ObjectID. (convex hulls are recoverable as well as the indexed triangle sets inside the TriangleMeshes or the triangle connectivity information). Now the top most snapshot (the temp one taken before Undo jump) will never be released. --- src/admesh/connect.cpp | 2 + src/libslic3r/Model.hpp | 24 ++++++- src/libslic3r/TriangleMesh.cpp | 28 ++++++++ src/libslic3r/TriangleMesh.hpp | 6 +- src/slic3r/Utils/UndoRedo.cpp | 115 ++++++++++++++++++++++++++++++--- 5 files changed, 162 insertions(+), 13 deletions(-) diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index b86ec50555..d6de6ce6a4 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -430,6 +430,8 @@ private: // floats of the first edge matches all six floats of the second edge. void stl_check_facets_exact(stl_file *stl) { + assert(stl->facet_start.size() == stl->neighbors_start.size()); + stl->stats.connected_edges = 0; stl->stats.connected_facets_1_edge = 0; stl->stats.connected_facets_2_edge = 0; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 7551bd8cb8..37741a1f10 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -526,8 +526,23 @@ private: ModelVolume() : ObjectBase(-1), config(-1), object(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); } - template void serialize(Archive &ar) { - ar(name, config, m_mesh, m_type, m_material_id, m_convex_hull, m_transformation, m_is_splittable); + template void load(Archive &ar) { + bool has_convex_hull; + ar(name, config, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + assert(m_mesh); + if (has_convex_hull) { + cereal::load_optional(ar, m_convex_hull); + if (! m_convex_hull && ! m_mesh->empty()) + // The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it. + this->calculate_convex_hull(); + } else + m_convex_hull.reset(); + } + template void save(Archive &ar) const { + bool has_convex_hull = m_convex_hull.get() != nullptr; + ar(name, config, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + if (has_convex_hull) + cereal::save_optional(ar, m_convex_hull); } }; @@ -747,4 +762,9 @@ void check_model_ids_equal(const Model &model1, const Model &model2); } // namespace Slic3r +namespace cereal +{ + template struct specialize {}; +} + #endif /* slic3r_Model_hpp_ */ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index d782bc5ace..c2fcb11bd2 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -613,6 +613,34 @@ size_t TriangleMesh::memsize() const return memsize; } +// Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released. +size_t TriangleMesh::release_optional() +{ + size_t memsize_released = sizeof(stl_neighbors) * this->stl.neighbors_start.size() + this->its.memsize(); + // The indexed triangle set may be recalculated using the stl_generate_shared_vertices() function. + this->its.clear(); + // The neighbors structure may be recalculated using the stl_check_facets_exact() function. + this->stl.neighbors_start.clear(); + return memsize_released; +} + +// Restore optional data possibly released by release_optional(). +void TriangleMesh::restore_optional() +{ + if (! this->stl.facet_start.empty()) { + // Save the old stats before calling stl_check_faces_exact, as it may modify the statistics. + stl_stats stats = this->stl.stats; + if (this->stl.neighbors_start.empty()) { + stl_reallocate(&this->stl); + stl_check_facets_exact(&this->stl); + } + if (this->its.vertices.empty()) + stl_generate_shared_vertices(&this->stl, this->its); + // Restore the old statistics. + this->stl.stats = stats; + } +} + void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel) { mesh = _mesh; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index d2b6886387..81390b79b0 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -67,8 +67,12 @@ public: size_t facets_count() const { return this->stl.stats.number_of_facets; } bool empty() const { return this->facets_count() == 0; } bool is_splittable() const; - // Estimate of the memory occupied by this structure. + // Estimate of the memory occupied by this structure, important for keeping an eye on the Undo / Redo stack allocation. size_t memsize() const; + // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released. + size_t release_optional(); + // Restore optional data possibly released by release_optional(). + void restore_optional(); stl_file stl; indexed_triangle_set its; diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 51a8113503..10873ea89b 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -25,6 +25,12 @@ #ifndef NDEBUG // #define SLIC3R_UNDOREDO_DEBUG #endif /* NDEBUG */ +#if 0 + // Stop at a fraction of the normal Undo / Redo stack size. + #define UNDO_REDO_DEBUG_LOW_MEM_FACTOR 10000 +#else + #define UNDO_REDO_DEBUG_LOW_MEM_FACTOR 1 +#endif namespace Slic3r { namespace UndoRedo { @@ -74,6 +80,9 @@ public: // Is the object captured by this history mutable or immutable? virtual bool is_mutable() const = 0; virtual bool is_immutable() const = 0; + // The object is optional, it may be released if the Undo / Redo stack memory grows over the limits. + virtual bool is_optional() const { return false; } + // If it is an immutable object, return its pointer. There is a map assigning a temporary ObjectID to the immutable object pointer. virtual const void* immutable_object_ptr() const { return nullptr; } // If the history is empty, the ObjectHistory object could be released. @@ -85,6 +94,10 @@ public: // Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released. // Return the amount of memory released. virtual size_t release_after_timestamp(size_t timestamp) = 0; + // Release all optional data of this history. + virtual size_t release_optional() = 0; + // Restore optional data possibly released by release_optional. + virtual void restore_optional() = 0; // Estimated size in memory, to be used to drop least recently used snapshots. virtual size_t memsize() const = 0; @@ -175,11 +188,13 @@ template class ImmutableObjectHistory : public ObjectHistory { public: - ImmutableObjectHistory(std::shared_ptr shared_object) : m_shared_object(shared_object) {} + ImmutableObjectHistory(std::shared_ptr shared_object, bool optional) : m_shared_object(shared_object), m_optional(optional) {} ~ImmutableObjectHistory() override {} bool is_mutable() const override { return false; } bool is_immutable() const override { return true; } + bool is_optional() const override { return m_optional; } + // If it is an immutable object, return its pointer. There is a map assigning a temporary ObjectID to the immutable object pointer. const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); } // Estimated size in memory, to be used to drop least recently used snapshots. @@ -216,6 +231,37 @@ public: return timestamp >= it->begin() && timestamp < it->end(); } + // Release all optional data of this history. + size_t release_optional() override { + size_t mem_released = 0; + if (m_optional) { + bool released = false; + if (this->is_serialized()) { + mem_released += m_serialized.size(); + m_serialized.clear(); + released = true; + } else if (m_shared_object.use_count() == 1) { + mem_released += m_shared_object->memsize(); + m_shared_object.reset(); + released = true; + } + if (released) { + mem_released += m_history.size() * sizeof(Interval); + m_history.clear(); + } + } else if (m_shared_object.use_count() == 1) { + // The object is in memory, but it is not shared with the scene. Let the object decide whether there is any optional data to release. + const_cast(m_shared_object.get())->release_optional(); + } + return mem_released; + } + + // Restore optional data possibly released by this->release_optional(). + void restore_optional() override { + if (m_shared_object.use_count() == 1) + const_cast(m_shared_object.get())->restore_optional(); + } + bool is_serialized() const { return m_shared_object.get() == nullptr; } const std::string& serialized_data() const { return m_serialized; } std::shared_ptr& shared_ptr(StackImpl &stack); @@ -240,6 +286,8 @@ private: // Either the source object is held by a shared pointer and the m_serialized field is empty, // or the shared pointer is null and the object is being serialized into m_serialized. std::shared_ptr m_shared_object; + // If this object is optional, then it may be deleted from the Undo / Redo stack and recalculated from other data (for example mesh convex hull). + bool m_optional; std::string m_serialized; }; @@ -375,6 +423,11 @@ public: return std::string(it->data(), it->data() + it->size()); } + // Currently all mutable snapshots are mandatory. + size_t release_optional() override { return 0; } + // Currently there is no way to release optional data from the mutable objects. + void restore_optional() override {} + #ifdef SLIC3R_UNDOREDO_DEBUG std::string format() override { std::string out = typeid(T).name(); @@ -430,7 +483,7 @@ class StackImpl public: // Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning. // Initially enable Undo / Redo stack to occupy maximum 10% of the total system physical memory. - StackImpl() : m_memory_limit(std::min(Slic3r::total_physical_memory() / 10, size_t(1 * 16384 * 65536))), m_active_snapshot_time(0), m_current_time(0) {} + StackImpl() : m_memory_limit(std::min(Slic3r::total_physical_memory() / 10, size_t(1 * 16384 * 65536 / UNDO_REDO_DEBUG_LOW_MEM_FACTOR))), m_active_snapshot_time(0), m_current_time(0) {} void set_memory_limit(size_t memsize) { m_memory_limit = memsize; } @@ -461,9 +514,9 @@ public: //protected: template ObjectID save_mutable_object(const T &object); - template ObjectID save_immutable_object(std::shared_ptr &object); + template ObjectID save_immutable_object(std::shared_ptr &object, bool optional); template T* load_mutable_object(const Slic3r::ObjectID id); - template std::shared_ptr load_immutable_object(const Slic3r::ObjectID id); + template std::shared_ptr load_immutable_object(const Slic3r::ObjectID id, bool optional); template void load_mutable_object(const Slic3r::ObjectID id, T &target); #ifdef SLIC3R_UNDOREDO_DEBUG @@ -620,7 +673,11 @@ namespace cereal // store just the ObjectID to this stream. template void save(BinaryOutputArchive &ar, const std::shared_ptr &ptr) { - ar(cereal::get_user_data(ar).save_immutable_object(const_cast&>(ptr))); + ar(cereal::get_user_data(ar).save_immutable_object(const_cast&>(ptr), false)); + } + template void save_optional(BinaryOutputArchive &ar, const std::shared_ptr &ptr) + { + ar(cereal::get_user_data(ar).save_immutable_object(const_cast&>(ptr), true)); } // Load ObjectBase derived class from the Undo / Redo stack as a separate object @@ -630,7 +687,14 @@ namespace cereal Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data(ar); size_t id; ar(id); - ptr = stack.load_immutable_object(Slic3r::ObjectID(id)); + ptr = stack.load_immutable_object(Slic3r::ObjectID(id), false); + } + template void load_optional(BinaryInputArchive &ar, std::shared_ptr &ptr) + { + Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data(ar); + size_t id; + ar(id); + ptr = stack.load_immutable_object(Slic3r::ObjectID(id), true); } } @@ -675,14 +739,16 @@ template ObjectID StackImpl::save_mutable_object(cons return object.id(); } -template ObjectID StackImpl::save_immutable_object(std::shared_ptr &object) +template ObjectID StackImpl::save_immutable_object(std::shared_ptr &object, bool optional) { // First allocate a temporary ObjectID for this pointer. ObjectID object_id = this->immutable_object_id(object); // and find or allocate a history stack for the ObjectID associated to this shared_ptr. auto it_object_history = m_objects.find(object_id); if (it_object_history == m_objects.end()) - it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr>(new ImmutableObjectHistory(object))); + it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr>(new ImmutableObjectHistory(object, optional))); + else + assert(it_object_history->second.get()->is_optional() == optional); // Then save the interval. static_cast*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time); return object_id; @@ -695,13 +761,16 @@ template T* StackImpl::load_mutable_object(const Slic3r::ObjectID id return target; } -template std::shared_ptr StackImpl::load_immutable_object(const Slic3r::ObjectID id) +template std::shared_ptr StackImpl::load_immutable_object(const Slic3r::ObjectID id, bool optional) { // First find a history stack for the ObjectID of this object instance. auto it_object_history = m_objects.find(id); - assert(it_object_history != m_objects.end()); + assert(optional || it_object_history != m_objects.end()); + if (it_object_history == m_objects.end()) + return std::shared_ptr(); auto *object_history = static_cast*>(it_object_history->second.get()); assert(object_history->has_snapshot(m_active_snapshot_time)); + object_history->restore_optional(); return object_history->shared_ptr(*this); } @@ -869,12 +938,32 @@ void StackImpl::release_least_recently_used() #ifdef SLIC3R_UNDOREDO_DEBUG bool released = false; #endif + // First try to release the optional immutable data (for example the convex hulls), + // or the shared vertices of triangle meshes. + for (auto it = m_objects.begin(); current_memsize > m_memory_limit && it != m_objects.end();) { + const void *ptr = it->second->immutable_object_ptr(); + size_t mem_released = it->second->release_optional(); + if (it->second->empty()) { + if (ptr != nullptr) + // Release the immutable object from the ptr to ObjectID map. + m_shared_ptr_to_object_id.erase(ptr); + mem_released += it->second->memsize(); + it = m_objects.erase(it); + } else + ++ it; + assert(current_memsize >= mem_released); + if (current_memsize >= mem_released) + current_memsize -= mem_released; + else + current_memsize = 0; + } while (current_memsize > m_memory_limit && m_snapshots.size() >= 3) { // From which side to remove a snapshot? assert(m_snapshots.front().timestamp < m_active_snapshot_time); size_t mem_released = 0; if (m_snapshots[1].timestamp == m_active_snapshot_time) { // Remove the last snapshot. +#if 0 for (auto it = m_objects.begin(); it != m_objects.end();) { mem_released += it->second->release_after_timestamp(m_snapshots.back().timestamp); if (it->second->empty()) { @@ -888,6 +977,12 @@ void StackImpl::release_least_recently_used() } m_snapshots.pop_back(); m_snapshots.back().name = topmost_snapshot_name; +#else + // Rather don't release the last snapshot as it will be very confusing to the user + // as of why he cannot jump to the top most state. The Undo / Redo stack maximum size + // should be set low enough to accomodate for the top most snapshot. + break; +#endif } else { // Remove the first snapshot. for (auto it = m_objects.begin(); it != m_objects.end();) { From 44d7462bdb4194a7df2837e3686434b92a1ea668 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 18 Jul 2019 12:03:41 +0200 Subject: [PATCH 344/627] Partially revert 8febd88e8072bc71332099ac749a107e1eeb36ca --- src/slic3r/GUI/GLTexture.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 7a29cc6cab..171a5c8851 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -280,9 +280,9 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector Date: Thu, 18 Jul 2019 12:07:50 +0200 Subject: [PATCH 345/627] Fix for compilation on clang: Forward declarations of templates. --- src/libslic3r/Model.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 37741a1f10..c026863ecb 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -17,6 +17,13 @@ #include #include +namespace cereal { + class BinaryInputArchive; + class BinaryOutputArchive; + template void load_optional(BinaryInputArchive &ar, std::shared_ptr &ptr); + template void save_optional(BinaryOutputArchive &ar, const std::shared_ptr &ptr); +} + namespace Slic3r { class Model; From de383b1809202a911aeefdd298ba1235a630da84 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 18 Jul 2019 12:56:52 +0200 Subject: [PATCH 346/627] Added selection of custom bed model to bed shape dialog --- src/libslic3r/PrintConfig.cpp | 5 ++ src/slic3r/GUI/BedShapeDialog.cpp | 101 +++++++++++++++++++++++++++--- src/slic3r/GUI/BedShapeDialog.hpp | 15 +++-- src/slic3r/GUI/ConfigWizard.cpp | 7 ++- src/slic3r/GUI/Preset.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 10 ++- 6 files changed, 120 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d6104d98cc..3ebab9f687 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -56,6 +56,11 @@ void PrintConfigDef::init_common_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); + def = this->add("bed_custom_model", coString); + def->label = L("Bed custom model"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("")); + def = this->add("layer_height", coFloat); def->label = L("Layer height"); def->category = L("Layers and Perimeters"); diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 1beda280b1..4ae91c7828 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -17,11 +17,11 @@ namespace Slic3r { namespace GUI { -void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture) +void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model) { SetFont(wxGetApp().normal_font()); m_panel = new BedShapePanel(this); - m_panel->build_panel(default_pt, custom_texture); + m_panel->build_panel(default_pt, custom_texture, custom_model); auto main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(m_panel, 1, wxEXPAND); @@ -55,10 +55,11 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) const std::string BedShapePanel::NONE = "None"; const std::string BedShapePanel::EMPTY_STRING = ""; -void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture) +void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model) { m_shape = default_pt.values; m_custom_texture = custom_texture.value.empty() ? NONE : custom_texture.value; + m_custom_model = custom_model.value.empty() ? NONE : custom_model.value; auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font()); @@ -113,6 +114,7 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf optgroup->append_line(line); wxPanel* texture_panel = init_texture_panel(); + wxPanel* model_panel = init_model_panel(); Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) { @@ -121,16 +123,13 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf // right pane with preview canvas m_canvas = new Bed_2D(this); - - if (m_canvas != nullptr) - { - m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent& e) { m_canvas->repaint(m_shape); }); - m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { m_canvas->Refresh(); }); - } + m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent& e) { m_canvas->repaint(m_shape); }); + m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { m_canvas->Refresh(); }); wxSizer* left_sizer = new wxBoxSizer(wxVERTICAL); left_sizer->Add(sbsizer, 0, wxEXPAND); left_sizer->Add(texture_panel, 1, wxEXPAND); + left_sizer->Add(model_panel, 1, wxEXPAND); wxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL); top_sizer->Add(left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); @@ -225,6 +224,66 @@ wxPanel* BedShapePanel::init_texture_panel() return panel; } +wxPanel* BedShapePanel::init_model_panel() +{ + wxPanel* panel = new wxPanel(this); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Model"))); + + optgroup->label_width = 10; + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + update_shape(); + }; + + Line line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load..."))); + wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL); + load_sizer->Add(load_btn, 1, wxEXPAND); + + wxStaticText* filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE)); + wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL); + filename_sizer->Add(filename_lbl, 1, wxEXPAND); + + wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove"))); + wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL); + remove_sizer->Add(remove_btn, 1, wxEXPAND); + + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(filename_sizer, 1, wxEXPAND); + sizer->Add(load_sizer, 1, wxEXPAND); + sizer->Add(remove_sizer, 1, wxEXPAND); + + load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + load_model(); + })); + + remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + m_custom_model = NONE; + update_shape(); + })); + + filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) + { + e.SetText(_(boost::filesystem::path(m_custom_model).filename().string())); + })); + + remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) + { + e.Enable(m_custom_model != NONE); + })); + + return sizer; + }; + optgroup->append_line(line); + + panel->SetSizerAndFit(optgroup->sizer); + + return panel; +} + // Called from the constructor. // Set the initial bed shape from a list of points. // Deduce the bed shape type(rect, circle, custom) @@ -390,7 +449,6 @@ void BedShapePanel::load_stl() return; std::string file_name = dialog.GetPath().ToUTF8().data(); - if (!boost::iequals(boost::filesystem::path(file_name).extension().string().c_str(), ".stl")) { show_error(this, _(L("Invalid file format."))); @@ -454,5 +512,28 @@ void BedShapePanel::load_texture() update_shape(); } +void BedShapePanel::load_model() +{ + wxFileDialog dialog(this, _(L("Choose an STL file to import bed model from:")), "", "", + file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() != wxID_OK) + return; + + m_custom_model = NONE; + + std::string file_name = dialog.GetPath().ToUTF8().data(); + if (!boost::iequals(boost::filesystem::path(file_name).extension().string().c_str(), ".stl")) + { + show_error(this, _(L("Invalid file format."))); + return; + } + + wxBusyCursor wait; + + m_custom_model = file_name; + update_shape(); +} + } // GUI } // Slic3r diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 3a8d1c82be..bf12cc8934 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -23,24 +23,28 @@ class BedShapePanel : public wxPanel std::vector m_shape; std::vector m_loaded_shape; std::string m_custom_texture; + std::string m_custom_model; public: - BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY), m_custom_texture(NONE) {} - ~BedShapePanel() {} + BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY), m_custom_texture(NONE), m_custom_model(NONE) {} + + void build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model); - void build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture); // Returns the resulting bed shape polygon. This value will be stored to the ini file. const std::vector& get_shape() const { return m_shape; } const std::string& get_custom_texture() const { return (m_custom_texture != NONE) ? m_custom_texture : EMPTY_STRING; } + const std::string& get_custom_model() const { return (m_custom_model != NONE) ? m_custom_model : EMPTY_STRING; } private: ConfigOptionsGroupShp init_shape_options_page(const wxString& title); wxPanel* init_texture_panel(); + wxPanel* init_model_panel(); void set_shape(const ConfigOptionPoints& points); void update_preview(); void update_shape(); void load_stl(); void load_texture(); + void load_model(); wxChoicebook* m_shape_options_book; std::vector m_optgroups; @@ -54,11 +58,12 @@ class BedShapeDialog : public DPIDialog public: BedShapeDialog(wxWindow* parent) : DPIDialog(parent, wxID_ANY, _(L("Bed Shape")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {} - ~BedShapeDialog() {} - void build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture); + void build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model); + const std::vector& get_shape() const { return m_panel->get_shape(); } const std::string& get_custom_texture() const { return m_panel->get_custom_texture(); } + const std::string& get_custom_model() const { return m_panel->get_custom_model(); } protected: void on_dpi_changed(const wxRect &suggested_rect) override; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index bdd4879312..6a70cb9fdb 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -533,7 +533,8 @@ PageBedShape::PageBedShape(ConfigWizard *parent) append_text(_(L("Set the shape of your printer's bed."))); shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"), - *wizard_p()->custom_config->option("bed_custom_texture")); + *wizard_p()->custom_config->option("bed_custom_texture"), + *wizard_p()->custom_config->option("bed_custom_model")); append(shape_panel); } @@ -542,8 +543,10 @@ void PageBedShape::apply_custom_config(DynamicPrintConfig &config) { const std::vector& points = shape_panel->get_shape(); const std::string& custom_texture = shape_panel->get_custom_texture(); + const std::string& custom_model = shape_panel->get_custom_model(); config.set_key_value("bed_shape", new ConfigOptionPoints(points)); config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture)); + config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); } PageDiameters::PageDiameters(ConfigWizard *parent) @@ -1089,7 +1092,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) p->load_vendors(); p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ - "gcode_flavor", "bed_shape", "bed_custom_texture", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", + "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", })); p->index = new ConfigWizardIndex(this); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 12fed23a0f..635b21ba97 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -411,7 +411,7 @@ const std::vector& Preset::printer_options() if (s_opts.empty()) { s_opts = { "printer_technology", - "bed_shape", "bed_custom_texture", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", + "bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "host_type", "print_host", "printhost_apikey", "printhost_cafile", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 20936ba3d4..953a91b689 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1857,14 +1857,17 @@ void TabPrinter::build_fff() { BedShapeDialog dlg(this); dlg.build_dialog(*m_config->option("bed_shape"), - *m_config->option("bed_custom_texture")); + *m_config->option("bed_custom_texture"), + *m_config->option("bed_custom_model")); if (dlg.ShowModal() == wxID_OK) { const std::vector& shape = dlg.get_shape(); const std::string& custom_texture = dlg.get_custom_texture(); + const std::string& custom_model = dlg.get_custom_model(); if (!shape.empty()) { load_key_value("bed_shape", shape); load_key_value("bed_custom_texture", custom_texture); + load_key_value("bed_custom_model", custom_model); update_changed_ui(); } } @@ -2066,14 +2069,17 @@ void TabPrinter::build_sla() { BedShapeDialog dlg(this); dlg.build_dialog(*m_config->option("bed_shape"), - *m_config->option("bed_custom_texture")); + *m_config->option("bed_custom_texture"), + *m_config->option("bed_custom_model")); if (dlg.ShowModal() == wxID_OK) { const std::vector& shape = dlg.get_shape(); const std::string& custom_texture = dlg.get_custom_texture(); + const std::string& custom_model = dlg.get_custom_model(); if (!shape.empty()) { load_key_value("bed_shape", shape); load_key_value("bed_custom_texture", custom_texture); + load_key_value("bed_custom_model", custom_model); update_changed_ui(); } } From 99b8e08e674711b0d8a63919c0ffac2b34f5755f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 18 Jul 2019 12:58:28 +0200 Subject: [PATCH 347/627] Remove arrange cache. --- src/libslic3r/Model.cpp | 45 ++++++++++++++++++----------------------- src/libslic3r/Model.hpp | 32 +++++++++-------------------- 2 files changed, 29 insertions(+), 48 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index ed8be8ce53..cdea83fb71 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1829,33 +1829,28 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const { static const double SIMPLIFY_TOLERANCE_MM = 0.1; - if (!m_arrange_cache.valid) { - Vec3d rotation = get_rotation(); - rotation.z() = 0.; - Transform3d trafo_instance = - Geometry::assemble_transform(Vec3d::Zero(), rotation, - get_scaling_factor(), get_mirror()); - - Polygon p = get_object()->convex_hull_2d(trafo_instance); - - assert(!p.points.empty()); - - // this may happen for malformed models, see: - // https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (!p.points.empty()) { - Polygons pp{p}; - pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); - if (!pp.empty()) p = pp.front(); - } - - m_arrange_cache.poly.contour = std::move(p); - m_arrange_cache.valid = true; - } + Vec3d rotation = get_rotation(); + rotation.z() = 0.; + Transform3d trafo_instance = + Geometry::assemble_transform(Vec3d::Zero(), rotation, + get_scaling_factor(), get_mirror()); + Polygon p = get_object()->convex_hull_2d(trafo_instance); + + assert(!p.points.empty()); + + // this may happen for malformed models, see: + // https://github.com/prusa3d/PrusaSlicer/issues/2209 + if (!p.points.empty()) { + Polygons pp{p}; + pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); + if (!pp.empty()) p = pp.front(); + } + arrangement::ArrangePolygon ret; - ret.poly = m_arrange_cache.poly; - ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}; - ret.rotation = get_rotation(Z); + ret.poly.contour = std::move(p); + ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))}; + ret.rotation = get_rotation(Z); return ret; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index f5786812f7..008699818b 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -555,7 +555,7 @@ public: ModelObject* get_object() const { return this->object; } const Geometry::Transformation& get_transformation() const { return m_transformation; } - void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; m_arrange_cache.valid = false; } + void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } const Vec3d& get_offset() const { return m_transformation.get_offset(); } double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } @@ -566,21 +566,21 @@ public: const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } - void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); m_arrange_cache.valid = false; } - void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); if (axis != Z) m_arrange_cache.valid = false; } + void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } + void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); } double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } - void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); m_arrange_cache.valid = false; } - void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); m_arrange_cache.valid = false; } + void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } + void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } bool is_left_handed() const { return m_transformation.is_left_handed(); } - void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); m_arrange_cache.valid = false; } - void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); m_arrange_cache.valid = false; } + void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } + void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } // To be called on an external mesh void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; @@ -623,18 +623,10 @@ private: ModelObject* object; // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject *object) : print_volume_state(PVS_Inside), object(object) - { - assert(this->id().valid()); - get_arrange_polygon(); // initialize the cache - } + explicit ModelInstance(ModelObject *object) : print_volume_state(PVS_Inside), object(object) { assert(this->id().valid()); } // Constructor, which assigns a new unique ID. explicit ModelInstance(ModelObject *object, const ModelInstance &other) : - m_transformation(other.m_transformation), print_volume_state(PVS_Inside), object(object) - { - assert(this->id().valid() && this->id() != other.id()); - get_arrange_polygon(); // initialize the cache - } + m_transformation(other.m_transformation), print_volume_state(PVS_Inside), object(object) { assert(this->id().valid() && this->id() != other.id()); } explicit ModelInstance(ModelInstance &&rhs) = delete; ModelInstance& operator=(const ModelInstance &rhs) = delete; @@ -647,12 +639,6 @@ private: template void serialize(Archive &ar) { ar(m_transformation, print_volume_state); } - - // Warning! This object is not guarded against concurrency. - mutable struct ArrangeCache { - bool valid = false; - ExPolygon poly; - } m_arrange_cache; }; // The print bed content. From f61d43de07ae973e701a5c516be344b251d44bfb Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 18 Jul 2019 14:39:19 +0200 Subject: [PATCH 348/627] Render custom bed textures in svg format on prusa beds --- src/slic3r/GUI/3DBed.cpp | 46 ++++++++++++++++++++++++++++++--------- src/slic3r/GUI/3DBed.hpp | 3 ++- src/slic3r/GUI/Plater.cpp | 25 ++++++++++++++------- src/slic3r/GUI/Preset.cpp | 1 + 4 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 482cd58adc..873da7116d 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -14,6 +14,7 @@ #include #include +#include static const float GROUND_Z = -0.02f; @@ -274,6 +275,7 @@ void Bed3D::Axes::render_axis(double length) const Bed3D::Bed3D() : m_type(Custom) + , m_custom_texture("") #if ENABLE_TEXTURES_FROM_SVG , m_requires_canvas_update(false) , m_vbo_id(0) @@ -282,14 +284,25 @@ Bed3D::Bed3D() { } -bool Bed3D::set_shape(const Pointfs& shape) +bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture) { EType new_type = detect_type(shape); - if (m_shape == shape && m_type == new_type) + + // check that the passed custom texture filename is valid + std::string cst_texture(custom_texture); + if (!cst_texture.empty()) + { + std::string ext = boost::filesystem::path(cst_texture).extension().string(); + if ((!boost::iequals(ext.c_str(), ".png") && !boost::iequals(ext.c_str(), ".svg")) || !boost::filesystem::exists(custom_texture)) + cst_texture = ""; + } + + if ((m_shape == shape) && (m_type == new_type) && (m_custom_texture == cst_texture)) // No change, no need to update the UI. return false; m_shape = shape; + m_custom_texture = cst_texture; m_type = new_type; calc_bounding_boxes(); @@ -498,26 +511,39 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const #if ENABLE_TEXTURES_FROM_SVG void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom) const { - std::string tex_path = resources_dir() + "/icons/bed/" + key; + std::string filename = !m_custom_texture.empty() ? m_custom_texture : resources_dir() + "/icons/bed/" + key + ".svg"; std::string model_path = resources_dir() + "/models/" + key; // use higher resolution images if graphic card and opengl version allow GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); - std::string filename = tex_path + ".svg"; - if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) { - // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8)) + std::string ext = boost::filesystem::path(filename).extension().string(); + if (boost::iequals(ext.c_str(), ".svg")) { + // generate a temporary lower resolution texture to show while no main texture levels have been compressed + if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8)) + { + render_custom(); + return; + } + + // starts generating the main texture, compression will run asynchronously + if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size)) + { + render_custom(); + return; + } + } + else if (boost::iequals(ext.c_str(), ".png")) + { + std::cout << "texture: " << filename << std::endl; render_custom(); return; } - - // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size)) + else { render_custom(); return; diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 6c8913a9ba..186282997f 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -87,6 +87,7 @@ public: private: EType m_type; Pointfs m_shape; + std::string m_custom_texture; mutable BoundingBoxf3 m_bounding_box; mutable BoundingBoxf3 m_extended_bounding_box; Polygon m_polygon; @@ -122,7 +123,7 @@ public: const Pointfs& get_shape() const { return m_shape; } // Return true if the bed shape changed, so the calee will update the UI. - bool set_shape(const Pointfs& shape); + bool set_shape(const Pointfs& shape, const std::string& custom_texture); const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; } bool contains(const Point& point) const; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8fefca00c1..ef3cbb9659 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1700,7 +1700,7 @@ struct Plater::priv // triangulate the bed and store the triangles into m_bed.m_triangles, // fills the m_bed.m_grid_lines and sets m_bed.m_origin. // Sets m_bed.m_polygon to limit the object placement. - void set_bed_shape(const Pointfs& shape); + void set_bed_shape(const Pointfs& shape, const std::string& custom_texture); bool can_delete() const; bool can_delete_all() const; @@ -1750,7 +1750,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : q(q) , main_frame(main_frame) , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ - "bed_shape", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", + "bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", "brim_width", "variable_layer_height", "serial_port", "serial_speed", "host_type", "print_host", "printhost_apikey", "printhost_cafile", "nozzle_diameter", "single_extruder_multi_material", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", @@ -1853,11 +1853,19 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this); view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); }); - view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values); }); + view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) + { + set_bed_shape(config->option("bed_shape")->values, + config->option("bed_custom_texture")->value); + }); // Preview events: preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); - preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values); }); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) + { + set_bed_shape(config->option("bed_shape")->values, + config->option("bed_custom_texture")->value); + }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); }); @@ -3504,9 +3512,9 @@ bool Plater::priv::can_mirror() const return get_selection().is_from_single_instance(); } -void Plater::priv::set_bed_shape(const Pointfs& shape) +void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture) { - bool new_shape = bed.set_shape(shape); + bool new_shape = bed.set_shape(shape, custom_texture); if (new_shape) { if (view3D) view3D->bed_shape_changed(); @@ -4251,7 +4259,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) p->config->set_key_value(opt_key, config.option(opt_key)->clone()); if (opt_key == "printer_technology") this->set_printer_technology(config.opt_enum(opt_key)); - else if (opt_key == "bed_shape") { + else if ((opt_key == "bed_shape") || (opt_key == "bed_custom_texture")) { bed_shape_changed = true; update_scheduled = true; } @@ -4285,7 +4293,8 @@ void Plater::on_config_change(const DynamicPrintConfig &config) } if (bed_shape_changed) - p->set_bed_shape(p->config->option("bed_shape")->values); + p->set_bed_shape(p->config->option("bed_shape")->values, + p->config->option("bed_custom_texture")->value); if (update_scheduled) update(); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 635b21ba97..e34c9be285 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -512,6 +512,7 @@ const std::vector& Preset::sla_printer_options() if (s_opts.empty()) { s_opts = { "printer_technology", + "bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height", "bed_shape", "max_print_height", "display_width", "display_height", "display_pixels_x", "display_pixels_y", "display_mirror_x", "display_mirror_y", From 1c5ff3c72d62d346940f8a1864f6b1a43a2021f1 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 18 Jul 2019 15:03:05 +0200 Subject: [PATCH 349/627] Small refactoring --- src/slic3r/GUI/3DBed.cpp | 8 +++----- src/slic3r/GUI/BedShapeDialog.cpp | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 873da7116d..f07b224acf 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -292,8 +292,7 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture) std::string cst_texture(custom_texture); if (!cst_texture.empty()) { - std::string ext = boost::filesystem::path(cst_texture).extension().string(); - if ((!boost::iequals(ext.c_str(), ".png") && !boost::iequals(ext.c_str(), ".svg")) || !boost::filesystem::exists(custom_texture)) + if ((!boost::algorithm::iends_with(custom_texture, ".png") && !boost::algorithm::iends_with(custom_texture, ".svg")) || !boost::filesystem::exists(custom_texture)) cst_texture = ""; } @@ -520,8 +519,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) { - std::string ext = boost::filesystem::path(filename).extension().string(); - if (boost::iequals(ext.c_str(), ".svg")) + if (boost::algorithm::iends_with(filename, ".svg")) { // generate a temporary lower resolution texture to show while no main texture levels have been compressed if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8)) @@ -537,7 +535,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom return; } } - else if (boost::iequals(ext.c_str(), ".png")) + else if (boost::algorithm::iends_with(filename, ".png")) { std::cout << "texture: " << filename << std::endl; render_custom(); diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 4ae91c7828..60b4f37f16 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -449,7 +449,7 @@ void BedShapePanel::load_stl() return; std::string file_name = dialog.GetPath().ToUTF8().data(); - if (!boost::iequals(boost::filesystem::path(file_name).extension().string().c_str(), ".stl")) + if (!boost::algorithm::iends_with(file_name, ".stl")) { show_error(this, _(L("Invalid file format."))); return; @@ -498,9 +498,7 @@ void BedShapePanel::load_texture() m_custom_texture = NONE; std::string file_name = dialog.GetPath().ToUTF8().data(); - std::string file_ext = boost::filesystem::path(file_name).extension().string(); - - if (!boost::iequals(file_ext.c_str(), ".png") && !boost::iequals(file_ext.c_str(), ".svg")) + if (!boost::algorithm::iends_with(file_name, ".png") && !boost::algorithm::iends_with(file_name, ".svg")) { show_error(this, _(L("Invalid file format."))); return; @@ -523,7 +521,7 @@ void BedShapePanel::load_model() m_custom_model = NONE; std::string file_name = dialog.GetPath().ToUTF8().data(); - if (!boost::iequals(boost::filesystem::path(file_name).extension().string().c_str(), ".stl")) + if (!boost::algorithm::iends_with(file_name, ".stl")) { show_error(this, _(L("Invalid file format."))); return; From e3ca95152c7754f6700d06eee2100bf71a432d29 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 18 Jul 2019 16:30:32 +0200 Subject: [PATCH 350/627] Minor refactor --- src/libslic3r/Arrange.hpp | 5 ++++- src/slic3r/GUI/Plater.cpp | 10 +++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 3391eb0d75..c02393dd9e 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -127,7 +127,7 @@ public: }; /// A logical bed representing an object not being arranged. Either the arrange -/// has not yet succesfully run on this ArrangePolygon or it could not fit the +/// has not yet successfully run on this ArrangePolygon or it could not fit the /// object due to overly large size or invalid geometry. static const constexpr int UNARRANGED = -1; @@ -152,6 +152,9 @@ struct ArrangePolygon { /// Helper function to call the setter with the arrange data arguments void apply() const { if (setter) setter(*this); } + + /// Test if arrange() was called previously and gave a successful result. + bool is_arranged() const { return bed_idx != UNARRANGED; } }; using ArrangePolygons = std::vector; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0c7a8a3eeb..d4697e7b30 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1345,7 +1345,6 @@ struct Plater::priv // Cache the wti info class WipeTower: public GLCanvas3D::WipeTowerInfo { using ArrangePolygon = arrangement::ArrangePolygon; - static const constexpr auto UNARRANGED = arrangement::UNARRANGED; friend priv; public: @@ -1535,7 +1534,6 @@ struct Plater::priv // 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.; - static const constexpr int UNARRANGED = arrangement::UNARRANGED; ArrangePolygons m_selected, m_unselected; @@ -1578,7 +1576,7 @@ struct Plater::priv ap.bed_idx = ap.translation.x() / stride; ap.setter = [mi, stride](const ArrangePolygon &p) { - if (p.bed_idx != UNARRANGED) { + if (p.is_arranged()) { auto t = p.translation; t.x() += p.bed_idx * stride; mi->apply_arrange_result(t, p.rotation); } @@ -1596,8 +1594,10 @@ struct Plater::priv ap.bed_idx = ap.translation.x() / stride; ap.priority = 1; // Wipe tower should be on physical bed ap.setter = [&wti, stride](const ArrangePolygon &p) { - auto t = p.translation; t.x() += p.bed_idx * stride; - wti.apply_arrange_result(t, p.rotation); + if (p.is_arranged()) { + auto t = p.translation; t.x() += p.bed_idx * stride; + wti.apply_arrange_result(t, p.rotation); + } }; sel.is_wipe_tower() ? From 76b1fbc5bf7ae42a0ea826afc6a6d5ae070f32ec Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 18 Jul 2019 16:32:04 +0200 Subject: [PATCH 351/627] Eliminate some igl warnings on msvc --- src/libslic3r/SLA/SLAAutoSupports.cpp | 2 +- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index 91b7dfe5d1..78efd08067 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -386,7 +386,7 @@ static inline std::vector poisson_disk_from_samples(const std::vector #include #include #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif #include From 6ae50a710a97e8379cb2f85392b0a6c90abd7d40 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 18 Jul 2019 17:31:11 +0200 Subject: [PATCH 352/627] Further refactoring --- src/libnest2d/include/libnest2d.h | 4 +-- src/libnest2d/include/libnest2d/libnest2d.hpp | 34 +++++++++---------- src/libnest2d/tests/test.cpp | 4 +-- src/libslic3r/Arrange.cpp | 8 ++--- src/slic3r/GUI/Plater.cpp | 2 +- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/libnest2d/include/libnest2d.h b/src/libnest2d/include/libnest2d.h index 5f7a29dfbe..4661b45744 100644 --- a/src/libnest2d/include/libnest2d.h +++ b/src/libnest2d/include/libnest2d.h @@ -65,7 +65,7 @@ void nest(Iterator from, Iterator to, const typename Placer::Config& pconf = {}, const typename Selector::Config& sconf = {}) { - Nester nester(bin, dist, pconf, sconf); + _Nester nester(bin, dist, pconf, sconf); nester.execute(from, to); } @@ -80,7 +80,7 @@ void nest(Iterator from, Iterator to, const typename Placer::Config& pconf = {}, const typename Selector::Config& sconf = {}) { - Nester nester(bin, dist, pconf, sconf); + _Nester nester(bin, dist, pconf, sconf); if(prg) nester.progressIndicator(prg); if(scond) nester.stopCondition(scond); nester.execute(from, to); diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index d0d91838f2..29d52c10f8 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -741,12 +741,11 @@ public: }; /** - * The Arranger is the front-end class for the libnest2d library. It takes the - * input items and outputs the items with the proper transformations to be - * inside the provided bin. + * The _Nester is the front-end class for the libnest2d library. It takes the + * input items and changes their transformations to be inside the provided bin. */ template -class Nester { +class _Nester { using TSel = SelectionStrategyLike; TSel selector_; public: @@ -771,12 +770,12 @@ private: using TSItem = remove_cvref_t; StopCondition stopfn_; - - template using TVal = remove_cvref_t; + + template using TVal = remove_ref_t; template - using ConvertibleOnly = - enable_if_t< std::is_convertible, TPItem>::value, void>; + using ItemIteratorOnly = + enable_if_t&, TPItem&>::value, Out>; public: @@ -789,10 +788,8 @@ public: template - Nester( TBinType&& bin, - Coord min_obj_distance = 0, - const PConf& pconfig = PConf(), - const SConf& sconfig = SConf()): + _Nester(TBinType&& bin, Coord min_obj_distance = 0, + const PConf& pconfig = PConf(), const SConf& sconfig = SConf()): bin_(std::forward(bin)), pconfig_(pconfig), min_obj_distance_(min_obj_distance) @@ -817,14 +814,17 @@ public: } /** - * \brief Arrange an input sequence and return a PackGroup object with - * the packed groups corresponding to the bins. + * \brief Arrange an input sequence of _Item-s. + * + * To get the result, call the translation(), rotation() and binId() + * methods of each item. If only the transformed polygon is needed, call + * transformedShape() to get the properly transformed shapes. * * The number of groups in the pack group is the number of bins opened by * the selection algorithm. */ template - inline ConvertibleOnly execute(It from, It to) + inline ItemIteratorOnly execute(It from, It to) { auto infl = static_cast(std::ceil(min_obj_distance_/2.0)); if(infl > 0) std::for_each(from, to, [this, infl](Item& item) { @@ -840,13 +840,13 @@ public: } /// Set a progress indicator function object for the selector. - inline Nester& progressIndicator(ProgressFunction func) + inline _Nester& progressIndicator(ProgressFunction func) { selector_.progressIndicator(func); return *this; } /// Set a predicate to tell when to abort nesting. - inline Nester& stopCondition(StopCondition fn) + inline _Nester& stopCondition(StopCondition fn) { stopfn_ = fn; selector_.stopCondition(fn); return *this; } diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 6891b16e14..29577344d1 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -370,7 +370,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) ASSERT_EQ(getX(bin.center()), 105); ASSERT_EQ(getY(bin.center()), 125); - Nester arrange(bin); + _Nester arrange(bin); arrange.execute(rects.begin(), rects.end()); @@ -438,7 +438,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) Coord min_obj_distance = 5; - Nester arrange(bin, min_obj_distance); + _Nester arrange(bin, min_obj_distance); arrange.execute(rects.begin(), rects.end()); diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index bc3eedb63c..b02efa55c1 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -66,10 +66,6 @@ using Circle = _Circle; using Segment = _Segment; using MultiPolygon = TMultiShape; -// The return value of nesting, a vector (for each logical bed) of Item -// reference vectors. -using PackGroup = _PackGroup; - // Summon the spatial indexing facilities from boost namespace bgi = boost::geometry::index; using SpatElement = std::pair; @@ -102,7 +98,7 @@ void fillConfig(PConf& pcfg) { pcfg.parallel = true; } -// Apply penality to object function result. This is used only when alignment +// Apply penalty to object function result. This is used only when alignment // after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN) double fixed_overfit(const std::tuple& result, const Box &binbb) { @@ -123,7 +119,7 @@ public: // Useful type shortcuts... using Placer = typename placers::_NofitPolyPlacer; using Selector = selections::_FirstFitSelection; - using Packer = Nester; + using Packer = _Nester; using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d4697e7b30..58f0db2928 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1609,7 +1609,7 @@ struct Plater::priv if (m_selected.empty()) m_selected.swap(m_unselected); // The strides have to be removed from the fixed items. For the - // arrangeable (selected) items it bed_idx is ignored and 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; } From a0ea96968d9097ad2f329e1e34bf207d955bed04 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 18 Jul 2019 17:41:47 +0200 Subject: [PATCH 353/627] Storing the active printer technology onto the Undo / Redo stack, remembering the last selected Printer profile for the SLA and FDM technologies separately, and activating them on Undo / Redo. When switching the technologies, user is asked whether to discard the modified profiles or not. --- src/libslic3r/Model.cpp | 25 ++++ src/libslic3r/Model.hpp | 6 + src/slic3r/GUI/GUI.cpp | 3 +- src/slic3r/GUI/GUI_App.cpp | 12 +- src/slic3r/GUI/GUI_App.hpp | 4 +- src/slic3r/GUI/GUI_ObjectList.cpp | 15 +- src/slic3r/GUI/GUI_ObjectList.hpp | 1 - src/slic3r/GUI/GUI_ObjectManipulation.cpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 146 +++++++++---------- src/slic3r/GUI/Tab.cpp | 2 +- src/slic3r/Utils/UndoRedo.cpp | 20 +-- src/slic3r/Utils/UndoRedo.hpp | 14 +- 14 files changed, 141 insertions(+), 119 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 949f82c0a5..66532e9ead 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1877,6 +1877,31 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO return false; } +extern bool model_has_multi_part_objects(const Model &model) +{ + for (const ModelObject *model_object : model.objects) + if (model_object->volumes.size() != 1 || ! model_object->volumes.front()->is_model_part()) + return true; + return false; +} + +extern bool model_has_advanced_features(const Model &model) +{ + auto config_is_advanced = [](const DynamicPrintConfig &config) { + return ! (config.empty() || (config.size() == 1 && config.cbegin()->first == "extruder")); + }; + for (const ModelObject *model_object : model.objects) { + // Is there more than one instance or advanced config data? + if (model_object->instances.size() > 1 || config_is_advanced(model_object->config)) + return true; + // Is there any modifier or advanced config data? + for (const ModelVolume* model_volume : model_object->volumes) + if (! model_volume->is_model_part() || config_is_advanced(model_volume->config)) + return true; + } + return false; +} + #ifndef NDEBUG // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. void check_model_ids_validity(const Model &model) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index c026863ecb..2b4a6d978a 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -761,6 +761,12 @@ extern bool model_object_list_extended(const Model &model_old, const Model &mode // than the old ModelObject. extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type); +// If the model has multi-part objects, then it is currently not supported by the SLA mode. +// Either the model cannot be loaded, or a SLA printer has to be activated. +extern bool model_has_multi_part_objects(const Model &model); +// If the model has advanced features, then it cannot be processed in simple mode. +extern bool model_has_advanced_features(const Model &model); + #ifndef NDEBUG // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. void check_model_ids_validity(const Model &model); diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 9a641c7c04..8f41ed5a39 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -135,8 +135,7 @@ void config_wizard(int reason) wxGetApp().load_current_presets(); - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && - wxGetApp().obj_list()->has_multi_part_objects()) + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && model_has_multi_part_objects(wxGetApp().model())) { show_info(nullptr, _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b4e7bc2b2a..02290e83d9 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -854,7 +854,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) // This is called when closing the application, when loading a config file or when starting the config wizard // to notify the user whether he is aware that some preset changes will be lost. -bool GUI_App::check_unsaved_changes() +bool GUI_App::check_unsaved_changes(const wxString &header) { wxString dirty; PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); @@ -868,8 +868,12 @@ bool GUI_App::check_unsaved_changes() // No changes, the application may close or reload presets. return true; // Ask the user. + wxString message; + if (! header.empty()) + message = header + "\n\n"; + message += _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?")); wxMessageDialog dialog(mainframe, - _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?")), + message, wxString(SLIC3R_APP_NAME) + " - " + _(L("Unsaved Presets")), wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); return dialog.ShowModal() == wxID_YES; @@ -944,9 +948,9 @@ Plater* GUI_App::plater() return plater_; } -ModelObjectPtrs* GUI_App::model_objects() +Model& GUI_App::model() { - return &plater_->model().objects; + return plater_->model(); } wxNotebook* GUI_App::tab_panel() const diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 2f0e8bcf3b..8d0bcfe2f1 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -139,7 +139,7 @@ public: void update_mode(); void add_config_menu(wxMenuBar *menu); - bool check_unsaved_changes(); + bool check_unsaved_changes(const wxString &header = wxString()); bool checked_tab(Tab* tab); void load_current_presets(); @@ -158,7 +158,7 @@ public: ObjectList* obj_list(); ObjectLayers* obj_layers(); Plater* plater(); - std::vector *model_objects(); + Model& model(); AppConfig* app_config{ nullptr }; PresetBundle* preset_bundle{ nullptr }; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 3f3256c420..f7abb41281 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2603,7 +2603,7 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay void ObjectList::init_objects() { - m_objects = wxGetApp().model_objects(); + m_objects = &wxGetApp().model().objects; } bool ObjectList::multiple_selection() const @@ -3080,19 +3080,6 @@ void ObjectList::last_volume_is_deleted(const int obj_idx) volume->config.set_key_value("extruder", new ConfigOptionInt(0)); } -bool ObjectList::has_multi_part_objects() -{ - if (!m_objects_model->IsEmpty()) { - wxDataViewItemArray items; - m_objects_model->GetChildren(wxDataViewItem(0), items); - - for (auto& item : items) - if (m_objects_model->GetItemByType(item, itVolume)) - return true; - } - return false; -} - /* #lm_FIXME_delete_after_testing void ObjectList::update_settings_items() { diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 2d7e9f5f16..5f5d5d6253 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -317,7 +317,6 @@ public: void change_part_type(); void last_volume_is_deleted(const int obj_idx); - bool has_multi_part_objects(); void update_settings_items(); void update_and_show_object_settings_item(); void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index fcb047139c..308b3f208d 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -25,7 +25,7 @@ static double get_volume_min_z(const GLVolume* volume) const Transform3f& world_matrix = volume->world_matrix().cast(); // need to get the ModelVolume pointer - const ModelObject* mo = wxGetApp().model_objects()->at(volume->composite_id.object_id); + const ModelObject* mo = wxGetApp().model().objects[volume->composite_id.object_id]; const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id]; const TriangleMesh& hull = mv->get_convex_hull(); @@ -466,7 +466,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.; } else { m_new_rotation = volume->get_instance_rotation() * (180. / M_PI); - m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct((*wxGetApp().model_objects())[volume->object_idx()]->raw_mesh_bounding_box().size()); + m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); m_new_scale = volume->get_instance_scaling_factor() * 100.; } @@ -779,7 +779,7 @@ void ObjectManipulation::change_size_value(int axis, double value) else if (selection.is_single_full_instance()) ref_size = m_world_coordinates ? selection.get_unscaled_instance_bounding_box().size() : - (*wxGetApp().model_objects())[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); + wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); @@ -902,7 +902,7 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) return; } // Bake the rotation into the meshes of the object. - (*wxGetApp().model_objects())[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id); + wxGetApp().model().objects[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id); // Update the 3D scene, selections etc. wxGetApp().plater()->update(); // Recalculate cached values at this panel, refresh the screen. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index cb996a1041..5a42cbd31f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -29,7 +29,7 @@ void GLGizmoFlatten::on_set_state() // m_model_object pointer can be invalid (for instance because of undo/redo action), // we should recover it from the object id m_model_object = nullptr; - for (const auto mo : *wxGetApp().model_objects()) { + for (const auto mo : wxGetApp().model().objects) { if (mo->id() == m_model_object_id) { m_model_object = mo; break; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 833ba3ee24..8027906a9b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -1049,7 +1049,7 @@ void GLGizmoSlaSupports::on_set_state() // we should recover it from the object id const ModelObject* old_model_object = m_model_object; m_model_object = nullptr; - for (const auto mo : *wxGetApp().model_objects()) { + for (const auto mo : wxGetApp().model().objects) { if (mo->id() == m_current_mesh_object_id) { m_model_object = mo; break; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7c7488c6ad..559145ebae 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1646,16 +1646,18 @@ struct Plater::priv if (this->m_prevent_snapshots > 0) return; assert(this->m_prevent_snapshots >= 0); - this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager()); + this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology); this->undo_redo_stack.release_least_recently_used(); + // Save the last active preset name of a particular printer technology. + ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info(); } void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } int get_active_snapshot_index(); void undo(); void redo(); - void undo_to(size_t time_to_load); - void redo_to(size_t time_to_load); + void undo_redo_to(size_t time_to_load); + void suppress_snapshots() { this->m_prevent_snapshots++; } void allow_snapshots() { this->m_prevent_snapshots--; } @@ -1749,10 +1751,13 @@ private: void update_fff_scene(); void update_sla_scene(); + void undo_redo_to(std::vector::const_iterator it_snapshot); void update_after_undo_redo(bool temp_snapshot_was_taken = false); // path to project file stored with no extension wxString m_project_filename; + std::string m_last_fff_printer_profile_name; + std::string m_last_sla_printer_profile_name; }; const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase); @@ -2102,66 +2107,22 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } } - else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf)) - { - bool advanced = false; - for (const ModelObject* model_object : model.objects) + else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) { + wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")), + _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_YES) { - // is there more than one instance ? - if (model_object->instances.size() > 1) - { - advanced = true; - break; - } - - // is there any advanced config data ? - auto opt_keys = model_object->config.keys(); - if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) - { - advanced = true; - break; - } - - // is there any modifier ? - for (const ModelVolume* model_volume : model_object->volumes) - { - if (!model_volume->is_model_part()) - { - advanced = true; - break; - } - - // is there any advanced config data ? - opt_keys = model_volume->config.keys(); - if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder"))) - { - advanced = true; - break; - } - } - - if (advanced) - break; - } - - if (advanced) - { - wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")), - _(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO); - if (dlg.ShowModal() == wxID_YES) - { - Slic3r::GUI::wxGetApp().save_mode(comAdvanced); - view3D->set_as_dirty(); - } - else - return obj_idxs; + Slic3r::GUI::wxGetApp().save_mode(comAdvanced); + view3D->set_as_dirty(); } + else + return obj_idxs; } - for (ModelObject* model_object : model.objects) { - model_object->center_around_origin(false); - model_object->ensure_on_bed(); - } + for (ModelObject* model_object : model.objects) { + model_object->center_around_origin(false); + model_object->ensure_on_bed(); + } // check multi-part object adding for the SLA-printing if (printer_technology == ptSLA) @@ -3629,28 +3590,58 @@ int Plater::priv::get_active_snapshot_index() void Plater::priv::undo() { - bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active(); - if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager())) - this->update_after_undo_redo(temp_snapshot_was_taken); + const std::vector &snapshots = this->undo_redo_stack.snapshots(); + auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack.active_snapshot_time())); + if (-- it_current != snapshots.begin()) + this->undo_redo_to(it_current); } void Plater::priv::redo() { - if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager())) - this->update_after_undo_redo(); + const std::vector &snapshots = this->undo_redo_stack.snapshots(); + auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack.active_snapshot_time())); + if (++ it_current != snapshots.end()) + this->undo_redo_to(it_current); } -void Plater::priv::undo_to(size_t time_to_load) +void Plater::priv::undo_redo_to(size_t time_to_load) { - bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active(); - if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load)) - this->update_after_undo_redo(temp_snapshot_was_taken); + const std::vector &snapshots = this->undo_redo_stack.snapshots(); + auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(time_to_load)); + assert(it_current != snapshots.end()); + this->undo_redo_to(it_current); } -void Plater::priv::redo_to(size_t time_to_load) -{ - if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load)) - this->update_after_undo_redo(); +void Plater::priv::undo_redo_to(std::vector::const_iterator it_snapshot) +{ + bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active(); + PrinterTechnology new_printer_technology = it_snapshot->printer_technology; + bool printer_technology_changed = this->printer_technology != new_printer_technology; + if (printer_technology_changed) { + // Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type. + std::string s_pt = (it_snapshot->printer_technology == ptFFF) ? "FFF" : "SLA"; + if (! wxGetApp().check_unsaved_changes(from_u8((boost::format(_utf8( + L("%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."))) % s_pt).str()))) + // Don't switch the profiles. + return; + } + // Save the last active preset name of a particular printer technology. + ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); + // Do the jump in time. + if (it_snapshot->timestamp < this->undo_redo_stack.active_snapshot_time() ? + this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, it_snapshot->timestamp) : + this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) { + if (printer_technology_changed) { + // Switch to the other printer technology. Switch to the last printer active for that particular technology. + AppConfig *app_config = wxGetApp().app_config; + app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name); + wxGetApp().preset_bundle->load_presets(*app_config); + // Load the currently selected preset into the GUI, update the preset selection box. + // This also switches the printer technology based on the printer technology of the active printer profile. + wxGetApp().load_current_presets(); + } + this->update_after_undo_redo(temp_snapshot_was_taken); + } } void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */) @@ -3669,6 +3660,13 @@ void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */) wxGetApp().obj_list()->update_after_undo_redo(); + if (wxGetApp().get_mode() == comSimple && model_has_advanced_features(this->model)) { + // If the user jumped to a snapshot that require user interface with advanced features, switch to the advanced mode without asking. + // There is a little risk of surprising the user, as he already must have had the advanced or expert mode active for such a snapshot to be taken. + Slic3r::GUI::wxGetApp().save_mode(comAdvanced); + view3D->set_as_dirty(); + } + //FIXME what about the state of the manipulators? //FIXME what about the focus? Cursor in the side panel? @@ -4230,7 +4228,7 @@ void Plater::undo_to(int selection) } const int idx = p->get_active_snapshot_index() - selection - 1; - p->undo_to(p->undo_redo_stack.snapshots()[idx].timestamp); + p->undo_redo_to(p->undo_redo_stack.snapshots()[idx].timestamp); } void Plater::redo_to(int selection) { @@ -4240,7 +4238,7 @@ void Plater::redo_to(int selection) } const int idx = p->get_active_snapshot_index() + selection + 1; - p->redo_to(p->undo_redo_stack.snapshots()[idx].timestamp); + p->undo_redo_to(p->undo_redo_stack.snapshots()[idx].timestamp); } bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text) { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 7ab564beb0..b52f9dd463 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2904,7 +2904,7 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr // Because of we can't to print the multi-part objects with SLA technology. bool Tab::may_switch_to_SLA_preset() { - if (wxGetApp().obj_list()->has_multi_part_objects()) + if (model_has_multi_part_objects(wxGetApp().model())) { show_info( parent(), _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 10873ea89b..c4a082fb6e 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -495,12 +495,12 @@ public: } // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. - void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos); + void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology); void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos); bool has_undo_snapshot() const; bool has_redo_snapshot() const; - bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, size_t jump_to_time); + bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, size_t jump_to_time); bool redo(Slic3r::Model &model, Slic3r::GUI::GLGizmosManager &gizmos, size_t jump_to_time); void release_least_recently_used(); @@ -788,7 +788,7 @@ template void StackImpl::load_mutable_object(const Sl } // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. -void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos) +void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology) { // Release old snapshot data. assert(m_active_snapshot_time <= m_current_time); @@ -808,11 +808,11 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo this->save_mutable_object(m_selection); this->save_mutable_object(gizmos); // Save the snapshot info. - m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id); + m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id, printer_technology); m_active_snapshot_time = m_current_time; // Save snapshot info of the last "current" aka "top most" state, that is only being serialized // if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet. - m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0); + m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0, printer_technology); // Release empty objects from the history. this->collect_garbage(); assert(this->valid()); @@ -858,7 +858,7 @@ bool StackImpl::has_redo_snapshot() const return ++ it != m_snapshots.end(); } -bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) +bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, size_t time_to_load) { assert(this->valid()); if (time_to_load == SIZE_MAX) { @@ -872,7 +872,7 @@ bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selecti bool new_snapshot_taken = false; if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { // The current state is temporary. The current state needs to be captured to be redoable. - this->take_snapshot(topmost_snapshot_name, model, selection, gizmos); + this->take_snapshot(topmost_snapshot_name, model, selection, gizmos, printer_technology); // The line above entered another topmost_snapshot_name. assert(m_snapshots.back().is_topmost()); assert(! m_snapshots.back().is_topmost_captured()); @@ -1020,10 +1020,12 @@ Stack::~Stack() {} void Stack::set_memory_limit(size_t memsize) { pimpl->set_memory_limit(memsize); } size_t Stack::memsize() const { return pimpl->memsize(); } void Stack::release_least_recently_used() { pimpl->release_least_recently_used(); } -void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos) { pimpl->take_snapshot(snapshot_name, model, selection, gizmos); } +void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology) + { pimpl->take_snapshot(snapshot_name, model, selection, gizmos, printer_technology); } bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); } bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); } -bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->undo(model, selection, gizmos, time_to_load); } +bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, size_t time_to_load) + { return pimpl->undo(model, selection, gizmos, printer_technology, time_to_load); } bool Stack::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->redo(model, gizmos, time_to_load); } const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); } diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 6785cd740a..00ab55822d 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -23,11 +23,13 @@ namespace UndoRedo { struct Snapshot { Snapshot(size_t timestamp) : timestamp(timestamp) {} - Snapshot(const std::string &name, size_t timestamp, size_t model_id) : name(name), timestamp(timestamp), model_id(model_id) {} + Snapshot(const std::string &name, size_t timestamp, size_t model_id, Slic3r::PrinterTechnology printer_technology) : + name(name), timestamp(timestamp), model_id(model_id), printer_technology(printer_technology) {} - std::string name; - size_t timestamp; - size_t model_id; + std::string name; + size_t timestamp; + size_t model_id; + PrinterTechnology printer_technology; bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; } bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; } @@ -66,7 +68,7 @@ public: void release_least_recently_used(); // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. - void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos); + void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology); // To be queried to enable / disable the Undo / Redo buttons at the UI. bool has_undo_snapshot() const; @@ -74,7 +76,7 @@ public: // Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated. // Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible. - bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX); + bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, size_t time_to_load = SIZE_MAX); // Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated. bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX); From 4049f33609858bc2f6884f3e8a1b4c3ec9c87f7d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 18 Jul 2019 18:19:40 +0200 Subject: [PATCH 354/627] Fix of osx builds --- src/libslic3r/Config.hpp | 2 +- src/slic3r/Utils/UndoRedo.hpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 844287efbe..7d96f6cf3d 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -93,7 +93,7 @@ enum ConfigOptionMode { comExpert }; -enum PrinterTechnology +enum PrinterTechnology : unsigned char { // Fused Filament Fabrication ptFFF, diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 00ab55822d..96b539f18d 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -12,6 +12,7 @@ namespace Slic3r { class Model; +enum PrinterTechnology : unsigned char; namespace GUI { class Selection; From ba4bc8ac82b5ae524f2ce62a5b079259483e82f4 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 19 Jul 2019 09:18:09 +0200 Subject: [PATCH 355/627] Render custom bed textures in png format on prusa beds --- resources/shaders/printbed.fs | 22 ++++- src/slic3r/GUI/3DBed.cpp | 33 +++++-- src/slic3r/GUI/GLTexture.cpp | 180 ++++++++++++++++++++++++++++++++++ src/slic3r/GUI/GLTexture.hpp | 2 + 4 files changed, 226 insertions(+), 11 deletions(-) diff --git a/resources/shaders/printbed.fs b/resources/shaders/printbed.fs index be14347c21..d1316ca2fe 100644 --- a/resources/shaders/printbed.fs +++ b/resources/shaders/printbed.fs @@ -5,16 +5,30 @@ const vec3 back_color_light = vec3(0.365, 0.365, 0.365); uniform sampler2D texture; uniform bool transparent_background; +uniform bool svg_source; varying vec2 tex_coords; -void main() +vec4 svg_color() { + // takes foreground from texture + vec4 fore_color = texture2D(texture, tex_coords); + // calculates radial gradient vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coords.xy) - vec2(0.5))))); - vec4 fore_color = texture2D(texture, tex_coords); - // blends foreground with background - gl_FragColor = vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0); + return vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0); +} + +vec4 non_svg_color() +{ + // takes foreground from texture + vec4 color = texture2D(texture, tex_coords); + return vec4(color.rgb, transparent_background ? color.a * 0.25 : color.a); +} + +void main() +{ + gl_FragColor = svg_source ? svg_color() : non_svg_color(); } \ No newline at end of file diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index f07b224acf..27531f030a 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -519,13 +519,18 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) { + m_texture.reset(); + if (boost::algorithm::iends_with(filename, ".svg")) { - // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8)) + if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != filename)) { - render_custom(); - return; + // generate a temporary lower resolution texture to show while no main texture levels have been compressed + if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8)) + { + render_custom(); + return; + } } // starts generating the main texture, compression will run asynchronously @@ -537,9 +542,22 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom } else if (boost::algorithm::iends_with(filename, ".png")) { - std::cout << "texture: " << filename << std::endl; - render_custom(); - return; + // generate a temporary lower resolution texture to show while no main texture levels have been compressed + if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != filename)) + { + if (!m_temp_texture.load_from_file(filename, false, false, false, max_tex_size)) + { + render_custom(); + return; + } + } + + // starts generating the main texture, compression will run asynchronously + if (!m_texture.load_from_file(filename, true, true, true, max_tex_size)) + { + render_custom(); + return; + } } else { @@ -634,6 +652,7 @@ void Bed3D::render_prusa_shader(bool transparent) const { m_shader.start_using(); m_shader.set_uniform("transparent_background", transparent); + m_shader.set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); unsigned int stride = m_triangles.get_vertex_data_size(); diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 171a5c8851..495a9864cb 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -147,6 +147,19 @@ bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, bo return false; } +bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) +{ + reset(); + + if (!boost::filesystem::exists(filename)) + return false; + + if (boost::algorithm::iends_with(filename, ".png")) + return load_from_png(filename, use_mipmaps, compress, apply_anisotropy, max_size_px); + else + return false; +} + bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) { reset(); @@ -465,6 +478,172 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, boo return true; } +bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) +{ + bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc; + + // Load a PNG with an alpha channel. + wxImage image; + if (!image.LoadFile(wxString::FromUTF8(filename.c_str()), wxBITMAP_TYPE_PNG)) + { + reset(); + return false; + } + + m_width = image.GetWidth(); + m_height = image.GetHeight(); + + unsigned int max_size = (unsigned int)std::max(m_width, m_height); + bool requires_rescale = false; + if (max_size_px < max_size) + { + float scale = (float)max_size_px / (float)max_size; + + m_width = (int)(scale * (float)m_width); + m_height = (int)(scale * (float)m_height); + requires_rescale = true; + } + + if (compression_enabled) + { + // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 + int width_rem = m_width % 4; + int height_rem = m_height % 4; + + if (width_rem != 0) + { + m_width += (4 - width_rem); + requires_rescale = true; + } + + if (height_rem != 0) + { + m_height += (4 - height_rem); + requires_rescale = true; + } + } + + if (requires_rescale) + image = image.ResampleBicubic(m_width, m_height); + + int n_pixels = m_width * m_height; + if (n_pixels <= 0) + { + reset(); + return false; + } + + // Get RGB & alpha raw data from wxImage, pack them into an array. + unsigned char* img_rgb = image.GetData(); + if (img_rgb == nullptr) + { + reset(); + return false; + } + + unsigned char* img_alpha = image.GetAlpha(); + + std::vector data(n_pixels * 4, 0); + for (int i = 0; i < n_pixels; ++i) + { + int data_id = i * 4; + int img_id = i * 3; + data[data_id + 0] = img_rgb[img_id + 0]; + data[data_id + 1] = img_rgb[img_id + 1]; + data[data_id + 2] = img_rgb[img_id + 2]; + data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255; + } + + // sends data to gpu + glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + glsafe(::glGenTextures(1, &m_id)); + glsafe(::glBindTexture(GL_TEXTURE_2D, m_id)); + + if (apply_anisotropy) + { + GLfloat max_anisotropy = GLCanvas3DManager::get_gl_info().get_max_anisotropy(); + if (max_anisotropy > 1.0f) + glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); + } + + if (compression_enabled) + { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); + } + else + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + + if (use_mipmaps) + { + // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards + int lod_w = m_width; + int lod_h = m_height; + GLint level = 0; + // we do not need to generate all levels down to 1x1 + while ((lod_w > 16) || (lod_h > 16)) + { + ++level; + + lod_w = std::max(lod_w / 2, 1); + lod_h = std::max(lod_h / 2, 1); + n_pixels = lod_w * lod_h; + + image = image.ResampleBicubic(lod_w, lod_h); + + data.resize(n_pixels * 4); + + img_rgb = image.GetData(); + img_alpha = image.GetAlpha(); + + for (int i = 0; i < n_pixels; ++i) + { + int data_id = i * 4; + int img_id = i * 3; + data[data_id + 0] = img_rgb[img_id + 0]; + data[data_id + 1] = img_rgb[img_id + 1]; + data[data_id + 2] = img_rgb[img_id + 2]; + data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255; + } + + if (compression_enabled) + { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data); + } + else + glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + } + + if (!compression_enabled) + { + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); + } + } + else + { + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); + } + + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + + m_source = filename; + + if (compression_enabled) + // start asynchronous compression + m_compressor.start_compressing(); + + return true; +} + bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) { bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc; @@ -579,6 +758,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); } + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index f4dc05a168..21e01733d6 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -76,6 +76,7 @@ namespace GUI { virtual ~GLTexture(); bool load_from_file(const std::string& filename, bool use_mipmaps, bool compress); + bool load_from_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); // meanings of states: (std::pair) // first field (int): @@ -106,6 +107,7 @@ namespace GUI { private: bool load_from_png(const std::string& filename, bool use_mipmaps, bool compress); + bool load_from_png(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); friend class Compressor; From 84d61e28fdcc15d337d960fcade06e627b27d57f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 19 Jul 2019 09:52:01 +0200 Subject: [PATCH 356/627] Tech ENABLE_TEXTURES_FROM_SVG set as default --- src/libslic3r/Technologies.hpp | 9 -- src/slic3r/GUI/3DBed.cpp | 263 --------------------------------- src/slic3r/GUI/3DBed.hpp | 30 ---- src/slic3r/GUI/GLCanvas3D.cpp | 85 ----------- src/slic3r/GUI/GLCanvas3D.hpp | 26 ---- src/slic3r/GUI/GLShader.cpp | 2 - src/slic3r/GUI/GLShader.hpp | 2 - 7 files changed, 417 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index ae43369d22..51d0920946 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -32,13 +32,4 @@ #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1) -//==================== -// 1.42.0.alpha7 techs -//==================== -#define ENABLE_1_42_0_ALPHA7 1 - -// Printbed textures generated from svg files -#define ENABLE_TEXTURES_FROM_SVG (1 && ENABLE_1_42_0_ALPHA7) - - #endif // _technologies_h_ diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 27531f030a..89ad5a4ad5 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -23,7 +23,6 @@ namespace GUI { bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords) { -#if ENABLE_TEXTURES_FROM_SVG m_vertices.clear(); unsigned int v_size = 3 * (unsigned int)triangles.size(); @@ -83,75 +82,12 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool } } } -#else - m_vertices.clear(); - m_tex_coords.clear(); - - unsigned int v_size = 9 * (unsigned int)triangles.size(); - unsigned int t_size = 6 * (unsigned int)triangles.size(); - if (v_size == 0) - return false; - - m_vertices = std::vector(v_size, 0.0f); - if (generate_tex_coords) - m_tex_coords = std::vector(t_size, 0.0f); - - float min_x = unscale(triangles[0].points[0](0)); - float min_y = unscale(triangles[0].points[0](1)); - float max_x = min_x; - float max_y = min_y; - - unsigned int v_coord = 0; - unsigned int t_coord = 0; - for (const Polygon& t : triangles) - { - for (unsigned int v = 0; v < 3; ++v) - { - const Point& p = t.points[v]; - float x = unscale(p(0)); - float y = unscale(p(1)); - - m_vertices[v_coord++] = x; - m_vertices[v_coord++] = y; - m_vertices[v_coord++] = z; - - if (generate_tex_coords) - { - m_tex_coords[t_coord++] = x; - m_tex_coords[t_coord++] = y; - - min_x = std::min(min_x, x); - max_x = std::max(max_x, x); - min_y = std::min(min_y, y); - max_y = std::max(max_y, y); - } - } - } - - if (generate_tex_coords) - { - float size_x = max_x - min_x; - float size_y = max_y - min_y; - - if ((size_x != 0.0f) && (size_y != 0.0f)) - { - float inv_size_x = 1.0f / size_x; - float inv_size_y = -1.0f / size_y; - for (unsigned int i = 0; i < m_tex_coords.size(); i += 2) - { - m_tex_coords[i] = (m_tex_coords[i] - min_x) * inv_size_x; - m_tex_coords[i + 1] = (m_tex_coords[i + 1] - min_y) * inv_size_y; - } - } - } -#endif // ENABLE_TEXTURES_FROM_SVG return true; } bool GeometryBuffer::set_from_lines(const Lines& lines, float z) { -#if ENABLE_TEXTURES_FROM_SVG m_vertices.clear(); unsigned int v_size = 2 * (unsigned int)lines.size(); @@ -175,37 +111,14 @@ bool GeometryBuffer::set_from_lines(const Lines& lines, float z) v2.position[2] = z; ++v_count; } -#else - m_vertices.clear(); - m_tex_coords.clear(); - - unsigned int size = 6 * (unsigned int)lines.size(); - if (size == 0) - return false; - - m_vertices = std::vector(size, 0.0f); - - unsigned int coord = 0; - for (const Line& l : lines) - { - m_vertices[coord++] = unscale(l.a(0)); - m_vertices[coord++] = unscale(l.a(1)); - m_vertices[coord++] = z; - m_vertices[coord++] = unscale(l.b(0)); - m_vertices[coord++] = unscale(l.b(1)); - m_vertices[coord++] = z; - } -#endif // ENABLE_TEXTURES_FROM_SVG return true; } -#if ENABLE_TEXTURES_FROM_SVG const float* GeometryBuffer::get_vertices_data() const { return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr; } -#endif // ENABLE_TEXTURES_FROM_SVG const double Bed3D::Axes::Radius = 0.5; const double Bed3D::Axes::ArrowBaseRadius = 2.5 * Bed3D::Axes::Radius; @@ -276,10 +189,8 @@ void Bed3D::Axes::render_axis(double length) const Bed3D::Bed3D() : m_type(Custom) , m_custom_texture("") -#if ENABLE_TEXTURES_FROM_SVG , m_requires_canvas_update(false) , m_vbo_id(0) -#endif // ENABLE_TEXTURES_FROM_SVG , m_scale_factor(1.0f) { } @@ -319,9 +230,7 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture) m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour; -#if ENABLE_TEXTURES_FROM_SVG reset(); -#endif // ENABLE_TEXTURES_FROM_SVG // Set the origin and size for painting of the coordinate system axes. m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z); @@ -341,7 +250,6 @@ Point Bed3D::point_projection(const Point& point) const return m_polygon.point_projection(point); } -#if ENABLE_TEXTURES_FROM_SVG void Bed3D::render(GLCanvas3D* canvas, float theta, float scale_factor) const { m_scale_factor = scale_factor; @@ -371,40 +279,6 @@ void Bed3D::render(GLCanvas3D* canvas, float theta, float scale_factor) const } } } -#else -void Bed3D::render(float theta, float scale_factor) const -{ - m_scale_factor = scale_factor; - - if (m_shape.empty()) - return; - - switch (m_type) - { - case MK2: - { - render_prusa("mk2", theta); - break; - } - case MK3: - { - render_prusa("mk3", theta); - break; - } - case SL1: - { - render_prusa("sl1", theta); - break; - } - default: - case Custom: - { - render_custom(); - break; - } - } -} -#endif // ENABLE_TEXTURES_FROM_SVG void Bed3D::render_axes() const { @@ -507,7 +381,6 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const return type; } -#if ENABLE_TEXTURES_FROM_SVG void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom) const { std::string filename = !m_custom_texture.empty() ? m_custom_texture : resources_dir() + "/icons/bed/" + key + ".svg"; @@ -692,136 +565,10 @@ void Bed3D::render_prusa_shader(bool transparent) const m_shader.stop_using(); } } -#else -void Bed3D::render_prusa(const std::string& key, float theta) const -{ - std::string tex_path = resources_dir() + "/icons/bed/" + key; - - // use higher resolution images if graphic card allows - GLint max_tex_size; - glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size)); - - // temporary set to lowest resolution - max_tex_size = 2048; - - if (max_tex_size >= 8192) - tex_path += "_8192"; - else if (max_tex_size >= 4096) - tex_path += "_4096"; - - std::string model_path = resources_dir() + "/models/" + key; - - // use anisotropic filter if graphic card allows - GLfloat max_anisotropy = 0.0f; - if (glewIsSupported("GL_EXT_texture_filter_anisotropic")) - glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy)); - - std::string filename = tex_path + "_top.png"; - if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) - { - if (!m_top_texture.load_from_file(filename, true, true)) - { - render_custom(); - return; - } - - if (max_anisotropy > 0.0f) - { - glsafe(::glBindTexture(GL_TEXTURE_2D, m_top_texture.get_id())); - glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - } - } - - filename = tex_path + "_bottom.png"; - if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename)) - { - if (!m_bottom_texture.load_from_file(filename, true, true)) - { - render_custom(); - return; - } - - if (max_anisotropy > 0.0f) - { - glsafe(::glBindTexture(GL_TEXTURE_2D, m_bottom_texture.get_id())); - glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy)); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - } - } - - if (theta <= 90.0f) - { - filename = model_path + "_bed.stl"; - if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { - Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); - if (key == "mk2") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 7.5, -0.03); - else if (key == "mk3") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 5.5, 2.43); - else if (key == "sl1") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 0.0, -0.03); - - m_model.center_around(offset); - } - - if (!m_model.get_filename().empty()) - { - glsafe(::glEnable(GL_LIGHTING)); - m_model.render(); - glsafe(::glDisable(GL_LIGHTING)); - } - } - - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - if (triangles_vcount > 0) - { - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDepthMask(GL_FALSE)); - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - glsafe(::glEnable(GL_TEXTURE_2D)); - glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_TEXTURE_COORD_ARRAY)); - - if (theta > 90.0f) - glsafe(::glFrontFace(GL_CW)); - - glsafe(::glBindTexture(GL_TEXTURE_2D, (theta <= 90.0f) ? (GLuint)m_top_texture.get_id() : (GLuint)m_bottom_texture.get_id())); - glsafe(::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices())); - glsafe(::glTexCoordPointer(2, GL_FLOAT, 0, (GLvoid*)m_triangles.get_tex_coords())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); - - if (theta > 90.0f) - glsafe(::glFrontFace(GL_CCW)); - - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - glsafe(::glDisableClientState(GL_TEXTURE_COORD_ARRAY)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - - glsafe(::glDisable(GL_TEXTURE_2D)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glDepthMask(GL_TRUE)); - } -} -#endif // ENABLE_TEXTURES_FROM_SVG void Bed3D::render_custom() const { -#if ENABLE_TEXTURES_FROM_SVG m_texture.reset(); -#else - m_top_texture.reset(); - m_bottom_texture.reset(); -#endif // ENABLE_TEXTURES_FROM_SVG unsigned int triangles_vcount = m_triangles.get_vertices_count(); if (triangles_vcount > 0) @@ -836,11 +583,7 @@ void Bed3D::render_custom() const glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f)); glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); -#if ENABLE_TEXTURES_FROM_SVG glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data())); -#else - glsafe(::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices())); -#endif // ENABLE_TEXTURES_FROM_SVG glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); // draw grid @@ -850,11 +593,7 @@ void Bed3D::render_custom() const glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glLineWidth(3.0f * m_scale_factor)); glsafe(::glColor4f(0.2f, 0.2f, 0.2f, 0.4f)); -#if ENABLE_TEXTURES_FROM_SVG glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data())); -#else - glsafe(::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_gridlines.get_vertices())); -#endif // ENABLE_TEXTURES_FROM_SVG glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)gridlines_vcount)); glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); @@ -864,7 +603,6 @@ void Bed3D::render_custom() const } } -#if ENABLE_TEXTURES_FROM_SVG void Bed3D::reset() { if (m_vbo_id > 0) @@ -873,7 +611,6 @@ void Bed3D::reset() m_vbo_id = 0; } } -#endif // ENABLE_TEXTURES_FROM_SVG } // GUI } // Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 186282997f..7a89444439 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -3,9 +3,7 @@ #include "GLTexture.hpp" #include "3DScene.hpp" -#if ENABLE_TEXTURES_FROM_SVG #include "GLShader.hpp" -#endif // ENABLE_TEXTURES_FROM_SVG class GLUquadric; typedef class GLUquadric GLUquadricObj; @@ -17,7 +15,6 @@ class GLCanvas3D; class GeometryBuffer { -#if ENABLE_TEXTURES_FROM_SVG struct Vertex { float position[3]; @@ -31,27 +28,17 @@ class GeometryBuffer }; std::vector m_vertices; -#else - std::vector m_vertices; - std::vector m_tex_coords; -#endif // ENABLE_TEXTURES_FROM_SVG public: bool set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords); bool set_from_lines(const Lines& lines, float z); -#if ENABLE_TEXTURES_FROM_SVG const float* get_vertices_data() const; unsigned int get_vertices_data_size() const { return (unsigned int)m_vertices.size() * get_vertex_data_size(); } unsigned int get_vertex_data_size() const { return (unsigned int)(5 * sizeof(float)); } size_t get_position_offset() const { return 0; } size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); } unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); } -#else - const float* get_vertices() const { return m_vertices.data(); } - const float* get_tex_coords() const { return m_tex_coords.data(); } - unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size() / 3; } -#endif // ENABLE_TEXTURES_FROM_SVG }; class Bed3D @@ -93,7 +80,6 @@ private: Polygon m_polygon; GeometryBuffer m_triangles; GeometryBuffer m_gridlines; -#if ENABLE_TEXTURES_FROM_SVG mutable GLTexture m_texture; // temporary texture shown until the main texture has still no levels compressed mutable GLTexture m_temp_texture; @@ -101,10 +87,6 @@ private: mutable bool m_requires_canvas_update; mutable Shader m_shader; mutable unsigned int m_vbo_id; -#else - mutable GLTexture m_top_texture; - mutable GLTexture m_bottom_texture; -#endif // ENABLE_TEXTURES_FROM_SVG mutable GLBed m_model; Axes m_axes; @@ -112,9 +94,7 @@ private: public: Bed3D(); -#if ENABLE_TEXTURES_FROM_SVG ~Bed3D() { reset(); } -#endif // ENABLE_TEXTURES_FROM_SVG EType get_type() const { return m_type; } @@ -129,11 +109,7 @@ public: bool contains(const Point& point) const; Point point_projection(const Point& point) const; -#if ENABLE_TEXTURES_FROM_SVG void render(GLCanvas3D* canvas, float theta, float scale_factor) const; -#else - void render(float theta, float scale_factor) const; -#endif // ENABLE_TEXTURES_FROM_SVG void render_axes() const; private: @@ -141,16 +117,10 @@ private: void calc_triangles(const ExPolygon& poly); void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); EType detect_type(const Pointfs& shape) const; -#if ENABLE_TEXTURES_FROM_SVG void render_prusa(GLCanvas3D* canvas, const std::string& key, bool bottom) const; void render_prusa_shader(bool transparent) const; -#else - void render_prusa(const std::string& key, float theta) const; -#endif // ENABLE_TEXTURES_FROM_SVG void render_custom() const; -#if ENABLE_TEXTURES_FROM_SVG void reset(); -#endif // ENABLE_TEXTURES_FROM_SVG }; } // GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 47b9ad48a4..70544cd37f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -116,87 +116,6 @@ void Size::set_scale_factor(int scale_factor) m_scale_factor = scale_factor; } -#if !ENABLE_TEXTURES_FROM_SVG -GLCanvas3D::Shader::Shader() - : m_shader(nullptr) -{ -} - -GLCanvas3D::Shader::~Shader() -{ - _reset(); -} - -bool GLCanvas3D::Shader::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) -{ - if (is_initialized()) - return true; - - m_shader = new GLShader(); - if (m_shader != nullptr) - { - if (!m_shader->load_from_file(fragment_shader_filename.c_str(), vertex_shader_filename.c_str())) - { - std::cout << "Compilaton of shader failed:" << std::endl; - std::cout << m_shader->last_error << std::endl; - _reset(); - return false; - } - } - - return true; -} - -bool GLCanvas3D::Shader::is_initialized() const -{ - return (m_shader != nullptr); -} - -bool GLCanvas3D::Shader::start_using() const -{ - if (is_initialized()) - { - m_shader->enable(); - return true; - } - else - return false; -} - -void GLCanvas3D::Shader::stop_using() const -{ - if (m_shader != nullptr) - m_shader->disable(); -} - -void GLCanvas3D::Shader::set_uniform(const std::string& name, float value) const -{ - if (m_shader != nullptr) - m_shader->set_uniform(name.c_str(), value); -} - -void GLCanvas3D::Shader::set_uniform(const std::string& name, const float* matrix) const -{ - if (m_shader != nullptr) - m_shader->set_uniform(name.c_str(), matrix); -} - -const GLShader* GLCanvas3D::Shader::get_shader() const -{ - return m_shader; -} - -void GLCanvas3D::Shader::_reset() -{ - if (m_shader != nullptr) - { - m_shader->release(); - delete m_shader; - m_shader = nullptr; - } -} -#endif // !ENABLE_TEXTURES_FROM_SVG - GLCanvas3D::LayersEditing::LayersEditing() : m_enabled(false) , m_z_texture_id(0) @@ -3907,11 +3826,7 @@ void GLCanvas3D::_render_bed(float theta) const #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); #endif // ENABLE_RETINA_GL -#if ENABLE_TEXTURES_FROM_SVG m_bed.render(const_cast(this), theta, scale_factor); -#else - m_bed.render(theta, scale_factor); -#endif // ENABLE_TEXTURES_FROM_SVG } void GLCanvas3D::_render_axes() const diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 6bb17da4a9..cdf45c4f7c 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -159,32 +159,6 @@ class GLCanvas3D void reset() { first_volumes.clear(); } }; -#if !ENABLE_TEXTURES_FROM_SVG - class Shader - { - GLShader* m_shader; - - public: - Shader(); - ~Shader(); - - bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename); - - bool is_initialized() const; - - bool start_using() const; - void stop_using() const; - - void set_uniform(const std::string& name, float value) const; - void set_uniform(const std::string& name, const float* matrix) const; - - const GLShader* get_shader() const; - - private: - void _reset(); - }; -#endif // !ENABLE_TEXTURES_FROM_SVG - class LayersEditing { public: diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 7f3bc0893a..577f6e1b50 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -268,7 +268,6 @@ sub SetMatrix } */ -#if ENABLE_TEXTURES_FROM_SVG Shader::Shader() : m_shader(nullptr) { @@ -363,6 +362,5 @@ void Shader::reset() m_shader = nullptr; } } -#endif // ENABLE_TEXTURES_FROM_SVG } // namespace Slic3r diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index 58e2678d03..df2a23f15c 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -37,7 +37,6 @@ public: std::string last_error; }; -#if ENABLE_TEXTURES_FROM_SVG class Shader { GLShader* m_shader; @@ -66,7 +65,6 @@ public: private: void reset(); }; -#endif // ENABLE_TEXTURES_FROM_SVG } From fca5562c6c35e2b3c087f2457fc2c60984330e25 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 19 Jul 2019 10:02:52 +0200 Subject: [PATCH 357/627] Process start_filament_gcode in case of usual single extruder printer Reported in https://github.com/prusa3d/PrusaSlicer/issues/2652 --- src/libslic3r/GCode.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 37a14d42c2..d5ee6a0484 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2784,7 +2784,17 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) // if we are running a single-extruder setup, just set the extruder and return nothing if (!m_writer.multiple_extruders) { m_placeholder_parser.set("current_extruder", extruder_id); - return m_writer.toolchange(extruder_id); + + std::string gcode; + // Append the filament start G-code. + const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); + if (! start_filament_gcode.empty()) { + // Process the start_filament_gcode for the filament. + gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); + check_add_eol(gcode); + } + gcode += m_writer.toolchange(extruder_id); + return gcode; } // prepend retraction on the current extruder From d9c325c7f067cff2bf997e67b205860025e32807 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 19 Jul 2019 10:29:06 +0200 Subject: [PATCH 358/627] Adding a generic bitmap "flags" attrib to the Undo / Redo snapshot. using this new "flags" attrib to store & recover the "Layers editing active" flag and restoring the "Layers editing" tool state. --- src/slic3r/GUI/Plater.cpp | 41 +++++++++++++++++++++++++---------- src/slic3r/Utils/UndoRedo.cpp | 22 +++++++++---------- src/slic3r/Utils/UndoRedo.hpp | 15 +++++++++---- 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 559145ebae..f1be5c9f2e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1641,17 +1641,7 @@ struct Plater::priv void split_volume(); void scale_selection_to_fit_print_volume(); - void take_snapshot(const std::string& snapshot_name) - { - if (this->m_prevent_snapshots > 0) - return; - assert(this->m_prevent_snapshots >= 0); - this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology); - this->undo_redo_stack.release_least_recently_used(); - // Save the last active preset name of a particular printer technology. - ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); - BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info(); - } + void take_snapshot(const std::string& snapshot_name); void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } int get_active_snapshot_index(); void undo(); @@ -3588,6 +3578,21 @@ int Plater::priv::get_active_snapshot_index() return it - ss_stack.begin(); } +void Plater::priv::take_snapshot(const std::string& snapshot_name) +{ + if (this->m_prevent_snapshots > 0) + return; + assert(this->m_prevent_snapshots >= 0); + unsigned int flags = 0; + if (this->view3D->is_layers_editing_enabled()) + flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE; + this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, flags); + this->undo_redo_stack.release_least_recently_used(); + // Save the last active preset name of a particular printer technology. + ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); + BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info(); +} + void Plater::priv::undo() { const std::vector &snapshots = this->undo_redo_stack.snapshots(); @@ -3627,9 +3632,18 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator } // Save the last active preset name of a particular printer technology. ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); + // Flags made of Snapshot::Flags enum values. + unsigned int new_flags = it_snapshot->flags; + unsigned int top_snapshot_flags = 0; + if (this->view3D->is_layers_editing_enabled()) + top_snapshot_flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE; + bool new_variable_layer_editing_active = (new_flags & UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE) != 0; + // Disable layer editing before the Undo / Redo jump. + if (! new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) + view3D->enable_layers_editing(false); // Do the jump in time. if (it_snapshot->timestamp < this->undo_redo_stack.active_snapshot_time() ? - this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, it_snapshot->timestamp) : + this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, top_snapshot_flags, it_snapshot->timestamp) : this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) { if (printer_technology_changed) { // Switch to the other printer technology. Switch to the last printer active for that particular technology. @@ -3641,6 +3655,9 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator wxGetApp().load_current_presets(); } this->update_after_undo_redo(temp_snapshot_was_taken); + // Enable layer editing after the Undo / Redo jump. + if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) + view3D->enable_layers_editing(true); } } diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index c4a082fb6e..efbdb767ad 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -495,12 +495,12 @@ public: } // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. - void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology); + void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags); void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos); bool has_undo_snapshot() const; bool has_redo_snapshot() const; - bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, size_t jump_to_time); + bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t jump_to_time); bool redo(Slic3r::Model &model, Slic3r::GUI::GLGizmosManager &gizmos, size_t jump_to_time); void release_least_recently_used(); @@ -788,7 +788,7 @@ template void StackImpl::load_mutable_object(const Sl } // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. -void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology) +void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags) { // Release old snapshot data. assert(m_active_snapshot_time <= m_current_time); @@ -808,11 +808,11 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo this->save_mutable_object(m_selection); this->save_mutable_object(gizmos); // Save the snapshot info. - m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id, printer_technology); + m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id, printer_technology, flags); m_active_snapshot_time = m_current_time; // Save snapshot info of the last "current" aka "top most" state, that is only being serialized // if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet. - m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0, printer_technology); + m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0, printer_technology, flags); // Release empty objects from the history. this->collect_garbage(); assert(this->valid()); @@ -858,7 +858,7 @@ bool StackImpl::has_redo_snapshot() const return ++ it != m_snapshots.end(); } -bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, size_t time_to_load) +bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t time_to_load) { assert(this->valid()); if (time_to_load == SIZE_MAX) { @@ -872,7 +872,7 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti bool new_snapshot_taken = false; if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { // The current state is temporary. The current state needs to be captured to be redoable. - this->take_snapshot(topmost_snapshot_name, model, selection, gizmos, printer_technology); + this->take_snapshot(topmost_snapshot_name, model, selection, gizmos, printer_technology, flags); // The line above entered another topmost_snapshot_name. assert(m_snapshots.back().is_topmost()); assert(! m_snapshots.back().is_topmost_captured()); @@ -1020,12 +1020,12 @@ Stack::~Stack() {} void Stack::set_memory_limit(size_t memsize) { pimpl->set_memory_limit(memsize); } size_t Stack::memsize() const { return pimpl->memsize(); } void Stack::release_least_recently_used() { pimpl->release_least_recently_used(); } -void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology) - { pimpl->take_snapshot(snapshot_name, model, selection, gizmos, printer_technology); } +void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags) + { pimpl->take_snapshot(snapshot_name, model, selection, gizmos, printer_technology, flags); } bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); } bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); } -bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, size_t time_to_load) - { return pimpl->undo(model, selection, gizmos, printer_technology, time_to_load); } +bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t time_to_load) + { return pimpl->undo(model, selection, gizmos, printer_technology, flags, time_to_load); } bool Stack::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->redo(model, gizmos, time_to_load); } const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); } diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 96b539f18d..28ba31a00c 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -24,13 +24,20 @@ namespace UndoRedo { struct Snapshot { Snapshot(size_t timestamp) : timestamp(timestamp) {} - Snapshot(const std::string &name, size_t timestamp, size_t model_id, Slic3r::PrinterTechnology printer_technology) : - name(name), timestamp(timestamp), model_id(model_id), printer_technology(printer_technology) {} + Snapshot(const std::string &name, size_t timestamp, size_t model_id, Slic3r::PrinterTechnology printer_technology, unsigned int flags) : + name(name), timestamp(timestamp), model_id(model_id), printer_technology(printer_technology), flags(flags) {} + + // Bitmask of various binary flags to be stored with the snapshot. + enum Flags { + VARIABLE_LAYER_EDITING_ACTIVE = 1, + }; std::string name; size_t timestamp; size_t model_id; PrinterTechnology printer_technology; + // Bitmap of Flags (see the Flags enum). + unsigned int flags; bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; } bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; } @@ -69,7 +76,7 @@ public: void release_least_recently_used(); // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. - void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology); + void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags); // To be queried to enable / disable the Undo / Redo buttons at the UI. bool has_undo_snapshot() const; @@ -77,7 +84,7 @@ public: // Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated. // Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible. - bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, size_t time_to_load = SIZE_MAX); + bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t time_to_load = SIZE_MAX); // Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated. bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX); From 2de6d95322430ba2835495f18dde302b3128001c Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 19 Jul 2019 11:12:38 +0200 Subject: [PATCH 359/627] Memory statistics (total memory, memory usage, Undo / Redo stack size) into the System INfo dialog. --- src/slic3r/GUI/Plater.cpp | 12 +++------- src/slic3r/GUI/Plater.hpp | 6 +++++ src/slic3r/GUI/SysInfoDialog.cpp | 38 +++++++++++++++++++++++++++++++- src/slic3r/Utils/UndoRedo.cpp | 2 ++ src/slic3r/Utils/UndoRedo.hpp | 1 + 5 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f1be5c9f2e..74636bbdc1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4564,15 +4564,9 @@ bool Plater::can_copy_to_clipboard() const return true; } -bool Plater::can_undo() const -{ - return p->undo_redo_stack.has_undo_snapshot(); -} - -bool Plater::can_redo() const -{ - return p->undo_redo_stack.has_redo_snapshot(); -} +bool Plater::can_undo() const { return p->undo_redo_stack.has_undo_snapshot(); } +bool Plater::can_redo() const { return p->undo_redo_stack.has_redo_snapshot(); } +const UndoRedo::Stack& Plater::undo_redo_stack() const { return p->undo_redo_stack; } SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() : m_was_running(wxGetApp().plater()->is_background_process_running()) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index a4ced64a08..d7e91f516c 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -27,6 +27,11 @@ class ModelObject; class Print; class SLAPrint; +namespace UndoRedo { + class Stack; + struct Snapshot; +}; + namespace GUI { class MainFrame; @@ -191,6 +196,7 @@ public: void undo_to(int selection); void redo_to(int selection); bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text); + const Slic3r::UndoRedo::Stack& undo_redo_stack() const; void on_extruders_change(int extruders_count); void on_config_change(const DynamicPrintConfig &config); diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index bd19c38c38..96a3e9a819 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -2,6 +2,7 @@ #include "I18N.hpp" #include "3DScene.hpp" #include "GUI.hpp" +#include "../Utils/UndoRedo.hpp" #include @@ -10,6 +11,14 @@ #include "GUI_App.hpp" #include "wxExtensions.hpp" +#ifdef _WIN32 + // The standard Windows includes. + #define WIN32_LEAN_AND_MEAN + #define NOMINMAX + #include + #include +#endif /* _WIN32 */ + namespace Slic3r { namespace GUI { @@ -36,10 +45,37 @@ std::string get_main_info(bool format_as_html) "System Version: " #endif << b_end << wxPlatformInfo::Get().GetOperatingSystemDescription() << line_end; + out << b_start << "Total RAM size [MB]: " << b_end << Slic3r::format_memsize_MB(Slic3r::total_physical_memory()); return out.str(); } +std::string get_mem_info(bool format_as_html) +{ + std::stringstream out; + + std::string b_start = format_as_html ? "" : ""; + std::string b_end = format_as_html ? "" : ""; + std::string line_end = format_as_html ? "
" : "\n"; + + const Slic3r::UndoRedo::Stack &stack = wxGetApp().plater()->undo_redo_stack(); + out << b_start << "RAM size reserved for the Undo / Redo stack [MB]: " << b_end << Slic3r::format_memsize_MB(stack.get_memory_limit()) << line_end; + out << b_start << "RAM size occupied by the Undo / Redo stack [MB]: " << b_end << Slic3r::format_memsize_MB(stack.memsize()) << line_end << line_end; + +#ifdef _WIN32 + HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ::GetCurrentProcessId()); + if (hProcess != nullptr) { + PROCESS_MEMORY_COUNTERS_EX pmc; + if (GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) + out << b_start << "WorkingSet [MB]: " << b_end << format_memsize_MB(pmc.WorkingSetSize) << line_end + << b_start << "PrivateBytes [MB]: " << b_end << format_memsize_MB(pmc.PrivateUsage) << line_end + << b_start << "Pagefile(peak) [MB]: " << b_end << format_memsize_MB(pmc.PagefileUsage) << "(" << format_memsize_MB(pmc.PeakPagefileUsage) << ")" << line_end; + CloseHandle(hProcess); + } +#endif + return out.str(); +} + SysInfoDialog::SysInfoDialog() : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("System Information")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) { @@ -111,7 +147,7 @@ SysInfoDialog::SysInfoDialog() "" "" "", bgr_clr_str, text_clr_str, text_clr_str, - _3DScene::get_gl_info(true, true)); + get_mem_info(true) + "
" + _3DScene::get_gl_info(true, true)); m_opengl_info_html->SetPage(text); main_sizer->Add(m_opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15); } diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index efbdb767ad..2605bd2a74 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -486,6 +486,7 @@ public: StackImpl() : m_memory_limit(std::min(Slic3r::total_physical_memory() / 10, size_t(1 * 16384 * 65536 / UNDO_REDO_DEBUG_LOW_MEM_FACTOR))), m_active_snapshot_time(0), m_current_time(0) {} void set_memory_limit(size_t memsize) { m_memory_limit = memsize; } + size_t get_memory_limit() const { return m_memory_limit; } size_t memsize() const { size_t memsize = 0; @@ -1018,6 +1019,7 @@ void StackImpl::release_least_recently_used() Stack::Stack() : pimpl(new StackImpl()) {} Stack::~Stack() {} void Stack::set_memory_limit(size_t memsize) { pimpl->set_memory_limit(memsize); } +size_t Stack::get_memory_limit() const { return pimpl->get_memory_limit(); } size_t Stack::memsize() const { return pimpl->memsize(); } void Stack::release_least_recently_used() { pimpl->release_least_recently_used(); } void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags) diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 28ba31a00c..916e44aa24 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -68,6 +68,7 @@ public: // Set maximum memory threshold. If the threshold is exceeded, least recently used snapshots are released. void set_memory_limit(size_t memsize); + size_t get_memory_limit() const; // Estimate size of the RAM consumed by the Undo / Redo stack. size_t memsize() const; From 749bb2bfed739f4d19ae06ced02b4b8463edf1ed Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 19 Jul 2019 11:18:19 +0200 Subject: [PATCH 360/627] Refactoring in GLTexture --- src/slic3r/GUI/3DBed.cpp | 4 +- src/slic3r/GUI/GLCanvas3D.cpp | 4 +- src/slic3r/GUI/GLTexture.cpp | 173 +++------------------- src/slic3r/GUI/GLTexture.hpp | 16 +- src/slic3r/GUI/GLToolbar.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 +- 6 files changed, 39 insertions(+), 162 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 89ad5a4ad5..89b7caedfb 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -418,7 +418,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom // generate a temporary lower resolution texture to show while no main texture levels have been compressed if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != filename)) { - if (!m_temp_texture.load_from_file(filename, false, false, false, max_tex_size)) + if (!m_temp_texture.load_from_file(filename, false, GLTexture::None, false)) { render_custom(); return; @@ -426,7 +426,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom } // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_file(filename, true, true, true, max_tex_size)) + if (!m_texture.load_from_file(filename, true, GLTexture::MultiThreaded, true)) { render_custom(); return; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 70544cd37f..b4a79c6f04 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -302,7 +302,7 @@ void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas if (m_tooltip_texture.get_id() == 0) { std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png"; - if (!m_tooltip_texture.load_from_file(filename, false, true)) + if (!m_tooltip_texture.load_from_file(filename, false, GLTexture::SingleThreaded, false)) return; } @@ -334,7 +334,7 @@ void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) co if (m_reset_texture.get_id() == 0) { std::string filename = resources_dir() + "/icons/variable_layer_height_reset.png"; - if (!m_reset_texture.load_from_file(filename, false, true)) + if (!m_reset_texture.load_from_file(filename, false, GLTexture::SingleThreaded, false)) return; } diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 495a9864cb..f3421f1508 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -134,7 +134,7 @@ GLTexture::~GLTexture() reset(); } -bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, bool compress) +bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy) { reset(); @@ -142,20 +142,7 @@ bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, bo return false; if (boost::algorithm::iends_with(filename, ".png")) - return load_from_png(filename, use_mipmaps, compress); - else - return false; -} - -bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) -{ - reset(); - - if (!boost::filesystem::exists(filename)) - return false; - - if (boost::algorithm::iends_with(filename, ".png")) - return load_from_png(filename, use_mipmaps, compress, apply_anisotropy, max_size_px); + return load_from_png(filename, use_mipmaps, compression_type, apply_anisotropy); else return false; } @@ -367,120 +354,9 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, glsafe(::glDisable(GL_BLEND)); } -unsigned int GLTexture::generate_mipmaps(wxImage& image, bool compress) +bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy) { - int w = image.GetWidth(); - int h = image.GetHeight(); - GLint level = 0; - std::vector data(w * h * 4, 0); - - while ((w > 1) || (h > 1)) - { - ++level; - - w = std::max(w / 2, 1); - h = std::max(h / 2, 1); - - int n_pixels = w * h; - - image = image.ResampleBicubic(w, h); - - unsigned char* img_rgb = image.GetData(); - unsigned char* img_alpha = image.GetAlpha(); - - data.resize(n_pixels * 4); - for (int i = 0; i < n_pixels; ++i) - { - int data_id = i * 4; - int img_id = i * 3; - data[data_id + 0] = img_rgb[img_id + 0]; - data[data_id + 1] = img_rgb[img_id + 1]; - data[data_id + 2] = img_rgb[img_id + 2]; - data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255; - } - - if (compress && GLEW_EXT_texture_compression_s3tc) - glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - else - glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - } - - return (unsigned int)level; -} - -bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, bool compress) -{ - // Load a PNG with an alpha channel. - wxImage image; - if (!image.LoadFile(wxString::FromUTF8(filename.c_str()), wxBITMAP_TYPE_PNG)) - { - reset(); - return false; - } - - m_width = image.GetWidth(); - m_height = image.GetHeight(); - int n_pixels = m_width * m_height; - - if (n_pixels <= 0) - { - reset(); - return false; - } - - // Get RGB & alpha raw data from wxImage, pack them into an array. - unsigned char* img_rgb = image.GetData(); - if (img_rgb == nullptr) - { - reset(); - return false; - } - - unsigned char* img_alpha = image.GetAlpha(); - - std::vector data(n_pixels * 4, 0); - for (int i = 0; i < n_pixels; ++i) - { - int data_id = i * 4; - int img_id = i * 3; - data[data_id + 0] = img_rgb[img_id + 0]; - data[data_id + 1] = img_rgb[img_id + 1]; - data[data_id + 2] = img_rgb[img_id + 2]; - data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255; - } - - // sends data to gpu - glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - glsafe(::glGenTextures(1, &m_id)); - glsafe(::glBindTexture(GL_TEXTURE_2D, m_id)); - if (compress && GLEW_EXT_texture_compression_s3tc) - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - else - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - if (use_mipmaps) - { - // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards - unsigned int levels_count = generate_mipmaps(image, compress); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels_count)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); - } - else - { - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); - } - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - - m_source = filename; - - return true; -} - -bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px) -{ - bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc; + bool compression_enabled = (compression_type != None) && GLEW_EXT_texture_compression_s3tc; // Load a PNG with an alpha channel. wxImage image; @@ -493,18 +369,9 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, boo m_width = image.GetWidth(); m_height = image.GetHeight(); - unsigned int max_size = (unsigned int)std::max(m_width, m_height); bool requires_rescale = false; - if (max_size_px < max_size) - { - float scale = (float)max_size_px / (float)max_size; - m_width = (int)(scale * (float)m_width); - m_height = (int)(scale * (float)m_height); - requires_rescale = true; - } - - if (compression_enabled) + if (compression_enabled && (compression_type == MultiThreaded)) { // the stb_dxt compression library seems to like only texture sizes which are a multiple of 4 int width_rem = m_width % 4; @@ -568,10 +435,15 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, boo if (compression_enabled) { - // initializes the texture on GPU - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - // and send the uncompressed data to the compressor - m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); + if (compression_type == SingleThreaded) + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + else + { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data); + } } else glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); @@ -610,10 +482,15 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, boo if (compression_enabled) { - // initializes the texture on GPU - glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); - // and send the uncompressed data to the compressor - m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data); + if (compression_type == SingleThreaded) + glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); + else + { + // initializes the texture on GPU + glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0)); + // and send the uncompressed data to the compressor + m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data); + } } else glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); @@ -637,7 +514,7 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, boo m_source = filename; - if (compression_enabled) + if (compression_enabled && (compression_type == MultiThreaded)) // start asynchronous compression m_compressor.start_compressing(); @@ -651,7 +528,6 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f); if (image == nullptr) { -// printf("Could not open SVG image.\n"); reset(); return false; } @@ -686,7 +562,6 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo NSVGrasterizer* rast = nsvgCreateRasterizer(); if (rast == nullptr) { -// printf("Could not init rasterizer.\n"); nsvgDelete(image); reset(); return false; diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index 21e01733d6..7fc5b8fcf6 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -48,6 +48,13 @@ namespace GUI { }; public: + enum ECompressionType : unsigned char + { + None, + SingleThreaded, + MultiThreaded + }; + struct UV { float u; @@ -75,8 +82,7 @@ namespace GUI { GLTexture(); virtual ~GLTexture(); - bool load_from_file(const std::string& filename, bool use_mipmaps, bool compress); - bool load_from_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); + bool load_from_file(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy); bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); // meanings of states: (std::pair) // first field (int): @@ -102,12 +108,8 @@ namespace GUI { static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs); - protected: - unsigned int generate_mipmaps(wxImage& image, bool compress); - private: - bool load_from_png(const std::string& filename, bool use_mipmaps, bool compress); - bool load_from_png(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); + bool load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy); bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); friend class Compressor; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 0002eda2d0..f4748f680e 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -172,7 +172,7 @@ bool GLToolbar::init(const BackgroundTexture::Metadata& background_texture) bool res = false; if (!background_texture.filename.empty()) - res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, true); + res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, GLTexture::SingleThreaded, false); if (res) m_background_texture.metadata = background_texture; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index df53b40aa9..1509c1328b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -43,7 +43,7 @@ bool GLGizmosManager::init() if (!m_background_texture.metadata.filename.empty()) { - if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false, true)) + if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false, GLTexture::SingleThreaded, false)) { reset(); return false; From 72ed8c034e17c89d37f8ab0eaba309da669621ad Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 19 Jul 2019 12:34:27 +0200 Subject: [PATCH 361/627] Fix for incorrect inside check of fixed items. libnest2d: Add dispatched overloads for offsetting different shapes. --- .../libnest2d/backends/clipper/geometries.hpp | 11 ++++++- .../include/libnest2d/geometry_traits.hpp | 32 ++++++++++++++++--- src/libslic3r/Arrange.cpp | 25 +++++++++------ 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index 2ca4f5d50e..06afbd1872 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -71,7 +71,8 @@ template<> inline ClipperLib::cInt& y(PointImpl& p) namespace shapelike { -template<> inline void offset(PolygonImpl& sh, TCoord distance) +template<> +inline void offset(PolygonImpl& sh, TCoord distance, const PolygonTag&) { #define DISABLE_BOOST_OFFSET @@ -123,6 +124,14 @@ template<> inline void offset(PolygonImpl& sh, TCoord distance) } } +template<> +inline void offset(PathImpl& sh, TCoord distance, const PathTag&) +{ + PolygonImpl p(std::move(sh)); + offset(p, distance, PolygonTag()); + sh = p.Contour; +} + // Tell libnest2d how to make string out of a ClipperPolygon object template<> inline std::string toString(const PolygonImpl& sh) { diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index c4f2fcaca5..827e2d8ba3 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -507,15 +507,13 @@ enum class Formats { namespace shapelike { template -inline S create(const TContour& contour, - const THolesContainer& holes) +inline S create(const TContour& contour, const THolesContainer& holes) { return S(contour, holes); } template -inline S create(TContour&& contour, - THolesContainer&& holes) +inline S create(TContour&& contour, THolesContainer&& holes) { return S(contour, holes); } @@ -727,11 +725,18 @@ inline void translate(S& /*sh*/, const P& /*offs*/) } template -inline void offset(S& /*sh*/, TCoord> /*distance*/) +inline void offset(S& /*sh*/, TCoord /*distance*/, const PathTag&) { dout() << "The current geometry backend does not support offsetting!\n"; } +template +inline void offset(S& sh, TCoord distance, const PolygonTag&) +{ + offset(contour(sh), distance); + for(auto &h : holes(sh)) offset(h, -distance); +} + template inline std::pair isValid(const S& /*sh*/) { @@ -1228,6 +1233,23 @@ template inline bool isConvex(const S& sh) // dispatch return isConvex(sh, Tag()); } +template inline void offset(Box& bb, TCoord d, const BoxTag&) +{ + TPoint md{d, d}; + bb.minCorner() -= md; + bb.maxCorner() += md; +} + +template inline void offset(C& circ, TCoord d, const CircleTag&) +{ + circ.radius(circ.radius() + double(d)); +} + +// Dispatch function +template inline void offset(S& sh, TCoord d) { + offset(sh, d, Tag()); +} + } #define DECLARE_MAIN_TYPES(T) \ diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index b02efa55c1..f692f91c78 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -514,19 +514,30 @@ void _arrange( coord_t minobjd, std::function prind, std::function stopfn) -{ - AutoArranger arranger{bin, minobjd, prind, stopfn}; +{ + // Integer ceiling the min distance from the bed perimeters + coord_t md = minobjd - 2 * scaled(0.1 + EPSILON); + md = (md % 2) ? md / 2 + 1 : md / 2; + + auto corrected_bin = bin; + sl::offset(corrected_bin, md); + + AutoArranger arranger{corrected_bin, 0, prind, stopfn}; + + auto infl = coord_t(std::ceil(minobjd / 2.0)); + for (Item& itm : shapes) itm.inflate(infl); + for (Item& itm : excludes) itm.inflate(infl); auto it = excludes.begin(); while (it != excludes.end()) - sl::isInside(it->transformedShape(), bin) ? + sl::isInside(it->transformedShape(), corrected_bin) ? ++it : it = excludes.erase(it); // If there is something on the plate if(!excludes.empty()) { arranger.preload(excludes); - auto binbb = sl::boundingBox(bin); + auto binbb = sl::boundingBox(corrected_bin); // Try to put the first item to the center, as the arranger // will not do this for us. @@ -548,6 +559,7 @@ void _arrange( for (auto &itm : excludes) inp.emplace_back(itm); arranger(inp.begin(), inp.end()); + for (Item &itm : inp) itm.inflate(-infl); } // The final client function for arrangement. A progress indicator and @@ -594,10 +606,6 @@ void arrange(ArrangePolygons & arrangables, for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON)); - // Integer ceiling the min distance from the bed perimeters - coord_t md = min_obj_dist - 2 * scaled(0.1 + EPSILON); - md = (md % 2) ? md / 2 + 1 : md / 2; - auto &cfn = stopcondition; auto &pri = progressind; @@ -605,7 +613,6 @@ void arrange(ArrangePolygons & arrangables, case bsBox: { // Create the arranger for the box shaped bed BoundingBox bbb = bedhint.get_box(); - bbb.min -= Point{md, md}, bbb.max += Point{md, md}; Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; _arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn); From eb29c3e01d3ff188db74faa567ece26cc3425fae Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 19 Jul 2019 12:59:56 +0200 Subject: [PATCH 362/627] Wipe tower accounts for extruder offsets Also, in case of non-single-extruder printer with the wipe tower, first wiping line was printed where the border should have been - fixed --- src/libslic3r/GCode.cpp | 47 ++++++++++++++++++++++++------- src/libslic3r/GCode.hpp | 7 +++-- src/libslic3r/GCode/WipeTower.cpp | 4 +-- src/libslic3r/GCode/WipeTower.hpp | 20 +++++++------ 4 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index d5ee6a0484..81880831f1 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -169,6 +169,9 @@ static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2 std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const { + if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) + throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); + std::string gcode; // Toolchangeresult.gcode assumes the wipe tower corner is at the origin @@ -182,8 +185,11 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T end_pos = Eigen::Rotation2Df(alpha) * end_pos; end_pos += m_wipe_tower_pos; } - std::string tcr_rotated_gcode = tcr.priming ? tcr.gcode : rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha); - + + Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; + float wipe_tower_rotation = tcr.priming ? 0.f : alpha; + + std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); // Disable linear advance for the wipe tower operations. gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); @@ -285,17 +291,21 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T // This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode // Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate) -std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const Vec2f& start_pos, const Vec2f& translation, float angle) const +std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const { - std::istringstream gcode_str(gcode_original); + Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast(); + + std::istringstream gcode_str(tcr.gcode); std::string gcode_out; std::string line; - Vec2f pos = start_pos; - Vec2f transformed_pos; + Vec2f pos = tcr.start_pos; + Vec2f transformed_pos = pos; Vec2f old_pos(-1000.1f, -1000.1f); while (gcode_str) { std::getline(gcode_str, line); // we read the gcode line by line + + // All G1 commands should be translated and rotated if (line.find("G1 ") == 0) { std::ostringstream line_out; std::istringstream line_str(line); @@ -317,17 +327,34 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco if (transformed_pos != old_pos) { line = line_out.str(); - char buf[2048] = "G1"; + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) << "G1 "; if (transformed_pos.x() != old_pos.x()) - sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x()); + oss << " X" << transformed_pos.x() - extruder_offset.x(); if (transformed_pos.y() != old_pos.y()) - sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y()); + oss << " Y" << transformed_pos.y() - extruder_offset.y(); - line.replace(line.find("G1 "), 3, buf); + line.replace(line.find("G1 "), 3, oss.str()); old_pos = transformed_pos; } } + gcode_out += line + "\n"; + + // If this was a toolchange command, we should change current extruder offset + if (line == "[toolchange_gcode]") { + extruder_offset = m_extruder_offsets[tcr.new_tool].cast(); + + // If the extruder offset changed, add an extra move so everything is continuous + if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast()) { + std::ostringstream oss; + oss << std::fixed << std::setprecision(3) + << "G1 X" << transformed_pos.x() - extruder_offset.x() + << " Y" << transformed_pos.y() - extruder_offset.y() + << "\n"; + gcode_out += oss.str(); + } + } } return gcode_out; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 4b81b42aac..f2a67f600b 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -90,6 +90,7 @@ public: m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)), m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)), m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)), + m_extruder_offsets(print_config.extruder_offset.values), m_priming(priming), m_tool_changes(tool_changes), m_final_purge(final_purge), @@ -107,14 +108,16 @@ private: WipeTowerIntegration& operator=(const WipeTowerIntegration&); std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const; - // Postprocesses gcode: rotates and moves all G1 extrusions and returns result - std::string rotate_wipe_tower_moves(const std::string& gcode_original, const Vec2f& start_pos, const Vec2f& translation, float angle) const; + // Postprocesses gcode: rotates and moves G1 extrusions and returns result + std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const; // Left / right edges of the wipe tower, for the planning of wipe moves. const float m_left; const float m_right; const Vec2f m_wipe_tower_pos; const float m_wipe_tower_rotation; + const std::vector m_extruder_offsets; + // Reference to cached values at the Printer class. const std::vector &m_priming; const std::vector> &m_tool_changes; diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 37e4040d19..354ec6d9e1 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -553,7 +553,7 @@ std::vector WipeTower::prime( result.elapsed_time = writer.elapsed_time(); result.extrusions = writer.extrusions(); result.start_pos = writer.start_pos_rotated(); - result.end_pos = writer.pos_rotated(); + result.end_pos = writer.pos(); results.push_back(std::move(result)); @@ -643,7 +643,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(unsigned int tool, bool last_ m_is_first_layer ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature); toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials. toolchange_Load(writer, cleaning_box); - writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road + writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. ++ m_num_tool_changes; } else diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index c18a502b13..badb3e8b24 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -139,13 +139,15 @@ public: m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter - std::stringstream stream{m_semm ? ramming_parameters : std::string()}; - float speed = 0.f; - stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; - m_filpar[idx].ramming_line_width_multiplicator /= 100; - m_filpar[idx].ramming_step_multiplicator /= 100; - while (stream >> speed) - m_filpar[idx].ramming_speed.push_back(speed); + if (m_semm) { + std::stringstream stream{ramming_parameters}; + float speed = 0.f; + stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; + m_filpar[idx].ramming_line_width_multiplicator /= 100; + m_filpar[idx].ramming_step_multiplicator /= 100; + while (stream >> speed) + m_filpar[idx].ramming_speed.push_back(speed); + } m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later } @@ -241,8 +243,8 @@ public: int cooling_moves = 0; float cooling_initial_speed = 0.f; float cooling_final_speed = 0.f; - float ramming_line_width_multiplicator = 0.f; - float ramming_step_multiplicator = 0.f; + float ramming_line_width_multiplicator = 1.f; + float ramming_step_multiplicator = 1.f; float max_e_speed = std::numeric_limits::max(); std::vector ramming_speed; float nozzle_diameter; From dbc1918193ebdf0badec67c7ca15bfeb553f70a4 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 19 Jul 2019 15:29:04 +0200 Subject: [PATCH 363/627] Undo / Redo. Workaround for the Wipe tower. --- src/libslic3r/Model.cpp | 2 +- src/libslic3r/Model.hpp | 65 ++++++++++++++++++++++++++++++++--- src/slic3r/GUI/Plater.cpp | 30 ++++++++++++++++ src/slic3r/Utils/UndoRedo.cpp | 39 ++++++++++----------- 4 files changed, 109 insertions(+), 27 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 66532e9ead..21e1557936 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1971,4 +1971,4 @@ CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelObject) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelVolume) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelInstance) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::Model) -#endif \ No newline at end of file +#endif diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 2b4a6d978a..f03d531536 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -22,6 +22,8 @@ namespace cereal { class BinaryOutputArchive; template void load_optional(BinaryInputArchive &ar, std::shared_ptr &ptr); template void save_optional(BinaryOutputArchive &ar, const std::shared_ptr &ptr); + template void load_by_value(BinaryInputArchive &ar, T &obj); + template void save_by_value(BinaryOutputArchive &ar, const T &obj); } namespace Slic3r { @@ -31,6 +33,7 @@ class ModelInstance; class ModelMaterial; class ModelObject; class ModelVolume; +class ModelWipeTower; class Print; class SLAPrint; @@ -66,6 +69,21 @@ private: } }; +namespace Internal { + template + class StaticSerializationWrapper + { + public: + StaticSerializationWrapper(T &wrap) : wrapped(wrap) {} + private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + template void load(Archive &ar) { cereal::load_by_value(ar, wrapped); } + template void save(Archive &ar) const { cereal::save_by_value(ar, wrapped); } + T& wrapped; + }; +} + typedef std::string t_model_material_id; typedef std::string t_model_material_attribute; typedef std::map t_model_material_attributes; @@ -140,7 +158,8 @@ private: ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); } template void serialize(Archive &ar) { assert(this->id().invalid()); assert(this->config.id().invalid()); - ar(attributes, config); + Internal::StaticSerializationWrapper config_wrapper(config); + ar(attributes, config_wrapper); // assert(this->id().valid()); assert(this->config.id().valid()); } @@ -349,7 +368,8 @@ private: } template void serialize(Archive &ar) { ar(cereal::base_class(this)); - ar(name, input_file, instances, volumes, config, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation, + Internal::StaticSerializationWrapper config_wrapper(config); + ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation, m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); } }; @@ -535,7 +555,8 @@ private: } template void load(Archive &ar) { bool has_convex_hull; - ar(name, config, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + cereal::load_by_value(ar, config); assert(m_mesh); if (has_convex_hull) { cereal::load_optional(ar, m_convex_hull); @@ -547,7 +568,8 @@ private: } template void save(Archive &ar) const { bool has_convex_hull = m_convex_hull.get() != nullptr; - ar(name, config, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + cereal::save_by_value(ar, config); if (has_convex_hull) cereal::save_optional(ar, m_convex_hull); } @@ -650,6 +672,35 @@ private: } }; +class ModelWipeTower final : public ObjectBase +{ +public: + Vec2d position; + double rotation; + +private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + friend class Model; + + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit ModelWipeTower() {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit ModelWipeTower(int) : ObjectBase(-1) {} + // Copy constructor copies the ID. + explicit ModelWipeTower(const ModelWipeTower &cfg) = default; + + // Disabled methods. + ModelWipeTower(ModelWipeTower &&rhs) = delete; + ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete; + ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete; + + // For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object. + template void serialize(Archive &ar) { ar(position, rotation); } +}; + // The print bed content. // Description of a triangular model with multiple materials, multiple instances with various affine transformations // and with multiple modifier meshes. @@ -665,6 +716,8 @@ public: ModelMaterialMap materials; // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation). ModelObjectPtrs objects; + // Wipe tower object. + ModelWipeTower wipe_tower; // Default constructor assigns a new ID to the model. Model() { assert(this->id().valid()); } @@ -742,7 +795,8 @@ private: friend class cereal::access; friend class UndoRedo::StackImpl; template void serialize(Archive &ar) { - ar(materials, objects); + Internal::StaticSerializationWrapper wipe_tower_wrapper(wipe_tower); + ar(materials, objects, wipe_tower_wrapper); } }; @@ -778,6 +832,7 @@ void check_model_ids_equal(const Model &model1, const Model &model2); namespace cereal { template struct specialize {}; + template struct specialize {}; } #endif /* slic3r_Model_hpp_ */ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 74636bbdc1..47109e2cd6 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3586,6 +3586,13 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) unsigned int flags = 0; if (this->view3D->is_layers_editing_enabled()) flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE; + //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config. + // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. + if (this->printer_technology == ptFFF) { + const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y")); + model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); + } this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, flags); this->undo_redo_stack.release_least_recently_used(); // Save the last active preset name of a particular printer technology. @@ -3632,6 +3639,13 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator } // Save the last active preset name of a particular printer technology. ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); + //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config. + // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. + if (this->printer_technology == ptFFF) { + const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y")); + model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); + } // Flags made of Snapshot::Flags enum values. unsigned int new_flags = it_snapshot->flags; unsigned int top_snapshot_flags = 0; @@ -3654,6 +3668,22 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator // This also switches the printer technology based on the printer technology of the active printer profile. wxGetApp().load_current_presets(); } + //FIXME updating the Print config from the Wipe tower config values at the ModelWipeTower. + // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. + if (this->printer_technology == ptFFF) { + const DynamicPrintConfig ¤t_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + Vec2d current_position(current_config.opt_float("wipe_tower_x"), current_config.opt_float("wipe_tower_y")); + double current_rotation = current_config.opt_float("wipe_tower_rotation_angle"); + if (current_position != model.wipe_tower.position || current_rotation != model.wipe_tower.rotation) { + DynamicPrintConfig new_config; + new_config.set_key_value("wipe_tower_x", new ConfigOptionFloat(model.wipe_tower.position.x())); + new_config.set_key_value("wipe_tower_y", new ConfigOptionFloat(model.wipe_tower.position.y())); + new_config.set_key_value("wipe_tower_rotation_angle", new ConfigOptionFloat(model.wipe_tower.rotation)); + Tab *tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); + tab_print->load_config(new_config); + tab_print->update_dirty(); + } + } this->update_after_undo_redo(temp_snapshot_was_taken); // Enable layer editing after the Undo / Redo jump. if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 2605bd2a74..a8f9cc134a 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -514,11 +514,11 @@ public: const Selection& selection_deserialized() const { return m_selection; } //protected: - template ObjectID save_mutable_object(const T &object); + template ObjectID save_mutable_object(const T &object); template ObjectID save_immutable_object(std::shared_ptr &object, bool optional); template T* load_mutable_object(const Slic3r::ObjectID id); template std::shared_ptr load_immutable_object(const Slic3r::ObjectID id, bool optional); - template void load_mutable_object(const Slic3r::ObjectID id, T &target); + template void load_mutable_object(const Slic3r::ObjectID id, T &target); #ifdef SLIC3R_UNDOREDO_DEBUG std::string format() const { @@ -601,7 +601,6 @@ class ModelObject; class ModelVolume; class ModelInstance; class ModelMaterial; -class ModelConfig; class DynamicPrintConfig; class TriangleMesh; @@ -616,14 +615,13 @@ namespace cereal template struct specialize {}; template struct specialize {}; template struct specialize {}; - template struct specialize {}; template struct specialize, cereal::specialization::non_member_load_save> {}; // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, // store just the ObjectID to this stream. template void save(BinaryOutputArchive& ar, T* const& ptr) { - ar(cereal::get_user_data(ar).save_mutable_object(*ptr)); + ar(cereal::get_user_data(ar).save_mutable_object(*ptr)); } // Load ObjectBase derived class from the Undo / Redo stack as a separate object @@ -655,19 +653,18 @@ namespace cereal // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, // store just the ObjectID to this stream. - void save(BinaryOutputArchive& ar, const Slic3r::ModelConfig &cfg) + template void save_by_value(BinaryOutputArchive& ar, const T &cfg) { - ar(cereal::get_user_data(ar).save_mutable_object(cfg)); + ar(cereal::get_user_data(ar).save_mutable_object(cfg)); } - // Load ObjectBase derived class from the Undo / Redo stack as a separate object // based on the ObjectID loaded from this stream. - void load(BinaryInputArchive& ar, Slic3r::ModelConfig &cfg) + template void load_by_value(BinaryInputArchive& ar, T &cfg) { Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data(ar); size_t id; ar(id); - stack.load_mutable_object(Slic3r::ObjectID(id), cfg); + stack.load_mutable_object(Slic3r::ObjectID(id), cfg); } // Store ObjectBase derived class onto the Undo / Redo stack as a separate object, @@ -723,7 +720,7 @@ template std::shared_ptr& ImmutableObjectHistory::share return m_shared_object; } -template ObjectID StackImpl::save_mutable_object(const T &object) +template ObjectID StackImpl::save_mutable_object(const T &object) { // First find or allocate a history stack for the ObjectID of this object instance. auto it_object_history = m_objects.find(object.id()); @@ -734,7 +731,7 @@ template ObjectID StackImpl::save_mutable_object(cons std::ostringstream oss; { Slic3r::UndoRedo::OutputArchive archive(*this, oss); - archive(static_cast(object)); + archive(object); } object_history->save(m_active_snapshot_time, m_current_time, oss.str()); return object.id(); @@ -758,7 +755,7 @@ template ObjectID StackImpl::save_immutable_object(std::shared_ptr T* StackImpl::load_mutable_object(const Slic3r::ObjectID id) { T *target = new T(); - this->load_mutable_object(id, *target); + this->load_mutable_object(id, *target); return target; } @@ -775,7 +772,7 @@ template std::shared_ptr StackImpl::load_immutable_object(c return object_history->shared_ptr(*this); } -template void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target) +template void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target) { // First find a history stack for the ObjectID of this object instance. auto it_object_history = m_objects.find(id); @@ -785,7 +782,7 @@ template void StackImpl::load_mutable_object(const Sl std::istringstream iss(object_history->load(m_active_snapshot_time)); Slic3r::UndoRedo::InputArchive archive(*this, iss); target.m_id = id; - archive(static_cast(target)); + archive(target); } // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. @@ -800,14 +797,14 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo m_snapshots.erase(it, m_snapshots.end()); } // Take new snapshots. - this->save_mutable_object(model); + this->save_mutable_object(model); m_selection.volumes_and_instances.clear(); m_selection.volumes_and_instances.reserve(selection.get_volume_idxs().size()); m_selection.mode = selection.get_mode(); for (unsigned int volume_idx : selection.get_volume_idxs()) m_selection.volumes_and_instances.emplace_back(selection.get_volume(volume_idx)->geometry_id); - this->save_mutable_object(m_selection); - this->save_mutable_object(gizmos); + this->save_mutable_object(m_selection); + this->save_mutable_object(gizmos); // Save the snapshot info. m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id, printer_technology, flags); m_active_snapshot_time = m_current_time; @@ -833,12 +830,12 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GU m_active_snapshot_time = timestamp; model.clear_objects(); model.clear_materials(); - this->load_mutable_object(ObjectID(it_snapshot->model_id), model); + this->load_mutable_object(ObjectID(it_snapshot->model_id), model); model.update_links_bottom_up_recursive(); m_selection.volumes_and_instances.clear(); - this->load_mutable_object(m_selection.id(), m_selection); + this->load_mutable_object(m_selection.id(), m_selection); gizmos.reset_all_states(); - this->load_mutable_object(gizmos.id(), gizmos); + this->load_mutable_object(gizmos.id(), gizmos); // Sort the volumes so that we may use binary search. std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end()); this->m_active_snapshot_time = timestamp; From 81d3669a2575231041ade3387cafd9928cf458e8 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 19 Jul 2019 15:36:55 +0200 Subject: [PATCH 364/627] Undo/Redo buttons moved into their own toolbar --- src/slic3r/GUI/GLCanvas3D.cpp | 231 ++++++++++++++++++++------------- src/slic3r/GUI/GLCanvas3D.hpp | 17 ++- src/slic3r/GUI/GLToolbar.cpp | 9 +- src/slic3r/GUI/GUI_Preview.cpp | 3 +- src/slic3r/GUI/Plater.cpp | 8 +- 5 files changed, 163 insertions(+), 105 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 47b9ad48a4..85cbcadae3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1202,7 +1202,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , m_bed(bed) , m_camera(camera) , m_view_toolbar(view_toolbar) - , m_toolbar(GLToolbar::Normal, "Top") + , m_main_toolbar(GLToolbar::Normal, "Top") + , m_undoredo_toolbar(GLToolbar::Normal, "Top") , m_gizmos(*this) , m_use_clipping_planes(false) , m_sidebar_field("") @@ -1309,7 +1310,7 @@ bool GLCanvas3D::init() return false; } - if (m_toolbar.is_enabled() && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs")) + if (m_main_toolbar.is_enabled() && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs")) { std::cout << "Unable to initialize variable_layer_height shader: please, check that the files variable_layer_height.vs and variable_layer_height.fs are available" << std::endl; return false; @@ -1323,7 +1324,7 @@ bool GLCanvas3D::init() if (m_gizmos.is_enabled() && !m_gizmos.init()) std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; - if (!_init_toolbar()) + if (!_init_toolbars()) return false; if (m_selection.is_enabled() && !m_selection.init()) @@ -1514,9 +1515,14 @@ void GLCanvas3D::enable_selection(bool enable) m_selection.set_enabled(enable); } -void GLCanvas3D::enable_toolbar(bool enable) +void GLCanvas3D::enable_main_toolbar(bool enable) { - m_toolbar.set_enabled(enable); + m_main_toolbar.set_enabled(enable); +} + +void GLCanvas3D::enable_undoredo_toolbar(bool enable) +{ + m_undoredo_toolbar.set_enabled(enable); } void GLCanvas3D::enable_dynamic_background(bool enable) @@ -2295,7 +2301,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) if (!m_initialized) return; - m_dirty |= m_toolbar.update_items_state(); + m_dirty |= m_main_toolbar.update_items_state(); + m_dirty |= m_undoredo_toolbar.update_items_state(); m_dirty |= m_view_toolbar.update_items_state(); if (!m_dirty) @@ -2674,7 +2681,15 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ } - if (m_toolbar.on_mouse(evt, *this)) + if (m_main_toolbar.on_mouse(evt, *this)) + { + if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) + mouse_up_cleanup(); + m_mouse.set_start_position_3D_as_invalid(); + return; + } + + if (m_undoredo_toolbar.on_mouse(evt, *this)) { if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) mouse_up_cleanup(); @@ -3015,7 +3030,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) tooltip = m_gizmos.get_tooltip(); if (tooltip.empty()) - tooltip = m_toolbar.get_tooltip(); + tooltip = m_main_toolbar.get_tooltip(); + + if (tooltip.empty()) + tooltip = m_undoredo_toolbar.get_tooltip(); if (tooltip.empty()) tooltip = m_view_toolbar.get_tooltip(); @@ -3480,7 +3498,7 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) ImGuiWrapper* imgui = wxGetApp().imgui(); const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width(); - imgui->set_next_window_pos(x, m_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); + imgui->set_next_window_pos(x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f); imgui->set_next_window_bg_alpha(0.5f); imgui->begin(wxString::Format(_(L("%s Stack")), stack_name), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); @@ -3502,9 +3520,20 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) imgui->end(); } -bool GLCanvas3D::_init_toolbar() +bool GLCanvas3D::_init_toolbars() { - if (!m_toolbar.is_enabled()) + if (!_init_main_toolbar()) + return false; + + if (!_init_undoredo_toolbar()) + return false; + + return true; +} + +bool GLCanvas3D::_init_main_toolbar() +{ + if (!m_main_toolbar.is_enabled()) return true; BackgroundTexture::Metadata background_data; @@ -3514,19 +3543,19 @@ bool GLCanvas3D::_init_toolbar() background_data.right = 16; background_data.bottom = 16; - if (!m_toolbar.init(background_data)) + if (!m_main_toolbar.init(background_data)) { // unable to init the toolbar texture, disable it - m_toolbar.set_enabled(false); + m_main_toolbar.set_enabled(false); return true; } -// m_toolbar.set_layout_type(GLToolbar::Layout::Vertical); - m_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); - m_toolbar.set_layout_orientation(GLToolbar::Layout::Top); - m_toolbar.set_border(5.0f); - m_toolbar.set_separator_size(5); - m_toolbar.set_gap_size(2); +// m_main_toolbar.set_layout_type(GLToolbar::Layout::Vertical); + m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); + m_main_toolbar.set_layout_orientation(GLToolbar::Layout::Top); + m_main_toolbar.set_border(5.0f); + m_main_toolbar.set_separator_size(5); + m_main_toolbar.set_gap_size(2); GLToolbarItem::Data item; @@ -3535,7 +3564,7 @@ bool GLCanvas3D::_init_toolbar() item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]"; item.sprite_id = 0; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; - if (!m_toolbar.add_item(item)) + if (!m_main_toolbar.add_item(item)) return false; item.name = "delete"; @@ -3544,7 +3573,7 @@ bool GLCanvas3D::_init_toolbar() item.sprite_id = 1; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); }; - if (!m_toolbar.add_item(item)) + if (!m_main_toolbar.add_item(item)) return false; item.name = "deleteall"; @@ -3553,7 +3582,7 @@ bool GLCanvas3D::_init_toolbar() item.sprite_id = 2; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); }; - if (!m_toolbar.add_item(item)) + if (!m_main_toolbar.add_item(item)) return false; item.name = "arrange"; @@ -3562,10 +3591,10 @@ bool GLCanvas3D::_init_toolbar() item.sprite_id = 3; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; - if (!m_toolbar.add_item(item)) + if (!m_main_toolbar.add_item(item)) return false; - if (!m_toolbar.add_separator()) + if (!m_main_toolbar.add_separator()) return false; item.name = "copy"; @@ -3574,7 +3603,7 @@ bool GLCanvas3D::_init_toolbar() item.sprite_id = 4; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); }; - if (!m_toolbar.add_item(item)) + if (!m_main_toolbar.add_item(item)) return false; item.name = "paste"; @@ -3583,10 +3612,10 @@ bool GLCanvas3D::_init_toolbar() item.sprite_id = 5; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); }; - if (!m_toolbar.add_item(item)) + if (!m_main_toolbar.add_item(item)) return false; - if (!m_toolbar.add_separator()) + if (!m_main_toolbar.add_separator()) return false; item.name = "more"; @@ -3597,7 +3626,7 @@ bool GLCanvas3D::_init_toolbar() item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); }; - if (!m_toolbar.add_item(item)) + if (!m_main_toolbar.add_item(item)) return false; item.name = "fewer"; @@ -3607,10 +3636,10 @@ bool GLCanvas3D::_init_toolbar() item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); }; - if (!m_toolbar.add_item(item)) + if (!m_main_toolbar.add_item(item)) return false; - if (!m_toolbar.add_separator()) + if (!m_main_toolbar.add_separator()) return false; item.name = "splitobjects"; @@ -3620,7 +3649,7 @@ bool GLCanvas3D::_init_toolbar() item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; item.visibility_callback = GLToolbarItem::Default_Visibility_Callback; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); }; - if (!m_toolbar.add_item(item)) + if (!m_main_toolbar.add_item(item)) return false; item.name = "splitvolumes"; @@ -3630,10 +3659,10 @@ bool GLCanvas3D::_init_toolbar() item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); }; - if (!m_toolbar.add_item(item)) + if (!m_main_toolbar.add_item(item)) return false; - if (!m_toolbar.add_separator()) + if (!m_main_toolbar.add_separator()) return false; item.name = "layersediting"; @@ -3644,16 +3673,44 @@ bool GLCanvas3D::_init_toolbar() item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; - if (!m_toolbar.add_item(item)) + if (!m_main_toolbar.add_item(item)) return false; - if (!m_toolbar.add_separator()) - return false; + return true; +} + +bool GLCanvas3D::_init_undoredo_toolbar() +{ + if (!m_undoredo_toolbar.is_enabled()) + return true; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!m_undoredo_toolbar.init(background_data)) + { + // unable to init the toolbar texture, disable it + m_undoredo_toolbar.set_enabled(false); + return true; + } + +// m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Vertical); + m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); + m_undoredo_toolbar.set_layout_orientation(GLToolbar::Layout::Top); + m_undoredo_toolbar.set_border(5.0f); + m_undoredo_toolbar.set_separator_size(5); + m_undoredo_toolbar.set_gap_size(2); + + GLToolbarItem::Data item; item.name = "undo"; item.icon_filename = "undo_toolbar.svg"; item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]"; - item.sprite_id = 11; + item.sprite_id = 0; item.left.toggable = false; item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); }; item.right.toggable = true; @@ -3661,18 +3718,18 @@ bool GLCanvas3D::_init_toolbar() item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); }; item.visibility_callback = []()->bool { return true; }; item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); }; - if (!m_toolbar.add_item(item)) + if (!m_undoredo_toolbar.add_item(item)) return false; item.name = "redo"; item.icon_filename = "redo_toolbar.svg"; item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]"; - item.sprite_id = 12; + item.sprite_id = 1; item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); }; item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, 0.5f * (left + right)); }; item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); }; - if (!m_toolbar.add_item(item)) + if (!m_undoredo_toolbar.add_item(item)) return false; return true; @@ -4003,7 +4060,8 @@ void GLCanvas3D::_render_overlays() const _render_gizmos_overlay(); _render_warning_texture(); _render_legend_texture(); - _render_toolbar(); + _render_main_toolbar(); + _render_undoredo_toolbar(); _render_view_toolbar(); if ((m_layers_editing.last_object_id >= 0) && (m_layers_editing.object_max_z() > 0.0f)) @@ -4093,64 +4151,57 @@ void GLCanvas3D::_render_gizmos_overlay() const m_gizmos.render_overlay(); } -void GLCanvas3D::_render_toolbar() const +void GLCanvas3D::_render_main_toolbar() const { + if (!m_main_toolbar.is_enabled()) + return; + #if ENABLE_RETINA_GL -// m_toolbar.set_scale(m_retina_helper->get_scale_factor()); +// m_main_toolbar.set_scale(m_retina_helper->get_scale_factor()); const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true); - m_toolbar.set_scale(scale); //! #ys_FIXME_experiment + m_main_toolbar.set_scale(scale); //! #ys_FIXME_experiment #else -// m_toolbar.set_scale(m_canvas->GetContentScaleFactor()); -// m_toolbar.set_scale(wxGetApp().em_unit()*0.1f); +// m_main_toolbar.set_scale(m_canvas->GetContentScaleFactor()); +// m_main_toolbar.set_scale(wxGetApp().em_unit()*0.1f); const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(true)); - m_toolbar.set_icons_size(size); //! #ys_FIXME_experiment + m_main_toolbar.set_icons_size(size); //! #ys_FIXME_experiment #endif // ENABLE_RETINA_GL Size cnv_size = get_canvas_size(); float zoom = (float)m_camera.get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation(); + float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; + float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width()) * inv_zoom; - float top = 0.0f; - float left = 0.0f; - switch (m_toolbar.get_layout_type()) - { - default: - case GLToolbar::Layout::Horizontal: - { - // centers the toolbar on the top edge of the 3d scene - if (orientation == GLToolbar::Layout::Top) - { - top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - left = -0.5f * m_toolbar.get_width() * inv_zoom; - } - else - { - top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; - left = -0.5f * m_toolbar.get_width() * inv_zoom; - } - break; - } - case GLToolbar::Layout::Vertical: - { - // centers the toolbar on the right edge of the 3d scene - if (orientation == GLToolbar::Layout::Left) - { - top = 0.5f * m_toolbar.get_height() * inv_zoom; - left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; - } - else - { - top = 0.5f * m_toolbar.get_height() * inv_zoom; - left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom; - } - break; - } - } - m_toolbar.set_position(top, left); + m_main_toolbar.set_position(top, left); + m_main_toolbar.render(*this); +} - m_toolbar.render(*this); +void GLCanvas3D::_render_undoredo_toolbar() const +{ + if (!m_undoredo_toolbar.is_enabled()) + return; + +#if ENABLE_RETINA_GL +// m_undoredo_toolbar.set_scale(m_retina_helper->get_scale_factor()); + const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true); + m_undoredo_toolbar.set_scale(scale); //! #ys_FIXME_experiment +#else +// m_undoredo_toolbar.set_scale(m_canvas->GetContentScaleFactor()); +// m_undoredo_toolbar.set_scale(wxGetApp().em_unit()*0.1f); + const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(true)); + m_undoredo_toolbar.set_icons_size(size); //! #ys_FIXME_experiment +#endif // ENABLE_RETINA_GL + + Size cnv_size = get_canvas_size(); + float zoom = (float)m_camera.get_zoom(); + float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + + float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; + float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width())) * inv_zoom; + m_undoredo_toolbar.set_position(top, left); + m_undoredo_toolbar.render(*this); } void GLCanvas3D::_render_view_toolbar() const @@ -5672,14 +5723,14 @@ void GLCanvas3D::_update_selection_from_hover() bool GLCanvas3D::_deactivate_undo_redo_toolbar_items() { - if (m_toolbar.is_item_pressed("undo")) + if (m_undoredo_toolbar.is_item_pressed("undo")) { - m_toolbar.force_right_action(m_toolbar.get_item_id("undo"), *this); + m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("undo"), *this); return true; } - else if (m_toolbar.is_item_pressed("redo")) + else if (m_undoredo_toolbar.is_item_pressed("redo")) { - m_toolbar.force_right_action(m_toolbar.get_item_id("redo"), *this); + m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("redo"), *this); return true; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 6bb17da4a9..022262c90a 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -438,7 +438,8 @@ private: Shader m_shader; Mouse m_mouse; mutable GLGizmosManager m_gizmos; - mutable GLToolbar m_toolbar; + mutable GLToolbar m_main_toolbar; + mutable GLToolbar m_undoredo_toolbar; ClippingPlane m_clipping_planes[2]; mutable ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; @@ -551,7 +552,8 @@ public: void enable_moving(bool enable); void enable_gizmos(bool enable); void enable_selection(bool enable); - void enable_toolbar(bool enable); + void enable_main_toolbar(bool enable); + void enable_undoredo_toolbar(bool enable); void enable_dynamic_background(bool enable); void allow_multisample(bool allow); @@ -644,10 +646,16 @@ public: void start_keeping_dirty() { m_keep_dirty = true; } void stop_keeping_dirty() { m_keep_dirty = false; } + unsigned int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); } + void force_main_toolbar_left_action(unsigned int item_id) { m_main_toolbar.force_left_action(item_id, *this); } + void force_main_toolbar_right_action(unsigned int item_id) { m_main_toolbar.force_right_action(item_id, *this); } + private: bool _is_shown_on_screen() const; - bool _init_toolbar(); + bool _init_toolbars(); + bool _init_main_toolbar(); + bool _init_undoredo_toolbar(); bool _set_current(); void _resize(unsigned int w, unsigned int h); @@ -674,7 +682,8 @@ private: void _render_volumes_for_picking() const; void _render_current_gizmo() const; void _render_gizmos_overlay() const; - void _render_toolbar() const; + void _render_main_toolbar() const; + void _render_undoredo_toolbar() const; void _render_view_toolbar() const; #if ENABLE_SHOW_CAMERA_TARGET void _render_camera_target() const; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 0002eda2d0..cdd80ed170 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -403,6 +403,9 @@ void GLToolbar::render(const GLCanvas3D& parent) const bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) { + if (!m_enabled) + return false; + Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY()); bool processed = false; @@ -1009,9 +1012,6 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const float bg_right = right; float bg_top = top; float bg_bottom = bottom; - float bg_width = right - left; - float bg_height = top - bottom; - float bg_min_size = std::min(bg_width, bg_height); float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; @@ -1139,9 +1139,6 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const float bg_right = right; float bg_top = top; float bg_bottom = bottom; - float bg_width = right - left; - float bg_height = top - bottom; - float bg_min_size = std::min(bg_width, bg_height); float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 0ba447c1bc..36354ab240 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -63,7 +63,8 @@ bool View3D::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_ m_canvas->set_config(config); m_canvas->enable_gizmos(true); m_canvas->enable_selection(true); - m_canvas->enable_toolbar(true); + m_canvas->enable_main_toolbar(true); + m_canvas->enable_undoredo_toolbar(true); wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 74636bbdc1..76b16f0554 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3639,8 +3639,8 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator top_snapshot_flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE; bool new_variable_layer_editing_active = (new_flags & UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE) != 0; // Disable layer editing before the Undo / Redo jump. - if (! new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) - view3D->enable_layers_editing(false); + if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) + view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); // Do the jump in time. if (it_snapshot->timestamp < this->undo_redo_stack.active_snapshot_time() ? this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, top_snapshot_flags, it_snapshot->timestamp) : @@ -3657,8 +3657,8 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator this->update_after_undo_redo(temp_snapshot_was_taken); // Enable layer editing after the Undo / Redo jump. if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) - view3D->enable_layers_editing(true); - } + view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); + } } void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */) From 3285bf7945d48e91f8f266866e21fc8847ec9ac0 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 19 Jul 2019 15:47:10 +0200 Subject: [PATCH 365/627] Fixed conflicts after pulling from master --- src/slic3r/GUI/Plater.cpp | 50 +++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5b8d6eca36..bc7b09975e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3589,10 +3589,10 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config. // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. if (this->printer_technology == ptFFF) { - const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config; - model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y")); - model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); - } + const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y")); + model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); + } this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, flags); this->undo_redo_stack.release_least_recently_used(); // Save the last active preset name of a particular printer technology. @@ -3642,10 +3642,10 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config. // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. if (this->printer_technology == ptFFF) { - const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config; - model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y")); - model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); - } + const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y")); + model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); + } // Flags made of Snapshot::Flags enum values. unsigned int new_flags = it_snapshot->flags; unsigned int top_snapshot_flags = 0; @@ -3668,23 +3668,23 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator // This also switches the printer technology based on the printer technology of the active printer profile. wxGetApp().load_current_presets(); } - //FIXME updating the Print config from the Wipe tower config values at the ModelWipeTower. - // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. - if (this->printer_technology == ptFFF) { - const DynamicPrintConfig ¤t_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; - Vec2d current_position(current_config.opt_float("wipe_tower_x"), current_config.opt_float("wipe_tower_y")); - double current_rotation = current_config.opt_float("wipe_tower_rotation_angle"); - if (current_position != model.wipe_tower.position || current_rotation != model.wipe_tower.rotation) { - DynamicPrintConfig new_config; - new_config.set_key_value("wipe_tower_x", new ConfigOptionFloat(model.wipe_tower.position.x())); - new_config.set_key_value("wipe_tower_y", new ConfigOptionFloat(model.wipe_tower.position.y())); - new_config.set_key_value("wipe_tower_rotation_angle", new ConfigOptionFloat(model.wipe_tower.rotation)); - Tab *tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); - tab_print->load_config(new_config); - tab_print->update_dirty(); - } - } - this->update_after_undo_redo(temp_snapshot_was_taken); + //FIXME updating the Print config from the Wipe tower config values at the ModelWipeTower. + // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. + if (this->printer_technology == ptFFF) { + const DynamicPrintConfig ¤t_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + Vec2d current_position(current_config.opt_float("wipe_tower_x"), current_config.opt_float("wipe_tower_y")); + double current_rotation = current_config.opt_float("wipe_tower_rotation_angle"); + if (current_position != model.wipe_tower.position || current_rotation != model.wipe_tower.rotation) { + DynamicPrintConfig new_config; + new_config.set_key_value("wipe_tower_x", new ConfigOptionFloat(model.wipe_tower.position.x())); + new_config.set_key_value("wipe_tower_y", new ConfigOptionFloat(model.wipe_tower.position.y())); + new_config.set_key_value("wipe_tower_rotation_angle", new ConfigOptionFloat(model.wipe_tower.rotation)); + Tab *tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); + tab_print->load_config(new_config); + tab_print->update_dirty(); + } + } + this->update_after_undo_redo(temp_snapshot_was_taken); // Enable layer editing after the Undo / Redo jump. if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); From 152c2fe0c0d9cb662ca7d51edc2e48376eeca215 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 19 Jul 2019 15:59:23 +0200 Subject: [PATCH 366/627] Undo / Redo pull down menu scaling fix on OSX Retina --- src/slic3r/GUI/GLCanvas3D.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 47b9ad48a4..247334fb95 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3488,8 +3488,11 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) int hovered = m_imgui_undo_redo_hovered_pos; int selected = -1; const float em = static_cast(wxGetApp().em_unit()); +#if ENABLE_RETINA_GL + em *= m_retina_helper->get_scale_factor(); +#endif - if (imgui->undo_redo_list(ImVec2(12 * em, 20 * em), is_undo, &string_getter, hovered, selected)) + if (imgui->undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected)) m_imgui_undo_redo_hovered_pos = hovered; else m_imgui_undo_redo_hovered_pos = -1; From 07a3072622147cf70e3fed9075e5eb713f41055f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 19 Jul 2019 17:14:37 +0200 Subject: [PATCH 367/627] Simplified loading of the SLA support structures into the scene. Fixed referesh of SLA support structures after Undo / Redo and when moving an object outside / inside the build volume. --- src/slic3r/GUI/GLCanvas3D.cpp | 65 +++++++---------------------------- src/slic3r/GUI/GLCanvas3D.hpp | 2 -- 2 files changed, 12 insertions(+), 55 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 69d33649d6..265f6eae77 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1858,6 +1858,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re PrinterTechnology printer_technology = m_process->current_printer_technology(); int volume_idx_wipe_tower_old = -1; + if (printer_technology == ptSLA) + // Always do the full refresh in SLA mode to show / hide SLA support structures when an object is moved outside / inside the build volume. + m_regenerate_volumes = true; + if (m_regenerate_volumes) { // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). @@ -1890,7 +1894,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re state.step[istep].state = PrintStateBase::INVALID; else for (const ModelInstance *model_instance : print_object->model_object()->instances) - aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); + // Only the instances, which are currently printable, will have the SLA support structures kept. + // The instances outside the print bed will have the GLVolumes of their support structures released. + if (model_instance->is_printable()) + aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); } } sla_support_state.emplace_back(state); @@ -2042,7 +2049,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed for (GLVolume* volume : m_volumes.volumes) - volume->set_sla_shift_z(shift_zs[volume->object_idx()]); + if (volume->object_idx() < m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) + volume->set_sla_shift_z(shift_zs[volume->object_idx()]); } if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) @@ -2198,7 +2206,8 @@ void GLCanvas3D::load_sla_preview() if ((m_canvas != nullptr) && (print != nullptr)) { _set_current(); - _load_sla_shells(); + // Reload the SLA support structures into GLVolumes. + this->reload_scene(true, true); _update_sla_shells_outside_state(); _show_warning_texture_if_needed(WarningTexture::SlaSupportsOutside); } @@ -5461,56 +5470,6 @@ void GLCanvas3D::_load_fff_shells() } } -void GLCanvas3D::_load_sla_shells() -{ - //FIXME use reload_scene -#if 1 - const SLAPrint* print = this->sla_print(); - if (print->objects().empty()) - // nothing to render, return - return; - - auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance, - const TriangleMesh &mesh, const float color[4], bool outside_printer_detection_enabled) { - m_volumes.volumes.emplace_back(new GLVolume(color)); - GLVolume& v = *m_volumes.volumes.back(); - v.indexed_vertex_array.load_mesh(mesh); - v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; - v.composite_id.volume_id = volume_id; - v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0)); - v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation)); - v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.); - v.set_convex_hull(mesh.convex_hull_3d()); - }; - - // adds objects' volumes - for (const SLAPrintObject* obj : print->objects()) - if (obj->is_step_done(slaposSliceSupports)) { - unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); - for (const SLAPrintObject::Instance& instance : obj->instances()) { - add_volume(*obj, 0, instance, obj->transformed_mesh(), GLVolume::MODEL_COLOR[0], true); - // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when - // through the update_volumes_colors_by_extruder() call. - m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); - if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree)) - add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true); - if (obj->is_step_done(slaposBasePool) && obj->has_mesh(slaposBasePool)) - add_volume(*obj, -int(slaposBasePool), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); - } - double shift_z = obj->get_current_elevation(); - for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { - GLVolume& v = *m_volumes.volumes[i]; - // apply shift z - v.set_sla_shift_z(shift_z); - } - } - - update_volumes_colors_by_extruder(); -#else - this->reload_scene(true, true); -#endif -} - void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data) { unsigned int size = (unsigned int)m_gcode_preview_volume_index.first_volumes.size(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 022262c90a..487416a144 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -730,8 +730,6 @@ private: void _load_gcode_unretractions(const GCodePreviewData& preview_data); // generates objects and wipe tower geometry void _load_fff_shells(); - // generates objects geometry for sla - void _load_sla_shells(); // sets gcode geometry visibility according to user selection void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data); void _update_toolpath_volumes_outside_state(); From 47df9506bb1086b04510a85c0cf3ad70ac48aaeb Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 19 Jul 2019 17:16:20 +0200 Subject: [PATCH 368/627] Fix of OSX Imgui Undo / Redo pull down list scaling issue. --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 265f6eae77..4255f4ecf9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3514,7 +3514,7 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x) int hovered = m_imgui_undo_redo_hovered_pos; int selected = -1; - const float em = static_cast(wxGetApp().em_unit()); + float em = static_cast(wxGetApp().em_unit()); #if ENABLE_RETINA_GL em *= m_retina_helper->get_scale_factor(); #endif From 99e2fe20a50b5003b87b39cb3f4a79b749bf7506 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 19 Jul 2019 17:56:16 +0200 Subject: [PATCH 369/627] Fix a memory access bug in ObjectManipulation --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 308b3f208d..0a2877d922 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -199,18 +199,17 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off.png"); m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png"); - for (const std::string axis : { "x", "y", "z" }) { - const std::string label = boost::algorithm::to_upper_copy(axis); - def.set_default_value(new ConfigOptionString{ " " + label }); - Option option = Option(def, axis + "_axis_legend"); - - unsigned int axis_idx = (axis[0] - 'x'); // 0, 1 or 2 + static const char axes[] = { 'X', 'Y', 'Z' }; + for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) { + const char label = axes[axis_idx]; + def.set_default_value(new ConfigOptionString{ std::string(" ") + label }); + Option option(def, std::string() + label + "_axis_legend"); // We will add a button to toggle mirroring to each axis: - auto mirror_button = [this, mirror_btn_width, axis_idx, &label](wxWindow* parent) { + auto mirror_button = [this, mirror_btn_width, axis_idx, label](wxWindow* parent) { wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width); auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off.png", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); - btn->SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), label)); + btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label)); m_mirror_buttons[axis_idx].first = btn; m_mirror_buttons[axis_idx].second = mbShown; @@ -245,7 +244,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : canvas->do_mirror(L("Set Mirror")); UpdateAndShow(true); }); - return sizer; + + return sizer; }; option.side_widget = mirror_button; From 63fada9469a4285212f70782863f713fbf45bd02 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 19 Jul 2019 18:10:10 +0200 Subject: [PATCH 370/627] Arrange selection if shift is pressed. Remove first item centering --- src/libslic3r/Arrange.cpp | 57 ++++++++++++--------- src/slic3r/GUI/Plater.cpp | 105 +++++++++++++++++++++++--------------- 2 files changed, 97 insertions(+), 65 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index f692f91c78..77f4f55db3 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -136,6 +136,9 @@ protected: ItemGroup m_remaining; // Remaining items (m_items at the beginning) ItemGroup m_items; // The items to be packed + // Used only for preloading objects before arrange + // std::vector m_preload_idx; // spatial index for preloaded beds + template ArithmeticOnly norm(T val) { return double(val) / m_norm; @@ -318,6 +321,7 @@ public: m_pilebb = sl::boundingBox(merged_pile); m_rtree.clear(); +// m_preload_idx.clear(); m_smallsrtree.clear(); // We will treat big items (compared to the print bed) differently @@ -341,7 +345,7 @@ public: } template inline void operator()(Args&&...args) { - m_rtree.clear(); + m_rtree.clear(); /*m_preload_idx.clear();*/ m_pck.execute(std::forward(args)...); } @@ -358,19 +362,25 @@ public: for(unsigned idx = 0; idx < fixeditems.size(); ++idx) { Item& itm = fixeditems[idx]; itm.markAsFixed(); - m_rtree.insert({itm.boundingBox(), idx}); +// size_t bedidx = itm.binId() < 0 ? 0u : size_t(itm.binId()); + +// while (m_preload_idx.size() <= bedidx) m_preload_idx.emplace_back(); +// m_preload_idx[bedidx].insert({itm.boundingBox(), idx}); } m_pck.configure(m_pconf); } - bool is_colliding(const Item& item) { - if(m_rtree.empty()) return false; - std::vector result; - m_rtree.query(bgi::intersects(item.boundingBox()), - std::back_inserter(result)); - return !result.empty(); - } +// int is_colliding(const Item& item) { +// size_t bedidx = item.binId() < 0 ? 0u : size_t(item.binId()); +// if (m_preload_idx.size() <= bedidx || m_preload_idx[bedidx].empty()) +// return false; + +// std::vector result; +// m_preload_idx[bedidx].query(bgi::intersects(item.boundingBox()), +// std::back_inserter(result)); +// return !result.empty(); +// } }; template<> std::function AutoArranger::get_objfn() @@ -534,24 +544,21 @@ void _arrange( ++it : it = excludes.erase(it); // If there is something on the plate - if(!excludes.empty()) - { - arranger.preload(excludes); - auto binbb = sl::boundingBox(corrected_bin); + if (!excludes.empty()) { + arranger.preload(excludes); +// auto binbb = sl::boundingBox(corrected_bin); - // Try to put the first item to the center, as the arranger - // will not do this for us. - for (Item &itm : shapes) { - auto ibb = itm.boundingBox(); - auto d = binbb.center() - ibb.center(); - itm.translate(d); +// // Try to put the first item to the center, as the arranger +// // will not do this for us. +// for (Item &itm : shapes) { +// auto ibb = itm.boundingBox(); +// auto d = binbb.center() - ibb.center(); +// itm.translate(d); +// itm.binId(UNARRANGED); - if (!arranger.is_colliding(itm)) { - itm.markAsFixed(); - break; - } - } - } +// if (!arranger.is_colliding(itm)) { itm.markAsFixed(); break; } +// } + } std::vector> inp; inp.reserve(shapes.size() + excludes.size()); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 58f0db2928..48a05110e7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1421,8 +1421,9 @@ struct Plater::priv wxQueueEvent(this, evt); } - priv &plater() { return *m_plater; } - bool was_canceled() const { return m_canceled.load(); } + priv & plater() { return *m_plater; } + const priv &plater() const { return *m_plater; } + bool was_canceled() const { return m_canceled.load(); } // Launched just before start(), a job can use it to prepare internals virtual void prepare() {} @@ -1537,51 +1538,76 @@ struct Plater::priv ArrangePolygons m_selected, m_unselected; - protected: - - void prepare() override - { - // Get the selection map - Selection& sel = plater().get_selection(); - const Selection::ObjectIdxsToInstanceIdxsMap &selmap = - sel.get_content(); - - Model &model = plater().model; + // clear m_selected and m_unselected, reserve space for next usage + void clear_input() { + const Model &model = plater().model; size_t count = 0; // To know how much space to reserve for (auto obj : model.objects) count += obj->instances.size(); - m_selected.clear(), m_unselected.clear(); m_selected.reserve(count + 1 /* for optional wti */); m_unselected.reserve(count + 1 /* for optional wti */); - - // Stride between logical beds + } + + // Stride between logical beds + coord_t bed_stride() const { double bedwidth = plater().bed_shape_bb().size().x(); - coord_t stride = scaled((1. + LOGICAL_BED_GAP) * bedwidth); + return scaled((1. + LOGICAL_BED_GAP) * bedwidth); + } + + // Set up arrange polygon for a ModelInstance and Wipe tower + template 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()) { + auto t = p.translation; 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) + m_selected.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 + obj_sel(model.objects.size(), nullptr); + + for (auto &s : plater().get_selection().get_content()) + if (s.first < int(obj_sel.size())) obj_sel[s.first] = &s.second; // Go through the objects and check if inside the selection for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { - auto oit = selmap.find(int(oidx)); + const Selection::InstanceIdxsList * instlist = obj_sel[oidx]; ModelObject *mo = model.objects[oidx]; std::vector inst_sel(mo->instances.size(), false); - if (oit != selmap.end()) - for (auto inst_id : oit->second) inst_sel[inst_id] = true; + if (instlist) + for (auto inst_id : *instlist) inst_sel[inst_id] = true; for (size_t i = 0; i < inst_sel.size(); ++i) { - ModelInstance *mi = mo->instances[i]; - ArrangePolygon ap = mi->get_arrange_polygon(); - ap.priority = 0; - ap.bed_idx = ap.translation.x() / stride; + ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); - ap.setter = [mi, stride](const ArrangePolygon &p) { - if (p.is_arranged()) { - auto t = p.translation; t.x() += p.bed_idx * stride; - mi->apply_arrange_result(t, p.rotation); - } - }; - inst_sel[i] ? m_selected.emplace_back(std::move(ap)) : m_unselected.emplace_back(std::move(ap)); @@ -1590,17 +1616,9 @@ struct Plater::priv auto& wti = plater().updated_wipe_tower(); if (wti) { - ArrangePolygon ap = wti.get_arrange_polygon(); - ap.bed_idx = ap.translation.x() / stride; - ap.priority = 1; // Wipe tower should be on physical bed - ap.setter = [&wti, stride](const ArrangePolygon &p) { - if (p.is_arranged()) { - auto t = p.translation; t.x() += p.bed_idx * stride; - wti.apply_arrange_result(t, p.rotation); - } - }; + ArrangePolygon &&ap = get_arrange_poly(&wti); - sel.is_wipe_tower() ? + plater().get_selection().is_wipe_tower() ? m_selected.emplace_back(std::move(ap)) : m_unselected.emplace_back(std::move(ap)); } @@ -1614,6 +1632,13 @@ struct Plater::priv 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 Job::Job; From 10e86a06cc5b9f3e8c9755cdd5181da8493b89f5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 19 Jul 2019 18:16:36 +0200 Subject: [PATCH 371/627] Dont do force_full_scene_refresh after arrange. --- src/slic3r/GUI/Plater.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 48a05110e7..52795fa41d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1647,17 +1647,13 @@ struct Plater::priv void process() override; void finalize() override { - - if (was_canceled()) { // Ignore the arrange result if aborted. - Job::finalize(); - return; - } + // Ignore the arrange result if aborted. + if (was_canceled()) return; // Apply the arrange result to all selected objects for (ArrangePolygon &ap : m_selected) ap.apply(); - // Call original finalize (will update the scene) - Job::finalize(); + plater().update(false /*dont force_full_scene_refresh*/); } }; From 33c0683d111e6e8e465e1a3e7a53e871ec16673d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Sat, 20 Jul 2019 12:02:29 +0200 Subject: [PATCH 372/627] Reworked rendering of toolbars background texture --- src/slic3r/GUI/GLCanvas3D.cpp | 6 +- src/slic3r/GUI/GLToolbar.cpp | 251 +++++++--------------- src/slic3r/GUI/GLToolbar.hpp | 30 ++- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 120 +++++------ src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 1 + src/slic3r/GUI/Plater.cpp | 3 +- 6 files changed, 164 insertions(+), 247 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 4255f4ecf9..42803f79af 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3564,7 +3564,8 @@ bool GLCanvas3D::_init_main_toolbar() // m_main_toolbar.set_layout_type(GLToolbar::Layout::Vertical); m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); - m_main_toolbar.set_layout_orientation(GLToolbar::Layout::Top); + m_main_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); + m_main_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); m_main_toolbar.set_border(5.0f); m_main_toolbar.set_separator_size(5); m_main_toolbar.set_gap_size(2); @@ -3712,7 +3713,8 @@ bool GLCanvas3D::_init_undoredo_toolbar() // m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Vertical); m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Horizontal); - m_undoredo_toolbar.set_layout_orientation(GLToolbar::Layout::Top); + m_undoredo_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left); + m_undoredo_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); m_undoredo_toolbar.set_border(5.0f); m_undoredo_toolbar.set_separator_size(5); m_undoredo_toolbar.set_gap_size(2); diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index cdd80ed170..0ee8945e4f 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -131,7 +131,8 @@ const float GLToolbar::Default_Icons_Size = 40.0f; GLToolbar::Layout::Layout() : type(Horizontal) - , orientation(Center) + , horizontal_orientation(HO_Center) + , vertical_orientation(VO_Center) , top(0.0f) , left(0.0f) , border(0.0f) @@ -191,16 +192,6 @@ void GLToolbar::set_layout_type(GLToolbar::Layout::EType type) m_layout.dirty = true; } -GLToolbar::Layout::EOrientation GLToolbar::get_layout_orientation() const -{ - return m_layout.orientation; -} - -void GLToolbar::set_layout_orientation(GLToolbar::Layout::EOrientation orientation) -{ - m_layout.orientation = orientation; -} - void GLToolbar::set_position(float top, float left) { m_layout.top = top; @@ -969,6 +960,84 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& return -1; } +void GLToolbar::render_background(float left, float top, float right, float bottom, float border) const +{ + unsigned int tex_id = m_background_texture.texture.get_id(); + float tex_width = (float)m_background_texture.texture.get_width(); + float tex_height = (float)m_background_texture.texture.get_height(); + if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0)) + { + float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; + float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; + + float internal_left = left + border; + float internal_right = right - border; + float internal_top = top - border; + float internal_bottom = bottom + border; + + float left_uv = 0.0f; + float right_uv = 1.0f; + float top_uv = 1.0f; + float bottom_uv = 0.0f; + + float internal_left_uv = (float)m_background_texture.metadata.left * inv_tex_width; + float internal_right_uv = 1.0f - (float)m_background_texture.metadata.right * inv_tex_width; + float internal_top_uv = 1.0f - (float)m_background_texture.metadata.top * inv_tex_height; + float internal_bottom_uv = (float)m_background_texture.metadata.bottom * inv_tex_height; + + // top-left corner + if ((m_layout.horizontal_orientation == Layout::HO_Left) || (m_layout.vertical_orientation == Layout::VO_Top)) + GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { left_uv, internal_top_uv }, { internal_left_uv, internal_top_uv }, { internal_left_uv, top_uv }, { left_uv, top_uv } }); + + // top edge + if (m_layout.vertical_orientation == Layout::VO_Top) + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_top, top, { { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, top_uv }, { internal_left_uv, top_uv } }); + + // top-right corner + if ((m_layout.horizontal_orientation == Layout::HO_Right) || (m_layout.vertical_orientation == Layout::VO_Top)) + GLTexture::render_sub_texture(tex_id, internal_right, right, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, internal_right, right, internal_top, top, { { internal_right_uv, internal_top_uv }, { right_uv, internal_top_uv }, { right_uv, top_uv }, { internal_right_uv, top_uv } }); + + // center-left edge + if (m_layout.horizontal_orientation == Layout::HO_Left) + GLTexture::render_sub_texture(tex_id, left, internal_left, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, left, internal_left, internal_bottom, internal_top, { { left_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv }, { internal_left_uv, internal_top_uv }, { left_uv, internal_top_uv } }); + + // center + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + + // center-right edge + if (m_layout.horizontal_orientation == Layout::HO_Right) + GLTexture::render_sub_texture(tex_id, internal_right, right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, internal_right, right, internal_bottom, internal_top, { { internal_right_uv, internal_bottom_uv }, { right_uv, internal_bottom_uv }, { right_uv, internal_top_uv }, { internal_right_uv, internal_top_uv } }); + + // bottom-left corner + if ((m_layout.horizontal_orientation == Layout::HO_Left) || (m_layout.vertical_orientation == Layout::VO_Bottom)) + GLTexture::render_sub_texture(tex_id, left, internal_left, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, left, internal_left, bottom, internal_bottom, { { left_uv, bottom_uv }, { internal_left_uv, bottom_uv }, { internal_left_uv, internal_bottom_uv }, { left_uv, internal_bottom_uv } }); + + // bottom edge + if (m_layout.vertical_orientation == Layout::VO_Bottom) + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, bottom, internal_bottom, { { internal_left_uv, bottom_uv }, { internal_right_uv, bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv } }); + + // bottom-right corner + if ((m_layout.horizontal_orientation == Layout::HO_Right) || (m_layout.vertical_orientation == Layout::VO_Bottom)) + GLTexture::render_sub_texture(tex_id, internal_right, right, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + else + GLTexture::render_sub_texture(tex_id, internal_right, right, bottom, internal_bottom, { { internal_right_uv, bottom_uv }, { right_uv, bottom_uv }, { right_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv } }); + } +} + void GLToolbar::render_horizontal(const GLCanvas3D& parent) const { unsigned int tex_id = m_icons_texture.get_id(); @@ -994,85 +1063,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const float right = left + scaled_width; float bottom = top - scaled_height; - // renders background - unsigned int bg_tex_id = m_background_texture.texture.get_id(); - float bg_tex_width = (float)m_background_texture.texture.get_width(); - float bg_tex_height = (float)m_background_texture.texture.get_height(); - if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) - { - float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; - float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; - - float bg_uv_left = 0.0f; - float bg_uv_right = 1.0f; - float bg_uv_top = 1.0f; - float bg_uv_bottom = 0.0f; - - float bg_left = left; - float bg_right = right; - float bg_top = top; - float bg_bottom = bottom; - - float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; - float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; - float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; - float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; - - float bg_i_left = bg_left + scaled_border; - float bg_i_right = bg_right - scaled_border; - float bg_i_top = bg_top - scaled_border; - float bg_i_bottom = bg_bottom + scaled_border; - - switch (m_layout.orientation) - { - case Layout::Top: - { - bg_uv_top = bg_uv_i_top; - bg_i_top = bg_top; - break; - } - case Layout::Bottom: - { - bg_uv_bottom = bg_uv_i_bottom; - bg_i_bottom = bg_bottom; - break; - } - case Layout::Center: - { - break; - } - }; - - if ((m_layout.border > 0) && (bg_uv_top != bg_uv_i_top)) - { - if (bg_uv_left != bg_uv_i_left) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); - - if (bg_uv_right != bg_uv_i_right) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); - } - - if ((m_layout.border > 0) && (bg_uv_left != bg_uv_i_left)) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); - - if ((m_layout.border > 0) && (bg_uv_right != bg_uv_i_right)) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); - - if ((m_layout.border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) - { - if (bg_uv_left != bg_uv_i_left) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); - - if (bg_uv_right != bg_uv_i_right) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); - } - } + render_background(left, top, right, bottom, scaled_border); left += scaled_border; top -= scaled_border; @@ -1121,85 +1112,7 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const float right = left + scaled_width; float bottom = top - scaled_height; - // renders background - unsigned int bg_tex_id = m_background_texture.texture.get_id(); - float bg_tex_width = (float)m_background_texture.texture.get_width(); - float bg_tex_height = (float)m_background_texture.texture.get_height(); - if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) - { - float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; - float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; - - float bg_uv_left = 0.0f; - float bg_uv_right = 1.0f; - float bg_uv_top = 1.0f; - float bg_uv_bottom = 0.0f; - - float bg_left = left; - float bg_right = right; - float bg_top = top; - float bg_bottom = bottom; - - float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; - float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; - float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; - float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; - - float bg_i_left = bg_left + scaled_border; - float bg_i_right = bg_right - scaled_border; - float bg_i_top = bg_top - scaled_border; - float bg_i_bottom = bg_bottom + scaled_border; - - switch (m_layout.orientation) - { - case Layout::Left: - { - bg_uv_left = bg_uv_i_left; - bg_i_left = bg_left; - break; - } - case Layout::Right: - { - bg_uv_right = bg_uv_i_right; - bg_i_right = bg_right; - break; - } - case Layout::Center: - { - break; - } - }; - - if ((m_layout.border > 0) && (bg_uv_top != bg_uv_i_top)) - { - if (bg_uv_left != bg_uv_i_left) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); - - if (bg_uv_right != bg_uv_i_right) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); - } - - if ((m_layout.border > 0) && (bg_uv_left != bg_uv_i_left)) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); - - if ((m_layout.border > 0) && (bg_uv_right != bg_uv_i_right)) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); - - if ((m_layout.border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) - { - if (bg_uv_left != bg_uv_i_left) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); - - if (bg_uv_right != bg_uv_i_right) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); - } - } + render_background(left, top, right, bottom, scaled_border); left += scaled_border; top -= scaled_border; diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 875b2f9f69..9e1eef5334 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -189,18 +189,25 @@ public: Num_Types }; - enum EOrientation : unsigned int + enum EHorizontalOrientation : unsigned char { - Top, - Bottom, - Left, - Right, - Center, - Num_Locations + HO_Left, + HO_Center, + HO_Right, + Num_Horizontal_Orientations + }; + + enum EVerticalOrientation : unsigned char + { + VO_Top, + VO_Center, + VO_Bottom, + Num_Vertical_Orientations }; EType type; - EOrientation orientation; + EHorizontalOrientation horizontal_orientation; + EVerticalOrientation vertical_orientation; float top; float left; float border; @@ -254,8 +261,10 @@ public: Layout::EType get_layout_type() const; void set_layout_type(Layout::EType type); - Layout::EOrientation get_layout_orientation() const; - void set_layout_orientation(Layout::EOrientation orientation); + Layout::EHorizontalOrientation get_horizontal_orientation() const { return m_layout.horizontal_orientation; } + void set_horizontal_orientation(Layout::EHorizontalOrientation orientation) { m_layout.horizontal_orientation = orientation; } + Layout::EVerticalOrientation get_vertical_orientation() const { return m_layout.vertical_orientation; } + void set_vertical_orientation(Layout::EVerticalOrientation orientation) { m_layout.vertical_orientation = orientation; } void set_position(float top, float left); void set_border(float border); @@ -311,6 +320,7 @@ private: int contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; int contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; + void render_background(float left, float top, float right, float bottom, float border) const; void render_horizontal(const GLCanvas3D& parent) const; void render_vertical(const GLCanvas3D& parent) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index df53b40aa9..2aaf55424a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -879,6 +879,60 @@ void GLGizmosManager::reset() m_gizmos.clear(); } +void GLGizmosManager::render_background(float left, float top, float right, float bottom, float border) const +{ + unsigned int tex_id = m_background_texture.texture.get_id(); + float tex_width = (float)m_background_texture.texture.get_width(); + float tex_height = (float)m_background_texture.texture.get_height(); + if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0)) + { + float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f; + float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f; + + float internal_left = left + border; + float internal_right = right - border; + float internal_top = top - border; + float internal_bottom = bottom + border; + + float left_uv = 0.0f; + float right_uv = 1.0f; + float top_uv = 1.0f; + float bottom_uv = 0.0f; + + float internal_left_uv = (float)m_background_texture.metadata.left * inv_tex_width; + float internal_right_uv = 1.0f - (float)m_background_texture.metadata.right * inv_tex_width; + float internal_top_uv = 1.0f - (float)m_background_texture.metadata.top * inv_tex_height; + float internal_bottom_uv = (float)m_background_texture.metadata.bottom * inv_tex_height; + + // top-left corner + GLTexture::render_sub_texture(tex_id, left, internal_left, internal_top, top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + + // top edge + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_top, top, { { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, top_uv }, { internal_left_uv, top_uv } }); + + // top-right corner + GLTexture::render_sub_texture(tex_id, internal_right, right, internal_top, top, { { internal_right_uv, internal_top_uv }, { right_uv, internal_top_uv }, { right_uv, top_uv }, { internal_right_uv, top_uv } }); + + // center-left edge + GLTexture::render_sub_texture(tex_id, left, internal_left, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + + // center + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + + // center-right edge + GLTexture::render_sub_texture(tex_id, internal_right, right, internal_bottom, internal_top, { { internal_right_uv, internal_bottom_uv }, { right_uv, internal_bottom_uv }, { right_uv, internal_top_uv }, { internal_right_uv, internal_top_uv } }); + + // bottom-left corner + GLTexture::render_sub_texture(tex_id, left, internal_left, bottom, internal_bottom, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } }); + + // bottom edge + GLTexture::render_sub_texture(tex_id, internal_left, internal_right, bottom, internal_bottom, { { internal_left_uv, bottom_uv }, { internal_right_uv, bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv } }); + + // bottom-right corner + GLTexture::render_sub_texture(tex_id, internal_right, right, bottom, internal_bottom, { { internal_right_uv, bottom_uv }, { right_uv, bottom_uv }, { right_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv } }); + } +} + void GLGizmosManager::do_render_overlay() const { if (m_gizmos.empty()) @@ -901,71 +955,7 @@ void GLGizmosManager::do_render_overlay() const float right = left + width * inv_zoom; float bottom = top - height * inv_zoom; - // renders background - unsigned int bg_tex_id = m_background_texture.texture.get_id(); - float bg_tex_width = (float)m_background_texture.texture.get_width(); - float bg_tex_height = (float)m_background_texture.texture.get_height(); - if ((bg_tex_id != 0) && (bg_tex_width > 0) && (bg_tex_height > 0)) - { - float inv_bg_tex_width = (bg_tex_width != 0.0f) ? 1.0f / bg_tex_width : 0.0f; - float inv_bg_tex_height = (bg_tex_height != 0.0f) ? 1.0f / bg_tex_height : 0.0f; - - float bg_uv_left = 0.0f; - float bg_uv_right = 1.0f; - float bg_uv_top = 1.0f; - float bg_uv_bottom = 0.0f; - - float bg_left = left; - float bg_right = right; - float bg_top = top; - float bg_bottom = bottom; - float bg_width = right - left; - float bg_height = top - bottom; - float bg_min_size = std::min(bg_width, bg_height); - - float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width; - float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width; - float bg_uv_i_top = 1.0f - (float)m_background_texture.metadata.top * inv_bg_tex_height; - float bg_uv_i_bottom = (float)m_background_texture.metadata.bottom * inv_bg_tex_height; - - float bg_i_left = bg_left + scaled_border; - float bg_i_right = bg_right - scaled_border; - float bg_i_top = bg_top - scaled_border; - float bg_i_bottom = bg_bottom + scaled_border; - - bg_uv_left = bg_uv_i_left; - bg_i_left = bg_left; - - if ((m_overlay_border > 0) && (bg_uv_top != bg_uv_i_top)) - { - if (bg_uv_left != bg_uv_i_left) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_top, bg_top, { { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_top }, { bg_uv_i_left, bg_uv_top } }); - - if (bg_uv_right != bg_uv_i_right) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); - } - - if ((m_overlay_border > 0) && (bg_uv_left != bg_uv_i_left)) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); - - if ((m_overlay_border > 0) && (bg_uv_right != bg_uv_i_right)) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); - - if ((m_overlay_border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) - { - if (bg_uv_left != bg_uv_i_left) - GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); - - GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_bottom, bg_i_bottom, { { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_right, bg_uv_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom } }); - - if (bg_uv_right != bg_uv_i_right) - GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_bottom, bg_i_bottom, { { bg_uv_i_right, bg_uv_bottom }, { bg_uv_right, bg_uv_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom } }); - } - } + render_background(left, top, right, bottom, scaled_border); top_x += scaled_border; top_y -= scaled_border; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 803613ec74..e1978e60dd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -187,6 +187,7 @@ public: private: void reset(); + void render_background(float left, float top, float right, float bottom, float border) const; void do_render_overlay() const; float get_total_overlay_height() const; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bc7b09975e..1acc24500f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3422,7 +3422,8 @@ void Plater::priv::init_view_toolbar() if (!view_toolbar.init(background_data)) return; - view_toolbar.set_layout_orientation(GLToolbar::Layout::Bottom); + view_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left); + view_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Bottom); view_toolbar.set_border(5.0f); view_toolbar.set_gap_size(1.0f); From b60b44ed5e297c3fa18097fc8123db83bdd22758 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Sat, 20 Jul 2019 14:03:34 +0200 Subject: [PATCH 373/627] Added additional tooltip to toolbar items and use it for undo/redo items --- src/slic3r/GUI/GLCanvas3D.cpp | 44 +++++++++++++++++++++++++++++---- src/slic3r/GUI/GLCanvas3D.hpp | 2 ++ src/slic3r/GUI/GLToolbar.cpp | 46 +++++++++++++++++++++++++++++++++-- src/slic3r/GUI/GLToolbar.hpp | 6 +++++ src/slic3r/GUI/Plater.cpp | 13 ++++++++++ src/slic3r/GUI/Plater.hpp | 1 + 6 files changed, 105 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 42803f79af..7ef15d2a41 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3033,7 +3033,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if (evt.Moving()) { m_mouse.position = pos.cast(); - std::string tooltip = ""; + std::string tooltip = L(""); if (tooltip.empty()) tooltip = m_gizmos.get_tooltip(); @@ -3725,13 +3725,29 @@ bool GLCanvas3D::_init_undoredo_toolbar() item.icon_filename = "undo_toolbar.svg"; item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]"; item.sprite_id = 0; - item.left.toggable = false; item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); }; item.right.toggable = true; item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); }; - item.visibility_callback = []()->bool { return true; }; - item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); }; + item.enabling_callback = [this]()->bool { + bool can_undo = wxGetApp().plater()->can_undo(); + unsigned int id = m_undoredo_toolbar.get_item_id("undo"); + + std::string curr_additional_tooltip; + m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); + + std::string new_additional_tooltip = L(""); + if (can_undo) + wxGetApp().plater()->undo_redo_topmost_string_getter(true, new_additional_tooltip); + + if (new_additional_tooltip != curr_additional_tooltip) + { + m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); + set_tooltip(L("")); + } + return can_undo; + }; + if (!m_undoredo_toolbar.add_item(item)) return false; @@ -3742,7 +3758,25 @@ bool GLCanvas3D::_init_undoredo_toolbar() item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); }; item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; }; item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, 0.5f * (left + right)); }; - item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); }; + item.enabling_callback = [this]()->bool { + bool can_redo = wxGetApp().plater()->can_redo(); + unsigned int id = m_undoredo_toolbar.get_item_id("redo"); + + std::string curr_additional_tooltip; + m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); + + std::string new_additional_tooltip = L(""); + if (can_redo) + wxGetApp().plater()->undo_redo_topmost_string_getter(false, new_additional_tooltip); + + if (new_additional_tooltip != curr_additional_tooltip) + { + m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); + set_tooltip(L("")); + } + return can_redo; + }; + if (!m_undoredo_toolbar.add_item(item)) return false; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 487416a144..16773b2b9a 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -649,6 +649,8 @@ public: unsigned int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); } void force_main_toolbar_left_action(unsigned int item_id) { m_main_toolbar.force_left_action(item_id, *this); } void force_main_toolbar_right_action(unsigned int item_id) { m_main_toolbar.force_right_action(item_id, *this); } + void get_undoredo_toolbar_additional_tooltip(unsigned int item_id, std::string& text) { return m_undoredo_toolbar.get_additional_tooltip(item_id, text); } + void set_undoredo_toolbar_additional_tooltip(unsigned int item_id, const std::string& text) { m_undoredo_toolbar.set_additional_tooltip(item_id, text); } private: bool _is_shown_on_screen() const; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 0ee8945e4f..ca52484177 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -48,6 +48,7 @@ GLToolbarItem::Data::Data() : name("") , icon_filename("") , tooltip("") + , additional_tooltip("") , sprite_id(-1) , visible(true) , visibility_callback(Default_Visibility_Callback) @@ -365,6 +366,31 @@ void GLToolbar::force_right_action(unsigned int item_id, GLCanvas3D& parent) do_action(GLToolbarItem::Right, item_id, parent, false); } +void GLToolbar::get_additional_tooltip(unsigned int item_id, std::string& text) +{ + if (item_id < (unsigned int)m_items.size()) + { + GLToolbarItem* item = m_items[item_id]; + if (item != nullptr) + { + text = item->get_additional_tooltip(); + return; + } + } + + text = L(""); +} + +void GLToolbar::set_additional_tooltip(unsigned int item_id, const std::string& text) +{ + if (item_id < (unsigned int)m_items.size()) + { + GLToolbarItem* item = m_items[item_id]; + if (item != nullptr) + item->set_additional_tooltip(text); + } +} + bool GLToolbar::update_items_state() { bool ret = false; @@ -427,7 +453,7 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) if (item_id == -1) { // mouse is outside the toolbar - m_tooltip = ""; + m_tooltip = L(""); } else { @@ -594,7 +620,7 @@ void GLToolbar::do_action(GLToolbarItem::EActionType type, unsigned int item_id, std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) { if (!m_enabled) - return ""; + return L(""); switch (m_layout.type) { @@ -643,7 +669,15 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC GLToolbarItem::EState state = item->get_state(); bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top); if (inside) + { tooltip = item->get_tooltip(); + if (!item->is_pressed()) + { + const std::string& additional_tooltip = item->get_additional_tooltip(); + if (!additional_tooltip.empty()) + tooltip += L("\n") + additional_tooltip; + } + } switch (state) { @@ -739,7 +773,15 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan GLToolbarItem::EState state = item->get_state(); bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top); if (inside) + { tooltip = item->get_tooltip(); + if (!item->is_pressed()) + { + const std::string& additional_tooltip = item->get_additional_tooltip(); + if (!additional_tooltip.empty()) + tooltip += L("\n") + additional_tooltip; + } + } switch (state) { diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 9e1eef5334..527317e589 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -80,6 +80,7 @@ public: std::string name; std::string icon_filename; std::string tooltip; + std::string additional_tooltip; unsigned int sprite_id; // mouse left click Option left; @@ -112,6 +113,8 @@ public: const std::string& get_name() const { return m_data.name; } const std::string& get_icon_filename() const { return m_data.icon_filename; } const std::string& get_tooltip() const { return m_data.tooltip; } + const std::string& get_additional_tooltip() const { return m_data.additional_tooltip; } + void set_additional_tooltip(const std::string& text) { m_data.additional_tooltip = text; } void do_left_action() { m_last_action_type = Left; m_data.left.action_callback(); } void do_right_action() { m_last_action_type = Right; m_data.right.action_callback(); } @@ -297,6 +300,9 @@ public: const std::string& get_tooltip() const { return m_tooltip; } + void get_additional_tooltip(unsigned int item_id, std::string& text); + void set_additional_tooltip(unsigned int item_id, const std::string& text); + // returns true if any item changed its state bool update_items_state(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1acc24500f..f05def49d4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4301,6 +4301,19 @@ bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** o return false; } +void Plater::undo_redo_topmost_string_getter(const bool is_undo, std::string& out_text) +{ + const std::vector& ss_stack = p->undo_redo_stack.snapshots(); + const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -1 : 0); + + if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) { + out_text = ss_stack[idx_in_ss_stack].name; + return; + } + + out_text = L(""); +} + void Plater::on_extruders_change(int num_extruders) { auto& choices = sidebar().combos_filament(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index d7e91f516c..e0737c6b9f 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -196,6 +196,7 @@ public: void undo_to(int selection); void redo_to(int selection); bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text); + void undo_redo_topmost_string_getter(const bool is_undo, std::string& out_text); const Slic3r::UndoRedo::Stack& undo_redo_stack() const; void on_extruders_change(int extruders_count); From f6633df57b2cd6e7fc7ec62f44dc8713a854db4b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 22 Jul 2019 09:41:34 +0200 Subject: [PATCH 374/627] Fix of SPE-987 (Slicer crash when layers are selected in right panel and settings is added) --- src/slic3r/GUI/GUI_ObjectList.cpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index f7abb41281..429cc18ee7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -359,9 +359,7 @@ DynamicPrintConfig& ObjectList::get_item_config(const wxDataViewItem& item) cons assert(item); const ItemType type = m_objects_model->GetItemType(item); - const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : - m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); - + const int obj_idx = m_objects_model->GetObjectIdByItem(item); const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; assert(obj_idx >= 0 || ((type & itVolume) && vol_idx >=0)); @@ -1037,12 +1035,17 @@ void ObjectList::get_settings_choice(const wxString& category_name) { wxArrayString names; wxArrayInt selections; + wxDataViewItem item = GetSelection(); settings_menu_hierarchy settings_menu; - const bool is_part = m_objects_model->GetParent(GetSelection()) != wxDataViewItem(0); + const bool is_part = m_objects_model->GetItemType(item) & (itVolume | itLayer); get_options_menu(settings_menu, is_part); std::vector< std::pair > *settings_list = nullptr; + if (!m_config) + m_config = &get_item_config(item); + + assert(m_config); auto opt_keys = m_config->keys(); for (auto& cat : settings_menu) @@ -1144,27 +1147,33 @@ void ObjectList::get_settings_choice(const wxString& category_name) // Add settings item for object/sub-object and show them - show_settings(add_settings_item(GetSelection(), m_config)); + if (!(m_objects_model->GetItemType(item) & (itObject | itVolume | itLayer))) + item = m_objects_model->GetTopParent(item); + show_settings(add_settings_item(item, m_config)); } void ObjectList::get_freq_settings_choice(const wxString& bundle_name) { std::vector options = get_options_for_bundle(bundle_name); + wxDataViewItem item = GetSelection(); /* Because of we couldn't edited layer_height for ItVolume from settings list, * correct options according to the selected item type : * remove "layer_height" option */ - if ((m_objects_model->GetItemType(GetSelection()) & itVolume) && bundle_name == _("Layers and Perimeters")) { + if ((m_objects_model->GetItemType(item) & itVolume) && bundle_name == _("Layers and Perimeters")) { const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); if (layer_height_it != options.end()) options.erase(layer_height_it); } + if (!m_config) + m_config = &get_item_config(item); + assert(m_config); auto opt_keys = m_config->keys(); - take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(GetSelection()) & itObject ? _(L("Object")) : _(L("Sub-object")))); + take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(item) & (itVolume|itLayer) ? _(L("Sub-object")) : _(L("Object")))); const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; for (auto& opt_key : options) @@ -1181,7 +1190,9 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) } // Add settings item for object/sub-object and show them - show_settings(add_settings_item(GetSelection(), m_config)); + if (!(m_objects_model->GetItemType(item) & (itObject | itVolume | itLayer))) + item = m_objects_model->GetTopParent(item); + show_settings(add_settings_item(item, m_config)); } void ObjectList::show_settings(const wxDataViewItem settings_item) From fb39516c650254e5132c87e9018d9af9870968b0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 22 Jul 2019 10:28:25 +0200 Subject: [PATCH 375/627] Added checking for a extruder value in ObjectList. Set value to "default" if "0" is selected. --- src/slic3r/GUI/wxExtensions.cpp | 23 +++++++++++++++++++++++ src/slic3r/GUI/wxExtensions.hpp | 22 +--------------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 7e8a2d92df..6300ada318 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -552,6 +552,29 @@ void ObjectDataViewModelNode::msw_rescale() update_settings_digest_bitmaps(); } +bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col) +{ + switch (col) + { + case 0: { + DataViewBitmapText data; + data << variant; + m_bmp = data.GetBitmap(); + m_name = data.GetText(); + return true; } + case 1: { + const wxString & val = variant.GetString(); + m_extruder = val == "0" ? _(L("default")) : val; + return true; } + case 2: + m_action_icon << variant; + return true; + default: + printf("MyObjectTreeModel::SetValue: wrong column"); + } + return false; +} + void ObjectDataViewModelNode::SetIdx(const int& idx) { m_idx = idx; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index d0edf9760b..9a6460dcb1 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -301,27 +301,7 @@ public: return m_children.GetCount(); } - bool SetValue(const wxVariant &variant, unsigned int col) - { - switch (col) - { - case 0:{ - DataViewBitmapText data; - data << variant; - m_bmp = data.GetBitmap(); - m_name = data.GetText(); - return true;} - case 1: - m_extruder = variant.GetString(); - return true; - case 2: - m_action_icon << variant; - return true; - default: - printf("MyObjectTreeModel::SetValue: wrong column"); - } - return false; - } + bool SetValue(const wxVariant &variant, unsigned int col); void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } const wxBitmap& GetBitmap() const { return m_bmp; } From 764efb1385a2ec644c1ec1b29a7e124cf256a8dc Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 22 Jul 2019 11:18:10 +0200 Subject: [PATCH 376/627] Remove disabled code. --- src/libslic3r/Arrange.cpp | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 77f4f55db3..b4cfac9546 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -136,9 +136,6 @@ protected: ItemGroup m_remaining; // Remaining items (m_items at the beginning) ItemGroup m_items; // The items to be packed - // Used only for preloading objects before arrange - // std::vector m_preload_idx; // spatial index for preloaded beds - template ArithmeticOnly norm(T val) { return double(val) / m_norm; @@ -321,7 +318,6 @@ public: m_pilebb = sl::boundingBox(merged_pile); m_rtree.clear(); -// m_preload_idx.clear(); m_smallsrtree.clear(); // We will treat big items (compared to the print bed) differently @@ -362,25 +358,10 @@ public: for(unsigned idx = 0; idx < fixeditems.size(); ++idx) { Item& itm = fixeditems[idx]; itm.markAsFixed(); -// size_t bedidx = itm.binId() < 0 ? 0u : size_t(itm.binId()); - -// while (m_preload_idx.size() <= bedidx) m_preload_idx.emplace_back(); -// m_preload_idx[bedidx].insert({itm.boundingBox(), idx}); } m_pck.configure(m_pconf); } - -// int is_colliding(const Item& item) { -// size_t bedidx = item.binId() < 0 ? 0u : size_t(item.binId()); -// if (m_preload_idx.size() <= bedidx || m_preload_idx[bedidx].empty()) -// return false; - -// std::vector result; -// m_preload_idx[bedidx].query(bgi::intersects(item.boundingBox()), -// std::back_inserter(result)); -// return !result.empty(); -// } }; template<> std::function AutoArranger::get_objfn() @@ -544,21 +525,7 @@ void _arrange( ++it : it = excludes.erase(it); // If there is something on the plate - if (!excludes.empty()) { - arranger.preload(excludes); -// auto binbb = sl::boundingBox(corrected_bin); - -// // Try to put the first item to the center, as the arranger -// // will not do this for us. -// for (Item &itm : shapes) { -// auto ibb = itm.boundingBox(); -// auto d = binbb.center() - ibb.center(); -// itm.translate(d); -// itm.binId(UNARRANGED); - -// if (!arranger.is_colliding(itm)) { itm.markAsFixed(); break; } -// } - } + if (!excludes.empty()) arranger.preload(excludes); std::vector> inp; inp.reserve(shapes.size() + excludes.size()); From 699319cd861737feedae77a1f052320860904c49 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 22 Jul 2019 11:23:42 +0200 Subject: [PATCH 377/627] #2663 - Added handling of gcode lines M401 and M402 for Repetier flavour to GCodeAnalyzer --- src/libslic3r/GCode/Analyzer.cpp | 90 +++++++++++++++++++++++++++++--- src/libslic3r/GCode/Analyzer.hpp | 12 +++++ 2 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 6cf118cc25..a08f83cf54 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -126,6 +126,7 @@ void GCodeAnalyzer::reset() _set_start_position(DEFAULT_START_POSITION); _set_start_extrusion(DEFAULT_START_EXTRUSION); _reset_axes_position(); + _reset_cached_position(); m_moves_map.clear(); m_extruder_offsets.clear(); @@ -262,6 +263,16 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi _processM108orM135(line); break; } + case 401: // Repetier: Store x, y and z position + { + _processM401(line); + break; + } + case 402: // Repetier: Go to stored position + { + _processM402(line); + break; + } } break; @@ -433,12 +444,6 @@ void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line) _set_e_local_positioning_type(Relative); } -void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line) -{ - m_state.cur_cp_color_id++; - _set_cp_color_id(m_state.cur_cp_color_id); -} - void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) { // These M-codes are used by MakerWare and Sailfish to change active tool. @@ -447,7 +452,7 @@ void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) size_t code = ::atoi(&(line.cmd()[1])); if ((code == 108 && m_gcode_flavor == gcfSailfish) - || (code == 135 && m_gcode_flavor == gcfMakerWare)) { + || (code == 135 && m_gcode_flavor == gcfMakerWare)) { std::string cmd = line.raw(); size_t T_pos = cmd.find("T"); @@ -458,6 +463,66 @@ void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) } } +void GCodeAnalyzer::_processM401(const GCodeReader::GCodeLine& line) +{ + if (m_gcode_flavor != gcfRepetier) + return; + + for (unsigned char a = 0; a <= 3; ++a) + { + _set_cached_position(a, _get_axis_position((EAxis)a)); + } + _set_cached_position(4, _get_feedrate()); +} + +void GCodeAnalyzer::_processM402(const GCodeReader::GCodeLine& line) +{ + if (m_gcode_flavor != gcfRepetier) + return; + + // see for reference: + // https://github.com/repetier/Repetier-Firmware/blob/master/src/ArduinoAVR/Repetier/Printer.cpp + // void Printer::GoToMemoryPosition(bool x, bool y, bool z, bool e, float feed) + + bool has_xyz = !(line.has_x() || line.has_y() || line.has_z()); + + float p = FLT_MAX; + for (unsigned char a = X; a <= Z; ++a) + { + if (has_xyz || line.has(a)) + { + p = _get_cached_position(a); + if (p != FLT_MAX) + _set_axis_position((EAxis)a, p); + } + } + + p = _get_cached_position(E); + if (p != FLT_MAX) + _set_axis_position(E, p); + + p = FLT_MAX; + if (!line.has_value(4, p)) + p = _get_cached_position(4); + + if (p != FLT_MAX) + _set_feedrate(p); +} + +void GCodeAnalyzer::_reset_cached_position() +{ + for (unsigned char a = 0; a <= 4; ++a) + { + m_state.cached_position[a] = FLT_MAX; + } +} + +void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line) +{ + m_state.cur_cp_color_id++; + _set_cp_color_id(m_state.cur_cp_color_id); +} + void GCodeAnalyzer::_processT(const std::string& cmd) { if (cmd.length() > 1) @@ -668,6 +733,17 @@ const Vec3d& GCodeAnalyzer::_get_start_position() const return m_state.start_position; } +void GCodeAnalyzer::_set_cached_position(unsigned char axis, float position) +{ + if ((0 <= axis) || (axis <= 4)) + m_state.cached_position[axis] = position; +} + +float GCodeAnalyzer::_get_cached_position(unsigned char axis) const +{ + return ((0 <= axis) || (axis <= 4)) ? m_state.cached_position[axis] : FLT_MAX; +} + void GCodeAnalyzer::_set_start_extrusion(float extrusion) { m_state.start_extrusion = extrusion; diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index ab8bade71b..5bd240e94d 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -96,6 +96,7 @@ private: EPositioningType e_local_positioning_type; Metadata data; Vec3d start_position = Vec3d::Zero(); + float cached_position[5]{ FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; float start_extrusion; float position[Num_Axis]; unsigned int cur_cp_color_id = 0; @@ -170,6 +171,12 @@ private: // Set tool (MakerWare and Sailfish flavor) void _processM108orM135(const GCodeReader::GCodeLine& line); + // Repetier: Store x, y and z position + void _processM401(const GCodeReader::GCodeLine& line); + + // Repetier: Go to stored position + void _processM402(const GCodeReader::GCodeLine& line); + // Set color change void _processM600(const GCodeReader::GCodeLine& line); @@ -232,6 +239,11 @@ private: void _set_start_position(const Vec3d& position); const Vec3d& _get_start_position() const; + void _set_cached_position(unsigned char axis, float position); + float _get_cached_position(unsigned char axis) const; + + void _reset_cached_position(); + void _set_start_extrusion(float extrusion); float _get_start_extrusion() const; float _get_delta_extrusion() const; From a59e782e85d296e953298cb33446600f96885bb8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 22 Jul 2019 11:36:56 +0200 Subject: [PATCH 378/627] Update Arrange tooltip: [Shift+A] --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 7388ec2196..38c0315abf 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3588,7 +3588,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "arrange"; item.icon_filename = "arrange.svg"; - item.tooltip = _utf8(L("Arrange")) + " [A]"; + item.tooltip = _utf8(L("Arrange")) + " [A]\n" + _utf8(L("Arrange selection")) + " [Shift+A]"; item.sprite_id = 3; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); }; From dcedb9e3d3a3a3933116a43afb491f1268d25079 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 22 Jul 2019 11:47:23 +0200 Subject: [PATCH 379/627] Follow-up of 699319cd861737feedae77a1f052320860904c49 -> Fixed build on Mac --- src/libslic3r/GCode/Analyzer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index 5bd240e94d..c9c81d429f 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -96,7 +96,7 @@ private: EPositioningType e_local_positioning_type; Metadata data; Vec3d start_position = Vec3d::Zero(); - float cached_position[5]{ FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; + float cached_position[5]; float start_extrusion; float position[Num_Axis]; unsigned int cur_cp_color_id = 0; From 7dd63b2c0080378feefb8b92fa1abcbd71afaee3 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 22 Jul 2019 13:38:53 +0200 Subject: [PATCH 380/627] Added takesnapshot() call for: - split of the instances, - renaming of the Object/Volume item - changing of the additional settings. --- src/slic3r/GUI/GUI_ObjectList.cpp | 14 +++++++++----- src/slic3r/GUI/GUI_ObjectSettings.cpp | 8 ++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 429cc18ee7..a2ddba376a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -487,13 +487,15 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const { const int obj_idx = m_objects_model->GetObjectIdByItem(item); if (obj_idx < 0) return; + const int volume_id = m_objects_model->GetVolumeIdByItem(item); - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + take_snapshot(wxString::Format(_(L("Rename %s")), volume_id < 0 ? _(L("Object")) : _(L("Sub-object")))); + + if (m_objects_model->GetItemType(item) & itObject) { (*m_objects)[obj_idx]->name = m_objects_model->GetName(item).ToUTF8().data(); return; } - const int volume_id = m_objects_model->GetVolumeIdByItem(item); if (volume_id < 0) return; (*m_objects)[obj_idx]->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data(); } @@ -925,7 +927,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event) if (m_dragged_data.type() == itInstance) { - take_snapshot(_(L("Instances to Separated Objects"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(),_(L("Instances to Separated Objects"))); instances_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.inst_idxs()); m_dragged_data.clear(); return; @@ -943,7 +945,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event) // if (to_volume_id > from_volume_id) to_volume_id--; // #endif // __WXGTK__ - take_snapshot(_(L("Remov Volume(s)"))); + take_snapshot(_(L("Remove Volume(s)"))); auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes; auto delta = to_volume_id < from_volume_id ? -1 : 1; @@ -3055,7 +3057,7 @@ void ObjectList::change_part_type() if (new_type == type || new_type == ModelVolumeType::INVALID) return; - take_snapshot(_(L("Paste from Clipboard"))); + take_snapshot(_(L("Change Part Type"))); const auto item = GetSelection(); volume->set_type(new_type); @@ -3294,6 +3296,8 @@ void ObjectList::split_instances() if (obj_idx == -1) return; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Instances to Separated Objects"))); + if (selection.is_single_full_object()) { instances_to_separated_objects(obj_idx); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 16c64360a2..8728156ac1 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -93,6 +93,7 @@ bool ObjectSettings::update_settings_list() btn->SetToolTip(_(L("Remove parameter"))); btn->Bind(wxEVT_BUTTON, [opt_key, config, this](wxEvent &event) { + wxGetApp().plater()->take_snapshot(wxString::Format(_(L("Delete Option %s")), opt_key)); config->erase(opt_key); wxGetApp().obj_list()->changed_object(); wxTheApp->CallAfter([this]() { @@ -137,6 +138,13 @@ bool ObjectSettings::update_settings_list() if (is_extruders_cat) option.opt.max = wxGetApp().extruders_edited_cnt(); optgroup->append_single_option_line(option); + + optgroup->get_field(opt)->m_on_change = [optgroup](const std::string& opt_id, const boost::any& value) { + // first of all take a snapshot and then change value in configuration + wxGetApp().plater()->take_snapshot(wxString::Format(_(L("Change Option %s")), opt_id)); + optgroup->on_change_OG(opt_id, value); + }; + } optgroup->reload_config(); From 77c3d2fbb3ef2343777dd00bbae4056d4509331c Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Sat, 13 Jul 2019 17:56:43 +0200 Subject: [PATCH 381/627] Use system Eigen3 on linux when found --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 49717b68d8..f60c8bde65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -317,10 +317,10 @@ endif() # Find eigen3 or use bundled version if (NOT SLIC3R_STATIC) - find_package(Eigen3 3) + find_package(Eigen3 3.3) endif () -if (NOT Eigen3_FOUND) - set(Eigen3_FOUND 1) +if (NOT EIGEN3_FOUND) + set(EIGEN3_FOUND 1) set(EIGEN3_INCLUDE_DIR ${LIBDIR}/eigen/) endif () include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR}) From ab677bad41bedf74296c9cc02224c64aea03af07 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 22 Jul 2019 16:00:52 +0200 Subject: [PATCH 382/627] Mac OS: Fix gettext lookup in deps build --- deps/deps-macos.cmake | 2 +- doc/How to build - Mac OS.md | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake index c7c6819e33..d22e4a2e2c 100644 --- a/deps/deps-macos.cmake +++ b/deps/deps-macos.cmake @@ -111,6 +111,6 @@ ExternalProject_Add(dep_wxwidgets --with-expat=builtin --disable-debug --disable-debug_flag - BUILD_COMMAND make "-j${NPROC}" && make -C locale allmo + BUILD_COMMAND make "-j${NPROC}" && PATH=/usr/local/opt/gettext/bin/:$ENV{PATH} make -C locale allmo INSTALL_COMMAND make install ) diff --git a/doc/How to build - Mac OS.md b/doc/How to build - Mac OS.md index b4196909d3..082c560b7a 100644 --- a/doc/How to build - Mac OS.md +++ b/doc/How to build - Mac OS.md @@ -1,7 +1,15 @@ # Building PrusaSlicer on Mac OS -To build PrusaSlicer on Mac OS, you will need to install XCode, [CMake](https://cmake.org/) (available on Brew) and possibly git. +To build PrusaSlicer on Mac OS, you will need the following software: + +- XCode +- CMake +- git +- gettext + +XCode is available through Apple's App Store, the other three tools are available on +[brew](https://brew.sh/) (use `brew install cmake git gettext` to install them). ### Dependencies From a5c64e8477d15cf9a9d923387c86b5a81643d920 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 22 Jul 2019 16:52:47 +0200 Subject: [PATCH 383/627] Refactoring of the Undo / Redo stack interface: The Snapshot specific data, which is fully provided from the outside of the Undo / Redo stack is now stored as an StackData structure. The StackData structure may be extended with small size data, like the cursor position in the side bar. --- src/slic3r/GUI/Plater.cpp | 20 +++++++++++--------- src/slic3r/Utils/UndoRedo.cpp | 22 +++++++++++----------- src/slic3r/Utils/UndoRedo.hpp | 24 +++++++++++++++++------- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a9972d1922..97606c0a09 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3725,9 +3725,10 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) if (this->m_prevent_snapshots > 0) return; assert(this->m_prevent_snapshots >= 0); - unsigned int flags = 0; + UndoRedo::SnapshotData snapshot_data; + snapshot_data.printer_technology = this->printer_technology; if (this->view3D->is_layers_editing_enabled()) - flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE; + snapshot_data.flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE; //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config. // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. if (this->printer_technology == ptFFF) { @@ -3735,7 +3736,7 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y")); model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); } - this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, flags); + this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data); this->undo_redo_stack.release_least_recently_used(); // Save the last active preset name of a particular printer technology. ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); @@ -3769,11 +3770,11 @@ void Plater::priv::undo_redo_to(size_t time_to_load) void Plater::priv::undo_redo_to(std::vector::const_iterator it_snapshot) { bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active(); - PrinterTechnology new_printer_technology = it_snapshot->printer_technology; + PrinterTechnology new_printer_technology = it_snapshot->snapshot_data.printer_technology; bool printer_technology_changed = this->printer_technology != new_printer_technology; if (printer_technology_changed) { // Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type. - std::string s_pt = (it_snapshot->printer_technology == ptFFF) ? "FFF" : "SLA"; + std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA"; if (! wxGetApp().check_unsaved_changes(from_u8((boost::format(_utf8( L("%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."))) % s_pt).str()))) // Don't switch the profiles. @@ -3789,17 +3790,18 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); } // Flags made of Snapshot::Flags enum values. - unsigned int new_flags = it_snapshot->flags; - unsigned int top_snapshot_flags = 0; + unsigned int new_flags = it_snapshot->snapshot_data.flags; + UndoRedo::SnapshotData top_snapshot_data; + top_snapshot_data.printer_technology = this->printer_technology; if (this->view3D->is_layers_editing_enabled()) - top_snapshot_flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE; + top_snapshot_data.flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE; bool new_variable_layer_editing_active = (new_flags & UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE) != 0; // Disable layer editing before the Undo / Redo jump. if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); // Do the jump in time. if (it_snapshot->timestamp < this->undo_redo_stack.active_snapshot_time() ? - this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, top_snapshot_flags, it_snapshot->timestamp) : + this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), top_snapshot_data, it_snapshot->timestamp) : this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) { if (printer_technology_changed) { // Switch to the other printer technology. Switch to the last printer active for that particular technology. diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index a8f9cc134a..6eaede6d75 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -496,12 +496,12 @@ public: } // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. - void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags); + void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data); void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos); bool has_undo_snapshot() const; bool has_redo_snapshot() const; - bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t jump_to_time); + bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, const SnapshotData &snapshot_data, size_t jump_to_time); bool redo(Slic3r::Model &model, Slic3r::GUI::GLGizmosManager &gizmos, size_t jump_to_time); void release_least_recently_used(); @@ -786,7 +786,7 @@ template void StackImpl::load_mutable_object(const Slic3r::ObjectID } // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. -void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags) +void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data) { // Release old snapshot data. assert(m_active_snapshot_time <= m_current_time); @@ -806,11 +806,11 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo this->save_mutable_object(m_selection); this->save_mutable_object(gizmos); // Save the snapshot info. - m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id, printer_technology, flags); + m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id, snapshot_data); m_active_snapshot_time = m_current_time; // Save snapshot info of the last "current" aka "top most" state, that is only being serialized // if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet. - m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0, printer_technology, flags); + m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0, snapshot_data); // Release empty objects from the history. this->collect_garbage(); assert(this->valid()); @@ -856,7 +856,7 @@ bool StackImpl::has_redo_snapshot() const return ++ it != m_snapshots.end(); } -bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t time_to_load) +bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, const SnapshotData &snapshot_data, size_t time_to_load) { assert(this->valid()); if (time_to_load == SIZE_MAX) { @@ -870,7 +870,7 @@ bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selecti bool new_snapshot_taken = false; if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) { // The current state is temporary. The current state needs to be captured to be redoable. - this->take_snapshot(topmost_snapshot_name, model, selection, gizmos, printer_technology, flags); + this->take_snapshot(topmost_snapshot_name, model, selection, gizmos, snapshot_data); // The line above entered another topmost_snapshot_name. assert(m_snapshots.back().is_topmost()); assert(! m_snapshots.back().is_topmost_captured()); @@ -1019,12 +1019,12 @@ void Stack::set_memory_limit(size_t memsize) { pimpl->set_memory_limit(memsize); size_t Stack::get_memory_limit() const { return pimpl->get_memory_limit(); } size_t Stack::memsize() const { return pimpl->memsize(); } void Stack::release_least_recently_used() { pimpl->release_least_recently_used(); } -void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags) - { pimpl->take_snapshot(snapshot_name, model, selection, gizmos, printer_technology, flags); } +void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data) + { pimpl->take_snapshot(snapshot_name, model, selection, gizmos, snapshot_data); } bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); } bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); } -bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t time_to_load) - { return pimpl->undo(model, selection, gizmos, printer_technology, flags, time_to_load); } +bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data, size_t time_to_load) + { return pimpl->undo(model, selection, gizmos, snapshot_data, time_to_load); } bool Stack::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->redo(model, gizmos, time_to_load); } const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); } diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 916e44aa24..209b5cf298 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -21,11 +21,23 @@ namespace GUI { namespace UndoRedo { +// Data structure to be stored with each snapshot. +// Storing short data (bit masks, ints) with each snapshot instead of being serialized into the Undo / Redo stack +// is likely cheaper in term of both the runtime and memory allocation. +// Also the SnapshotData is available without having to deserialize the snapshot from the Undo / Redo stack, +// which may be handy sometimes. +struct SnapshotData +{ + PrinterTechnology printer_technology = ptUnknown; + // Bitmap of Flags (see the Flags enum). + unsigned int flags = 0; +}; + struct Snapshot { Snapshot(size_t timestamp) : timestamp(timestamp) {} - Snapshot(const std::string &name, size_t timestamp, size_t model_id, Slic3r::PrinterTechnology printer_technology, unsigned int flags) : - name(name), timestamp(timestamp), model_id(model_id), printer_technology(printer_technology), flags(flags) {} + Snapshot(const std::string &name, size_t timestamp, size_t model_id, const SnapshotData &snapshot_data) : + name(name), timestamp(timestamp), model_id(model_id), snapshot_data(snapshot_data) {} // Bitmask of various binary flags to be stored with the snapshot. enum Flags { @@ -35,9 +47,7 @@ struct Snapshot std::string name; size_t timestamp; size_t model_id; - PrinterTechnology printer_technology; - // Bitmap of Flags (see the Flags enum). - unsigned int flags; + SnapshotData snapshot_data; bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; } bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; } @@ -77,7 +87,7 @@ public: void release_least_recently_used(); // Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time. - void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags); + void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data); // To be queried to enable / disable the Undo / Redo buttons at the UI. bool has_undo_snapshot() const; @@ -85,7 +95,7 @@ public: // Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated. // Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible. - bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t time_to_load = SIZE_MAX); + bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, const SnapshotData &snapshot_data, size_t time_to_load = SIZE_MAX); // Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated. bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX); From bed21b1e2d3b1bcebbc6c03334940ed66068aa2d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 22 Jul 2019 17:00:50 +0200 Subject: [PATCH 384/627] Undo / Redo refactoring: Moved the snapshot flags definition to SnapshotData --- src/slic3r/GUI/Plater.cpp | 6 +++--- src/slic3r/Utils/UndoRedo.hpp | 11 ++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 97606c0a09..9e818adfa1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3728,7 +3728,7 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) UndoRedo::SnapshotData snapshot_data; snapshot_data.printer_technology = this->printer_technology; if (this->view3D->is_layers_editing_enabled()) - snapshot_data.flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE; + snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config. // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. if (this->printer_technology == ptFFF) { @@ -3794,8 +3794,8 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator UndoRedo::SnapshotData top_snapshot_data; top_snapshot_data.printer_technology = this->printer_technology; if (this->view3D->is_layers_editing_enabled()) - top_snapshot_data.flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE; - bool new_variable_layer_editing_active = (new_flags & UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE) != 0; + top_snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; + bool new_variable_layer_editing_active = (new_flags & UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE) != 0; // Disable layer editing before the Undo / Redo jump. if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 209b5cf298..a5ebd62e8b 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -31,6 +31,12 @@ struct SnapshotData PrinterTechnology printer_technology = ptUnknown; // Bitmap of Flags (see the Flags enum). unsigned int flags = 0; + + + // Bitmask of various binary flags to be stored with the snapshot. + enum Flags { + VARIABLE_LAYER_EDITING_ACTIVE = 1, + }; }; struct Snapshot @@ -38,11 +44,6 @@ struct Snapshot Snapshot(size_t timestamp) : timestamp(timestamp) {} Snapshot(const std::string &name, size_t timestamp, size_t model_id, const SnapshotData &snapshot_data) : name(name), timestamp(timestamp), model_id(model_id), snapshot_data(snapshot_data) {} - - // Bitmask of various binary flags to be stored with the snapshot. - enum Flags { - VARIABLE_LAYER_EDITING_ACTIVE = 1, - }; std::string name; size_t timestamp; From b34252bf0fe577c2ee25be885ee6a3d48e9cff51 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 22 Jul 2019 17:26:06 +0200 Subject: [PATCH 385/627] Fixed compilation on clang. --- src/slic3r/Utils/UndoRedo.cpp | 4 ++++ src/slic3r/Utils/UndoRedo.hpp | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 6eaede6d75..72e7431bd4 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -35,6 +35,10 @@ namespace Slic3r { namespace UndoRedo { +SnapshotData::SnapshotData() : printer_technology(ptUnknown), flags(0) +{ +} + static std::string topmost_snapshot_name = "@@@ Topmost @@@"; bool Snapshot::is_topmost() const diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index a5ebd62e8b..c178025601 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -28,10 +28,12 @@ namespace UndoRedo { // which may be handy sometimes. struct SnapshotData { - PrinterTechnology printer_technology = ptUnknown; - // Bitmap of Flags (see the Flags enum). - unsigned int flags = 0; + // Constructor is defined in .cpp due to the forward declaration of enum PrinterTechnology. + SnapshotData(); + PrinterTechnology printer_technology; + // Bitmap of Flags (see the Flags enum). + unsigned int flags; // Bitmask of various binary flags to be stored with the snapshot. enum Flags { From 2c6e4b4e5bab8ff9f41a41e0c4ef7a4bcfdd4a68 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 23 Jul 2019 09:40:07 +0200 Subject: [PATCH 386/627] Fixed a missing include in UndoRedo.cpp --- src/slic3r/Utils/UndoRedo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 72e7431bd4..eba839ffb1 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -17,6 +17,7 @@ #define CEREAL_FUTURE_EXPERIMENTAL #include +#include #include #include From 3fe355509ce2dd4d9e99cbc6b977bee6ceba4699 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 23 Jul 2019 12:57:58 +0200 Subject: [PATCH 387/627] Fixed undo/redo snapshot when opening a project using the recent files list --- src/slic3r/GUI/Plater.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 9e818adfa1..b98faaa3cf 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3904,8 +3904,6 @@ void Plater::load_project() // Ask user for a project file name. wxString input_file; wxGetApp().load_project(this, input_file); - // Take the Undo / Redo snapshot. - Plater::TakeSnapshot snapshot(this, _(L("Load Project")) + ": " + wxString::FromUTF8(into_path(input_file).stem().string().c_str())); // And finally load the new project. load_project(input_file); } @@ -3915,6 +3913,9 @@ void Plater::load_project(const wxString& filename) if (filename.empty()) return; + // Take the Undo / Redo snapshot. + Plater::TakeSnapshot snapshot(this, _(L("Load Project")) + ": " + wxString::FromUTF8(into_path(filename).stem().string().c_str())); + p->reset(); p->set_project_filename(filename); From 506be9035b9467127de2dec88579209cb250e403 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 23 Jul 2019 13:58:07 +0200 Subject: [PATCH 388/627] Wipe tower now supports filaments with diameters different from 1.75 mm --- src/libslic3r/GCode/WipeTower.cpp | 4 ++-- src/libslic3r/GCode/WipeTower.hpp | 16 +++++++++++----- src/libslic3r/Print.cpp | 10 +++++++++- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 354ec6d9e1..55a6c4437e 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -157,7 +157,7 @@ public: change_analyzer_mm3_per_mm(len, e); // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. - float width = float(double(e) * /*Filament_Area*/2.40528 / (len * m_layer_height)); + float width = float(double(e) * m_filpar[0].filament_area / (len * m_layer_height)); // Correct for the roundings of a squished extrusion. width += m_layer_height * float(1. - M_PI / 4.); if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos) @@ -822,7 +822,7 @@ void WipeTower::toolchange_Unload( while (i < m_filpar[m_current_tool].ramming_speed.size()) { const float x = volume_to_length(m_filpar[m_current_tool].ramming_speed[i] * 0.25f, line_width, m_layer_height); - const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / Filament_Area; // transform volume per sec to E move; + const float e = m_filpar[m_current_tool].ramming_speed[i] * 0.25f / filament_area(); // transform volume per sec to E move; const float dist = std::min(x - e_done, remaining); // distance to travel for either the next 0.25s, or to the next turnaround const float actual_time = dist/x * 0.25; writer.ram(writer.x(), writer.x() + (m_left_to_right ? 1.f : -1.f) * dist, 0, 0, e * (dist / x), dist / (actual_time / 60.)); diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index badb3e8b24..6dfad1b8c5 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -111,7 +111,8 @@ public: // Set the extruder properties. void set_extruder(size_t idx, std::string material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start, float unloading_speed, float unloading_speed_start, float delay, int cooling_moves, - float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float max_volumetric_speed, float nozzle_diameter) + float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float max_volumetric_speed, + float nozzle_diameter, float filament_diameter) { //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector m_filpar.push_back(FilamentParameters()); @@ -133,10 +134,12 @@ public: m_filpar[idx].cooling_final_speed = cooling_final_speed; } - if (max_volumetric_speed != 0.f) - m_filpar[idx].max_e_speed = (max_volumetric_speed / Filament_Area); + m_filpar[idx].filament_area = (M_PI/4.f) * pow(filament_diameter, 2); // all extruders are assumed to have the same filament diameter at this point m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM + if (max_volumetric_speed != 0.f) + m_filpar[idx].max_e_speed = (max_volumetric_speed / filament_area()); + m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter if (m_semm) { @@ -248,6 +251,7 @@ public: float max_e_speed = std::numeric_limits::max(); std::vector ramming_speed; float nozzle_diameter; + float filament_area; }; private: @@ -261,9 +265,11 @@ private: const bool m_peters_wipe_tower = false; // sparse wipe tower inspired by Peter's post processor - not finished yet - const float Filament_Area = float(M_PI * 1.75f * 1.75f / 4.f); // filament area in mm^2 const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust const float WT_EPSILON = 1e-3f; + const float filament_area() const { + return m_filpar[0].filament_area; // all extruders are assumed to have the same filament diameter at this point + } bool m_semm = true; // Are we using a single extruder multimaterial printer? @@ -315,7 +321,7 @@ private: { if ( layer_height < 0 ) return m_extrusion_flow; - return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / Filament_Area; + return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / filament_area(); } // Calculates length of extrusion line to extrude given volume diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index a9b8a827f1..5702f49e33 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1092,6 +1092,13 @@ std::string Print::validate() const } if (this->has_wipe_tower() && ! m_objects.empty()) { + // make sure all extruders use same diameter filament and have the same nozzle diameter + for (const auto& extruder_idx : extruders()) { + if (m_config.nozzle_diameter.get_at(extruder_idx) != m_config.nozzle_diameter.get_at(extruders().front()) + || m_config.filament_diameter.get_at(extruder_idx) != m_config.filament_diameter.get_at(extruders().front())) + return L("The wipe tower is only supported if all extruders have the same nozzle diameter and use filaments of the same diameter."); + } + if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin) return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors."); if (! m_config.use_relative_e_distances) @@ -1701,7 +1708,8 @@ void Print::_make_wipe_tower() (float)m_config.filament_cooling_final_speed.get_at(i), m_config.filament_ramming_parameters.get_at(i), m_config.filament_max_volumetric_speed.get_at(i), - m_config.nozzle_diameter.get_at(i)); + m_config.nozzle_diameter.get_at(i), + m_config.filament_diameter.get_at(i)); m_wipe_tower_data.priming = Slic3r::make_unique>( wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); From 91a5d70a627aec290860971c338d638e60ce595e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 23 Jul 2019 11:22:54 +0200 Subject: [PATCH 389/627] Fixed a few warnings in headers (meaning they were reported once for each include) Fixed an identification of CXX compiler in cmake so that 'AppleClang' is recognized --- CMakeLists.txt | 4 ++-- src/libslic3r/Utils.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 4 ++-- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 6 +++--- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 +- src/slic3r/GUI/wxExtensions.hpp | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f60c8bde65..6f2332ce0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,7 +162,7 @@ if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUXX) endif () endif() -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") add_compile_options(-Wall) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder" ) @@ -170,7 +170,7 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STRE add_compile_options(-Werror=return-type) #removes LOTS of extraneous Eigen warnings (GCC only supports it since 6.1) - #if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6.1) + #if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 6.1) # add_compile_options(-Wno-ignored-attributes) # Tamas: Eigen include dirs are marked as SYSTEM #endif() diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 09e24a475a..8a4f1424b2 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -172,7 +172,7 @@ extern std::string xml_escape(std::string text); #if defined __GNUC__ && __GNUC__ < 5 && !defined __clang__ // Older GCCs don't have std::is_trivially_copyable // cf. https://gcc.gnu.org/onlinedocs/gcc-4.9.4/libstdc++/manual/manual/status.html#status.iso.2011 -#warning "GCC version < 5, faking std::is_trivially_copyable" +// #warning "GCC version < 5, faking std::is_trivially_copyable" template struct IsTriviallyCopyable { static constexpr bool value = true; }; #else template struct IsTriviallyCopyable : public std::is_trivially_copyable {}; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 4b975e87e9..a650746468 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -166,7 +166,7 @@ void GLGizmoBase::set_highlight_color(const float* color) void GLGizmoBase::enable_grabber(unsigned int id) { - if ((0 <= id) && (id < (unsigned int)m_grabbers.size())) + if (id < m_grabbers.size()) m_grabbers[id].enabled = true; on_enable_grabber(id); @@ -174,7 +174,7 @@ void GLGizmoBase::enable_grabber(unsigned int id) void GLGizmoBase::disable_grabber(unsigned int id) { - if ((0 <= id) && (id < (unsigned int)m_grabbers.size())) + if (id < m_grabbers.size()) m_grabbers[id].enabled = false; on_disable_grabber(id); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 7846edb22e..8733c9a118 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -93,19 +93,19 @@ protected: } virtual void on_set_hover_id() { - for (unsigned int i = 0; i < 3; ++i) + for (int i = 0; i < 3; ++i) { m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); } } virtual void on_enable_grabber(unsigned int id) { - if ((0 <= id) && (id < 3)) + if (id < 3) m_gizmos[id].enable_grabber(0); } virtual void on_disable_grabber(unsigned int id) { - if ((0 <= id) && (id < 3)) + if (id < 3) m_gizmos[id].disable_grabber(0); } virtual void on_start_dragging(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 2aaf55424a..d96b806e5b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -223,7 +223,7 @@ void GLGizmosManager::update_data() enable_grabber(Rotate, 1, !is_wipe_tower); bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); - for (int i = 0; i < 6; ++i) + for (unsigned int i = 0; i < 6; ++i) { enable_grabber(Scale, i, enable_scale_xyz); } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 9a6460dcb1..a704f79d7a 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -323,9 +323,9 @@ public: } bool SwapChildrens(int frst_id, int scnd_id) { - if (GetChildCount() < 2 || - frst_id < 0 || frst_id >= GetChildCount() || - scnd_id < 0 || scnd_id >= GetChildCount()) + if (GetChildCount() < 2 || + frst_id < 0 || (size_t)frst_id >= GetChildCount() || + scnd_id < 0 || (size_t)scnd_id >= GetChildCount()) return false; ObjectDataViewModelNode new_scnd = *GetNthChild(frst_id); From bb604242aaa119b190da35465f390e954493c814 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 23 Jul 2019 14:04:10 +0200 Subject: [PATCH 390/627] Updated the Prusa3D profiles. --- resources/profiles/PrusaResearch.idx | 9 + resources/profiles/PrusaResearch.ini | 334 +++++++++++++++++++-------- 2 files changed, 242 insertions(+), 101 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index de5e3d1060..81b1ca15fd 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,7 @@ min_slic3r_version = 1.42.0-alpha6 +0.8.3 FW version and SL1 materials update +0.8.2 FFF and SL1 settings update +0.8.1 Output settings and SLA materials update 0.8.0 Updated for the PrusaSlicer 2.0.0 final release 0.8.0-rc2 Updated firmware versions for MK2.5/S and MK3/S 0.8.0-rc1 Updated SLA profiles @@ -18,6 +21,8 @@ min_slic3r_version = 1.42.0-alpha 0.4.0-alpha3 Update of SLA profiles 0.4.0-alpha2 First SLA profiles min_slic3r_version = 1.41.3-alpha +0.4.8 MK2.5/3/S FW update +0.4.7 MK2/S/MMU FW update 0.4.6 Updated firmware versions for MK2.5/S and MK3/S 0.4.5 Enabled remaining time support for MK2/S/MMU1 0.4.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt @@ -26,6 +31,8 @@ min_slic3r_version = 1.41.3-alpha 0.4.1 New MK2.5S and MK3S FW versions 0.4.0 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt min_slic3r_version = 1.41.1 +0.3.8 MK2.5/3/S FW update +0.3.7 MK2/S/MMU FW update 0.3.6 Updated firmware versions for MK2.5 and MK3 0.3.5 New MK2.5 and MK3 FW versions 0.3.4 Changelog: https://github.com/prusa3d/Slic3r-settings/blob/master/live/PrusaResearch/changelog.txt @@ -60,6 +67,8 @@ min_slic3r_version = 1.41.0-alpha 0.2.0-alpha1 added initial profiles for the i3 MK3 Multi Material Upgrade 2.0 0.2.0-alpha moved machine limits from the start G-code to the new print profile parameters min_slic3r_version = 1.40.0 +0.1.16 MK2.5/3/S FW update +0.1.15 MK2/S/MMU FW update 0.1.14 Updated firmware versions for MK2.5 and MK3 0.1.13 New MK2.5 and MK3 FW versions 0.1.12 New MK2.5 and MK3 FW versions diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 9db3a7a14e..f17066636d 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -4,8 +4,8 @@ # Vendor name will be shown by the Config Wizard. name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. -# This means, the server may force the Slic3r configuration to be downgraded. -config_version = 0.8.0 +# This means, the server may force the PrusaSlicer configuration to be downgraded. +config_version = 0.8.3 # Where to get the updates from? config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ changelog_url = http://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -105,8 +105,8 @@ external_fill_pattern = rectilinear external_perimeters_first = 0 external_perimeter_extrusion_width = 0.45 extra_perimeters = 0 -extruder_clearance_height = 20 -extruder_clearance_radius = 20 +extruder_clearance_height = 25 +extruder_clearance_radius = 45 extrusion_width = 0.45 fill_angle = 45 fill_density = 20% @@ -133,7 +133,7 @@ notes = overhangs = 0 only_retract_when_crossing_perimeters = 0 ooze_prevention = 0 -output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}.gcode +output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode perimeters = 2 perimeter_extruder = 1 perimeter_extrusion_width = 0.45 @@ -213,6 +213,7 @@ support_material_interface_layers = 0 support_material_interface_spacing = 0.15 support_material_spacing = 1 support_material_xy_spacing = 150% +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode [print:*0.25nozzleMK3*] external_perimeter_extrusion_width = 0.25 @@ -245,6 +246,7 @@ max_print_speed = 80 perimeters = 3 fill_pattern = grid fill_density = 20% +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode # Print parameters common to a 0.6mm diameter nozzle. [print:*0.6nozzle*] @@ -256,6 +258,7 @@ perimeter_extrusion_width = 0.65 solid_infill_extrusion_width = 0.65 top_infill_extrusion_width = 0.6 support_material_extrusion_width = 0.55 +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode [print:*0.6nozzleMK3*] external_perimeter_extrusion_width = 0.65 @@ -268,6 +271,7 @@ top_infill_extrusion_width = 0.6 support_material_extrusion_width = 0.55 bridge_flow_ratio = 0.95 bridge_speed = 25 +output_filename_format = {input_filename_base}_{nozzle_diameter[0]}n_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode [print:*soluble_support*] overhangs = 1 @@ -987,7 +991,7 @@ first_layer_temperature = 215 max_fan_speed = 100 min_fan_speed = 100 temperature = 210 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}15{else}30{endif} ; Filament gcode" +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}18{else}30{endif} ; Filament gcode" [filament:*PET*] inherits = *common* @@ -1003,7 +1007,7 @@ first_layer_bed_temperature = 85 first_layer_temperature = 230 max_fan_speed = 50 min_fan_speed = 30 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}22{else}45{endif} ; Filament gcode" +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}24{else}45{endif} ; Filament gcode" temperature = 240 [filament:*PET06*] @@ -1028,7 +1032,7 @@ first_layer_temperature = 255 max_fan_speed = 30 min_fan_speed = 20 temperature = 255 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}15{else}30{endif} ; Filament gcode" +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}18{else}30{endif} ; Filament gcode" [filament:*FLEX*] inherits = *common* @@ -1164,11 +1168,13 @@ temperature = 260 inherits = *PET* filament_cost = 56.9 filament_density = 1.26 +filament_type = EDGE filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" [filament:E3D PC-ABS] inherits = *ABS* filament_cost = 0 +filament_type = PC filament_density = 1.05 first_layer_temperature = 270 temperature = 270 @@ -1187,12 +1193,14 @@ filament_density = 1.04 fan_always_on = 1 first_layer_temperature = 265 temperature = 265 +filament_type = ASA [filament:Fillamentum CPE HG100 HM100] inherits = *PET* filament_cost = 54.1 filament_density = 1.25 filament_notes = "CPE HG100 , CPE HM100" +filament_type = CPE first_layer_bed_temperature = 90 first_layer_temperature = 275 max_fan_speed = 50 @@ -1234,6 +1242,7 @@ filament_notes = "List of materials tested with standard PLA print settings:\n\n inherits = *ABS* filament_cost = 77.3 filament_density = 1.20 +filament_type = PC bed_temperature = 115 filament_colour = #3A80CA first_layer_bed_temperature = 100 @@ -1312,6 +1321,7 @@ first_layer_temperature = 240 temperature = 250 filament_cost = 24.99 filament_density = 1.27 +filament_type = PETG compatible_printers_condition = nozzle_diameter[0]!=0.6 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) [filament:Prusa PET 0.6 nozzle] @@ -1326,6 +1336,7 @@ first_layer_temperature = 240 temperature = 250 filament_cost = 24.99 filament_density = 1.27 +filament_type = PETG [filament:*PET MMU2*] inherits = Prusa PET @@ -1352,12 +1363,13 @@ inherits = *PET MMU2* [filament:Prusament PETG MMU2] inherits = *PET MMU2* +filament_type = PETG [filament:Prusa PLA] inherits = *PLA* filament_cost = 25.4 filament_density = 1.24 -filament_notes = "List of materials tested with standard PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +filament_notes = "List of materials tested with standard PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFiberlogy PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nAmazonBasics PLA" [filament:Prusament PLA] inherits = *PLA* @@ -1409,7 +1421,7 @@ fan_below_layer_time = 20 filament_colour = #DEE0E6 filament_max_volumetric_speed = 10 filament_soluble = 0 -filament_type = PET +filament_type = NYLON first_layer_bed_temperature = 60 first_layer_temperature = 240 max_fan_speed = 5 @@ -1524,7 +1536,7 @@ fan_below_layer_time = 100 filament_colour = #DEE0E6 filament_max_volumetric_speed = 5 filament_notes = "List of materials tested with standard PLA print settings:\n\nEsun PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nEUMAKERS PLA" -filament_type = PLA +filament_type = PP first_layer_bed_temperature = 100 first_layer_temperature = 220 max_fan_speed = 100 @@ -1575,6 +1587,9 @@ layer_height = 0.05 [sla_print:0.1 Fast] inherits = *common* layer_height = 0.1 +support_head_front_diameter = 0.5 +support_head_penetration = 0.5 +support_pillar_diameter = 1.3 ########### Materials 0.025 @@ -1605,82 +1620,117 @@ initial_layer_height = 0.035 inherits = *common 0.05* compatible_prints_condition = layer_height == 0.1 exposure_time = 20 -initial_exposure_time = 90 +initial_exposure_time = 45 initial_layer_height = 0.1 ########### Materials 0.025 -[sla_material:Bluecast Phrozen Wax 0.025] +[sla_material:BlueCast Phrozen Wax 0.025] inherits = *common 0.025* -exposure_time = 8 +exposure_time = 15 +initial_exposure_time = 50 + +[sla_material:BlueCast EcoGray 0.025] +inherits = *common 0.025* +exposure_time = 6 +initial_exposure_time = 40 + +[sla_material:BlueCast Keramaster Dental 0.025] +inherits = *common 0.025* +exposure_time = 6 initial_exposure_time = 45 +[sla_material:BlueCast X10 0.025] +inherits = *common 0.025* +exposure_time = 4 +initial_exposure_time = 100 + [sla_material:Prusa Orange Tough 0.025] inherits = *common 0.025* exposure_time = 6 initial_exposure_time = 30 +[sla_material:Prusa Grey Tough 0.025] +inherits = *common 0.025* +exposure_time = 7 +initial_exposure_time = 30 + +[sla_material:Prusa Azure Blue Tough 0.025] +inherits = *common 0.025* +exposure_time = 7 +initial_exposure_time = 30 + +[sla_material:Prusa Maroon Tough 0.025] +inherits = *common 0.025* +exposure_time = 6 +initial_exposure_time = 30 + +[sla_material:Prusa Beige Tough 0.025] +inherits = *common 0.025* +exposure_time = 6 +initial_exposure_time = 30 + +[sla_material:Prusa Pink Tough 0.025] +inherits = *common 0.025* +exposure_time = 7 +initial_exposure_time = 30 + +[sla_material:Prusa White Tough 0.025] +inherits = *common 0.025* +exposure_time = 6.5 +initial_exposure_time = 30 + +[sla_material:Prusa Transparent Tough 0.025] +inherits = *common 0.025* +exposure_time = 6 +initial_exposure_time = 15 + +[sla_material:Prusa Green Casting 0.025] +inherits = *common 0.025* +exposure_time = 12 +initial_exposure_time = 35 + +## [sla_material:Prusa Transparent Green Tough 0.025] +## inherits = *common 0.025* +## exposure_time = 5 +## initial_exposure_time = 30 + ########### Materials 0.05 -[sla_material:3DM-HTR140 (high temperature) 0.05] -inherits = *common 0.05* -exposure_time = 12 -initial_exposure_time = 45 - -[sla_material:Bluecast Ecogray 0.05] -inherits = *common 0.05* -exposure_time = 8 -initial_exposure_time = 45 - -[sla_material:Bluecast Keramaster 0.05] -inherits = *common 0.05* -exposure_time = 8 -initial_exposure_time = 45 - -[sla_material:Bluecast Keramaster Dental 0.05] +[sla_material:BlueCast EcoGray 0.05] inherits = *common 0.05* exposure_time = 7 +initial_exposure_time = 35 + +[sla_material:BlueCast Keramaster 0.05] +inherits = *common 0.05* +exposure_time = 8 initial_exposure_time = 45 -[sla_material:Bluecast LCD-DLP Original 0.05] +[sla_material:BlueCast Keramaster Dental 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 50 + +[sla_material:BlueCast LCD-DLP Original 0.05] inherits = *common 0.05* exposure_time = 10 initial_exposure_time = 60 -[sla_material:Bluecast Phrozen Wax 0.05] +[sla_material:BlueCast Phrozen Wax 0.05] inherits = *common 0.05* -exposure_time = 10 -initial_exposure_time = 55 +exposure_time = 16 +initial_exposure_time = 50 -[sla_material:Bluecast S+ 0.05] +[sla_material:BlueCast S+ 0.05] inherits = *common 0.05* exposure_time = 9 initial_exposure_time = 45 -[sla_material:Bluecast X2 0.05] -inherits = *common 0.05* -exposure_time = 10 -initial_exposure_time = 60 - -[sla_material:Prusa Skin Tough 0.05] +[sla_material:BlueCast X10 0.05] inherits = *common 0.05* exposure_time = 6 -initial_exposure_time = 30 - -[sla_material:Prusa Orange Tough 0.05] -inherits = *common 0.05* -exposure_time = 7.5 -initial_exposure_time = 30 - -[sla_material:Prusa Grey Tough 0.05] -inherits = *common 0.05* -exposure_time = 8.5 -initial_exposure_time = 30 - -[sla_material:Prusa Black Tough 0.05] -inherits = *common 0.05* -exposure_time = 6 -initial_exposure_time = 30 +initial_exposure_time = 100 [sla_material:Monocure 3D Black Rapid Resin 0.05] inherits = *common 0.05* @@ -1697,20 +1747,30 @@ inherits = *common 0.05* exposure_time = 8 initial_exposure_time = 40 -[sla_material:Monocure 3D Gray Rapid Resin 0.05] +[sla_material:Monocure 3D Grey Rapid Resin 0.05] inherits = *common 0.05* -exposure_time = 7 -initial_exposure_time = 40 +exposure_time = 10 +initial_exposure_time = 30 [sla_material:Monocure 3D White Rapid Resin 0.05] inherits = *common 0.05* exposure_time = 7 initial_exposure_time = 40 +[sla_material:3DM-HTR140 (high temperature) 0.05] +inherits = *common 0.05* +exposure_time = 12 +initial_exposure_time = 45 + [sla_material:3DM-ABS 0.05] inherits = *common 0.05* -exposure_time = 9 -initial_exposure_time = 35 +exposure_time = 13 +initial_exposure_time = 25 + +[sla_material:3DM-BLACK 0.05] +inherits = *common 0.05* +exposure_time = 20 +initial_exposure_time = 40 [sla_material:3DM-DENT 0.05] inherits = *common 0.05* @@ -1737,7 +1797,39 @@ inherits = *common 0.05* exposure_time = 9 initial_exposure_time = 40 -## [sla_material:Prusa Skin Super Low Odor 0.05] +[sla_material:Harz Labs Model Resin Cherry 0.05] +inherits = *common 0.05* +exposure_time = 8 +initial_exposure_time = 45 + +[sla_material:Photocentric Hard Grey 0.05] +inherits = *common 0.05* +exposure_time = 15 +initial_exposure_time = 30 + +## Prusa + +[sla_material:Prusa Beige Tough 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 30 + +[sla_material:Prusa Orange Tough 0.05] +inherits = *common 0.05* +exposure_time = 7.5 +initial_exposure_time = 30 + +[sla_material:Prusa Grey Tough 0.05] +inherits = *common 0.05* +exposure_time = 8.5 +initial_exposure_time = 30 + +[sla_material:Prusa Black Tough 0.05] +inherits = *common 0.05* +exposure_time = 6 +initial_exposure_time = 30 + +## [sla_material:Prusa Beige Super Low Odor 0.05] ## inherits = *common 0.05* ## exposure_time = 7.5 ## initial_exposure_time = 30 @@ -1752,11 +1844,6 @@ initial_exposure_time = 40 ## exposure_time = 6.5 ## initial_exposure_time = 30 -[sla_material:Harz Labs Model Resin Cherry 0.05] -inherits = *common 0.05* -exposure_time = 8 -initial_exposure_time = 45 - ## [sla_material:Prusa Black High Tenacity 0.05] ## inherits = *common 0.05* ## exposure_time = 7 @@ -1765,7 +1852,7 @@ initial_exposure_time = 45 [sla_material:Prusa Green Casting 0.05] inherits = *common 0.05* exposure_time = 13 -initial_exposure_time = 30 +initial_exposure_time = 40 ## [sla_material:Prusa Yellow Solid 0.05] ## inherits = *common 0.05* @@ -1774,10 +1861,10 @@ initial_exposure_time = 30 [sla_material:Prusa White Tough 0.05] inherits = *common 0.05* -exposure_time = 7 +exposure_time = 7.5 initial_exposure_time = 30 -## [sla_material:Prusa Green Transparent 0.05] +## [sla_material:Prusa Transparent Green Tough 0.05] ## inherits = *common 0.05* ## exposure_time = 6 ## initial_exposure_time = 30 @@ -1789,12 +1876,12 @@ initial_exposure_time = 30 [sla_material:Prusa Maroon Tough 0.05] inherits = *common 0.05* -exposure_time = 9 +exposure_time = 7.5 initial_exposure_time = 30 [sla_material:Prusa Pink Tough 0.05] inherits = *common 0.05* -exposure_time = 7 +exposure_time = 8 initial_exposure_time = 30 [sla_material:Prusa Azure Blue Tough 0.05] @@ -1802,6 +1889,11 @@ inherits = *common 0.05* exposure_time = 8 initial_exposure_time = 30 +[sla_material:Prusa Transparent Tough 0.05] +inherits = *common 0.05* +exposure_time = 7 +initial_exposure_time = 15 + ## [sla_material:Prusa Yellow Flexible 0.05] ## inherits = *common 0.05* ## exposure_time = 9 @@ -1809,8 +1901,8 @@ initial_exposure_time = 30 ## [sla_material:Prusa Clear Flexible 0.05] ## inherits = *common 0.05* -## exposure_time = 9 -## initial_exposure_time = 30 +## exposure_time = 5 +## initial_exposure_time = 15 ## [sla_material:Prusa White Flexible 0.05] ## inherits = *common 0.05* @@ -1843,9 +1935,49 @@ initial_exposure_time = 30 [sla_material:Prusa Orange Tough 0.1] inherits = *common 0.1* -exposure_time = 10 +exposure_time = 13 +initial_exposure_time = 45 + +[sla_material:Prusa Beige Tough 0.1] +inherits = *common 0.1* +exposure_time = 13 +initial_exposure_time = 45 + +[sla_material:Prusa Pink Tough 0.1] +inherits = *common 0.1* +exposure_time = 13 +initial_exposure_time = 45 + +[sla_material:Prusa Azure Blue Tough 0.1] +inherits = *common 0.1* +exposure_time = 13 +initial_exposure_time = 45 + +[sla_material:Prusa Maroon Tough 0.1] +inherits = *common 0.1* +exposure_time = 13 +initial_exposure_time = 45 + +[sla_material:Prusa White Tough 0.1] +inherits = *common 0.1* +exposure_time = 13 +initial_exposure_time = 45 + +[sla_material:Prusa Black Tough 0.1] +inherits = *common 0.1* +exposure_time = 13 +initial_exposure_time = 55 + +[sla_material:Prusa Transparent Tough 0.1] +inherits = *common 0.1* +exposure_time = 8 initial_exposure_time = 30 +[sla_material:Prusa Green Casting 0.1] +inherits = *common 0.1* +exposure_time = 15 +initial_exposure_time = 50 + [printer:*common*] printer_technology = FFF bed_shape = 0x0,250x0,250x210,0x210 @@ -1897,7 +2029,7 @@ retract_speed = 35 serial_port = serial_speed = 250000 single_extruder_multi_material = 0 -start_gcode = M115 U3.1.0 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.2.3 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 toolchange_gcode = use_firmware_retraction = 0 use_relative_e_distances = 1 @@ -1934,7 +2066,7 @@ printer_model = MK2SMM inherits = *multimaterial* end_gcode = G1 E-4 F2100.00000\nG91\nG1 Z1 F7200.000\nG90\nG1 X245 Y1\nG1 X240 E4\nG1 F4000\nG1 X190 E2.7 \nG1 F4600\nG1 X110 E2.8\nG1 F5200\nG1 X40 E3 \nG1 E-15.0000 F5000\nG1 E-50.0000 F5400\nG1 E-15.0000 F3000\nG1 E-12.0000 F2000\nG1 F1600\nG1 X0 Y1 E3.0000\nG1 X50 Y1 E-5.0000\nG1 F2000\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-5.0000\nG1 F2400\nG1 X0 Y1 E5.0000\nG1 X50 Y1 E-3.0000\nG4 S0\nM107 ; turn off fan\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nG28 X0 ; home X axis\nM84 ; disable motors\n\n printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN -start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG92 E0.0 +start_gcode = M115 U3.2.3 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT?\nM104 S[first_layer_temperature]\nM140 S[first_layer_bed_temperature]\nM109 S[first_layer_temperature]\nM190 S[first_layer_bed_temperature]\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100\nM92 E140\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\nG92 E0.0 default_print_profile = 0.15mm OPTIMAL [printer:*mm-multi*] @@ -1944,7 +2076,7 @@ end_gcode = {if not has_wipe_tower}\n; Pull the filament into the cooling tubes. extruder_colour = #FFAA55;#E37BA0;#4ECDD3;#FB7259 nozzle_diameter = 0.4,0.4,0.4,0.4 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2\nPRINTER_HAS_BOWDEN -start_gcode = M115 U3.1.0 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_single_extruder_multi_material_priming}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\n{endif}\nG92 E0.0 +start_gcode = M115 U3.2.3 ; tell printer latest fw version\nM204 S[machine_max_acceleration_extruding] T[machine_max_acceleration_retracting] ; MK2 firmware only supports the old M204 format\n; Start G-Code sequence START\nT[initial_tool]\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG21 ; set units to millimeters\nG90 ; use absolute coordinates\nM83 ; use relative distances for extrusion\nG28 W\nG80\nG92 E0.0\nM203 E100 ; set max feedrate\nM92 E140 ; E-steps per filament milimeter\n{if not has_single_extruder_multi_material_priming}\nG1 Z0.250 F7200.000\nG1 X50.0 E80.0 F1000.0\nG1 X160.0 E20.0 F1000.0\nG1 Z0.200 F7200.000\nG1 X220.0 E13 F1000.0\nG1 X240.0 E0 F1000.0\n{endif}\nG92 E0.0 default_print_profile = 0.15mm OPTIMAL # XXXXXXXXXXXXXXXXX @@ -2012,19 +2144,19 @@ min_layer_height = 0.1 inherits = Original Prusa i3 MK2S printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 0.25 nozzle] inherits = Original Prusa i3 MK2S 0.25 nozzle printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 0.6 nozzle] inherits = Original Prusa i3 MK2S 0.6 nozzle printer_model = MK2.5 remaining_times = 1 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5 MMU2 Single] inherits = Original Prusa i3 MK2.5; *mm2* @@ -2053,7 +2185,7 @@ machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL MK2.5 default_filament_profile = Prusament PLA printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\n; select extruder\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; load to nozzle\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\n; select extruder\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; load to nozzle\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK2.5 MMU2 Single 0.6 nozzle] @@ -2095,23 +2227,23 @@ single_extruder_multi_material = 1 # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors\n [printer:Original Prusa i3 MK2.5S] inherits = Original Prusa i3 MK2.5 printer_model = MK2.5S -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5S 0.25 nozzle] inherits = Original Prusa i3 MK2.5 0.25 nozzle printer_model = MK2.5S -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5S 0.6 nozzle] inherits = Original Prusa i3 MK2.5 0.6 nozzle printer_model = MK2.5S -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0 [printer:Original Prusa i3 MK2.5S MMU2S Single] inherits = Original Prusa i3 MK2.5; *mm2s* @@ -2140,7 +2272,7 @@ machine_min_travel_rate = 0 default_print_profile = 0.15mm OPTIMAL MK2.5 default_filament_profile = Prusament PLA printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK2.5\n -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK2.5S MMU2S Single 0.6 nozzle] @@ -2160,7 +2292,7 @@ min_layer_height = 0.05 nozzle_diameter = 0.25 printer_variant = 0.25 default_print_profile = 0.10mm DETAIL 0.25 nozzle -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n [printer:Original Prusa i3 MK2.5S MMU2S] inherits = Original Prusa i3 MK2.5; *mm2s* @@ -2193,7 +2325,7 @@ single_extruder_multi_material = 1 # to be defined explicitely. nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors\n @@ -2225,7 +2357,7 @@ remaining_times = 1 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 max_print_height = 210 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} printer_model = MK3 default_print_profile = 0.15mm QUALITY MK3 @@ -2235,7 +2367,7 @@ nozzle_diameter = 0.25 max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E8.0 F700.0 ; intro line\nG1 X100.0 E12.5 F700.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E8.0 F700.0 ; intro line\nG1 X100.0 E12.5 F700.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} default_print_profile = 0.10mm DETAIL 0.25 nozzle MK3 [printer:Original Prusa i3 MK3 0.6 nozzle] @@ -2249,17 +2381,17 @@ default_print_profile = 0.30mm QUALITY 0.6 nozzle MK3 [printer:Original Prusa i3 MK3S] inherits = Original Prusa i3 MK3 printer_model = MK3S -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} [printer:Original Prusa i3 MK3S 0.25 nozzle] inherits = Original Prusa i3 MK3 0.25 nozzle printer_model = MK3S -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E8.0 F700.0 ; intro line\nG1 X100.0 E12.5 F700.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E8.0 F700.0 ; intro line\nG1 X100.0 E12.5 F700.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} [printer:Original Prusa i3 MK3S 0.6 nozzle] inherits = Original Prusa i3 MK3 0.6 nozzle printer_model = MK3S -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height<0.075}100{else}95{endif} [printer:*mm2*] inherits = Original Prusa i3 MK3 @@ -2289,7 +2421,7 @@ default_filament_profile = Prusament PLA MMU2 inherits = *mm2* single_extruder_multi_material = 0 default_filament_profile = Prusament PLA -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK3 MMU2 Single 0.6 nozzle] @@ -2308,7 +2440,7 @@ nozzle_diameter = 0.25 max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F1000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 E8.0 F1000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n default_print_profile = 0.10mm DETAIL 0.25 nozzle MK3 [printer:Original Prusa i3 MK3 MMU2] @@ -2319,14 +2451,14 @@ inherits = *mm2* machine_max_acceleration_e = 8000,8000 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E32.0 F1073.0\nG1 X5.0 E32.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors\n [printer:Original Prusa i3 MK3S MMU2S Single] inherits = *mm2s* single_extruder_multi_material = 0 default_filament_profile = Prusament PLA -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n end_gcode = G1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors [printer:Original Prusa i3 MK3S MMU2S Single 0.6 nozzle] @@ -2345,7 +2477,7 @@ nozzle_diameter = 0.25 max_layer_height = 0.15 min_layer_height = 0.05 printer_variant = 0.25 -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nTx\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\n\nG21 ; set units to millimeters\n\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nTc\n; purge line\nG1 X55.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F1400.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n default_print_profile = 0.10mm DETAIL 0.25 nozzle MK3 [printer:Original Prusa i3 MK3S MMU2S] @@ -2353,7 +2485,7 @@ inherits = *mm2s* machine_max_acceleration_e = 8000,8000 nozzle_diameter = 0.4,0.4,0.4,0.4,0.4 extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F -start_gcode = M115 U3.7.1 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n +start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors\n # 0.6 nozzle MMU printer profile - only for single mode for now From 3b1a44c0842e590e257dcf7c8c78937e4e7f96ff Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 23 Jul 2019 14:15:42 +0200 Subject: [PATCH 391/627] WIP: Nullable configuration value concept, implemented for ConfigOptionFloatsNullable, ConfigOptionIntsNullable, ConfigOptionPercentsNullable, ConfigOptionBoolsNullable. retract override values were added to the Filament profile: vector of floats: "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_restart_extra", "retract_before_travel", vector of bools: "retract_layer_change", "wipe" vector of percents: "retract_before_wipe" --- src/boost/nowide/utf8_codecvt.hpp | 2 +- src/libslic3r/Config.cpp | 87 +++++-- src/libslic3r/Config.hpp | 365 ++++++++++++++++++++---------- src/libslic3r/Print.cpp | 42 +--- src/libslic3r/PrintConfig.cpp | 28 ++- src/slic3r/GUI/Preset.cpp | 4 + src/slic3r/GUI/PresetBundle.cpp | 2 + 7 files changed, 348 insertions(+), 182 deletions(-) diff --git a/src/boost/nowide/utf8_codecvt.hpp b/src/boost/nowide/utf8_codecvt.hpp index cc5046fc85..877c9f0e0d 100644 --- a/src/boost/nowide/utf8_codecvt.hpp +++ b/src/boost/nowide/utf8_codecvt.hpp @@ -102,7 +102,7 @@ protected: #ifndef BOOST_NOWIDE_DO_LENGTH_MBSTATE_CONST return from - save_from; #else - return save_max - max; + return int(save_max - max); #endif } diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 9d0649a1fe..5776980714 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -211,25 +211,35 @@ std::vector ConfigOptionDef::cli_args(const std::string &key) const ConfigOption* ConfigOptionDef::create_empty_option() const { - switch (this->type) { - case coFloat: return new ConfigOptionFloat(); - case coFloats: return new ConfigOptionFloats(); - case coInt: return new ConfigOptionInt(); - case coInts: return new ConfigOptionInts(); - case coString: return new ConfigOptionString(); - case coStrings: return new ConfigOptionStrings(); - case coPercent: return new ConfigOptionPercent(); - case coPercents: return new ConfigOptionPercents(); - case coFloatOrPercent: return new ConfigOptionFloatOrPercent(); - case coPoint: return new ConfigOptionPoint(); - case coPoints: return new ConfigOptionPoints(); - case coPoint3: return new ConfigOptionPoint3(); -// case coPoint3s: return new ConfigOptionPoint3s(); - case coBool: return new ConfigOptionBool(); - case coBools: return new ConfigOptionBools(); - case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map); - default: throw std::runtime_error(std::string("Unknown option type for option ") + this->label); - } + if (this->nullable) { + switch (this->type) { + case coFloats: return new ConfigOptionFloatsNullable(); + case coInts: return new ConfigOptionIntsNullable(); + case coPercents: return new ConfigOptionPercentsNullable(); + case coBools: return new ConfigOptionBoolsNullable(); + default: throw std::runtime_error(std::string("Unknown option type for nullable option ") + this->label); + } + } else { + switch (this->type) { + case coFloat: return new ConfigOptionFloat(); + case coFloats: return new ConfigOptionFloats(); + case coInt: return new ConfigOptionInt(); + case coInts: return new ConfigOptionInts(); + case coString: return new ConfigOptionString(); + case coStrings: return new ConfigOptionStrings(); + case coPercent: return new ConfigOptionPercent(); + case coPercents: return new ConfigOptionPercents(); + case coFloatOrPercent: return new ConfigOptionFloatOrPercent(); + case coPoint: return new ConfigOptionPoint(); + case coPoints: return new ConfigOptionPoints(); + case coPoint3: return new ConfigOptionPoint3(); + // case coPoint3s: return new ConfigOptionPoint3s(); + case coBool: return new ConfigOptionBool(); + case coBools: return new ConfigOptionBools(); + case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map); + default: throw std::runtime_error(std::string("Unknown option type for option ") + this->label); + } + } } ConfigOption* ConfigOptionDef::create_default_option() const @@ -254,6 +264,13 @@ ConfigOptionDef* ConfigDef::add(const t_config_option_key &opt_key, ConfigOption return opt; } +ConfigOptionDef* ConfigDef::add_nullable(const t_config_option_key &opt_key, ConfigOptionType type) +{ + ConfigOptionDef *def = this->add(opt_key, type); + def->nullable = true; + return def; +} + std::string ConfigOptionDef::nocli = "~~~noCLI"; std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function filter) const @@ -642,6 +659,17 @@ void ConfigBase::save(const std::string &file) const c.close(); } +// Set all the nullable values to nils. +void ConfigBase::null_nullables() +{ + for (const std::string &opt_key : this->keys()) { + ConfigOption *opt = this->optptr(opt_key, false); + assert(opt != nullptr); + if (opt->nullable()) + opt->deserialize("nil"); + } +} + bool DynamicConfig::operator==(const DynamicConfig &rhs) const { auto it1 = this->options.begin(); @@ -655,6 +683,19 @@ bool DynamicConfig::operator==(const DynamicConfig &rhs) const return it1 == it1_end && it2 == it2_end; } +// Remove options with all nil values, those are optional and it does not help to hold them. +size_t DynamicConfig::remove_nil_options() +{ + size_t cnt_removed = 0; + for (auto it = options.begin(); it != options.end();) + if (it->second->is_nil()) { + it = options.erase(it); + ++ cnt_removed; + } else + ++ it; + return cnt_removed; +} + ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) { auto it = options.find(opt_key); @@ -838,18 +879,22 @@ CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloat) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloats) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatsNullable) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInt) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInts) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionIntsNullable) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionString) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionStrings) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercent) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercents) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercentsNullable) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatOrPercent) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoints) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint3) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBool) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBools) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBoolsNullable) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionEnumGeneric) CEREAL_REGISTER_TYPE(Slic3r::ConfigBase) CEREAL_REGISTER_TYPE(Slic3r::DynamicConfig) @@ -868,17 +913,21 @@ CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::Con CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionFloat) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionFloats) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionFloatsNullable) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionInt) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionInts) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionIntsNullable) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionString) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionStrings) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloat, Slic3r::ConfigOptionPercent) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercents) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercentsNullable) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionPercent, Slic3r::ConfigOptionFloatOrPercent) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionPoint) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionPoints) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionPoint3) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::ConfigOptionBool) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionBools) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionBoolsNullable) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionInt, Slic3r::ConfigOptionEnumGeneric) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigBase, Slic3r::DynamicConfig) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 7d96f6cf3d..d8759c6ebe 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -15,6 +15,7 @@ #include "clonable_ptr.hpp" #include "Point.hpp" +#include #include #include @@ -124,6 +125,10 @@ public: bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); } bool is_scalar() const { return (int(this->type()) & int(coVectorType)) == 0; } bool is_vector() const { return ! this->is_scalar(); } + // If this option is nullable, then it may have its value or values set to nil. + virtual bool nullable() const { return false; } + // A scalar is nil, or all values of a vector are nil. + virtual bool is_nil() const { return false; } }; typedef ConfigOption* ConfigOptionPtr; @@ -345,26 +350,35 @@ private: template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; -class ConfigOptionFloats : public ConfigOptionVector +template +class ConfigOptionFloatsTempl : public ConfigOptionVector { public: - ConfigOptionFloats() : ConfigOptionVector() {} - explicit ConfigOptionFloats(size_t n, double value) : ConfigOptionVector(n, value) {} - explicit ConfigOptionFloats(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} - explicit ConfigOptionFloats(const std::vector &vec) : ConfigOptionVector(vec) {} - explicit ConfigOptionFloats(std::vector &&vec) : ConfigOptionVector(std::move(vec)) {} + ConfigOptionFloatsTempl() : ConfigOptionVector() {} + explicit ConfigOptionFloatsTempl(size_t n, double value) : ConfigOptionVector(n, value) {} + explicit ConfigOptionFloatsTempl(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} + explicit ConfigOptionFloatsTempl(const std::vector &vec) : ConfigOptionVector(vec) {} + explicit ConfigOptionFloatsTempl(std::vector &&vec) : ConfigOptionVector(std::move(vec)) {} static ConfigOptionType static_type() { return coFloats; } ConfigOptionType type() const override { return static_type(); } - ConfigOption* clone() const override { return new ConfigOptionFloats(*this); } - bool operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; } + ConfigOption* clone() const override { return new ConfigOptionFloatsTempl(*this); } + bool operator==(const ConfigOptionFloatsTempl &rhs) const { return this->values == rhs.values; } + // Could a special "nil" value be stored inside the vector, indicating undefined value? + bool nullable() const override { return NULLABLE; } + // Special "nil" value to be stored into the vector if this->supports_nil(). + static double nil_value() { return std::numeric_limits::quiet_NaN(); } + // A scalar is nil, or all values of a vector are nil. + virtual bool is_nil() const override { for (auto v : this->values) if (! std::isnan(v)) return false; return true; } + bool is_nil(size_t idx) const { return std::isnan(v->values[idx]); } std::string serialize() const override { std::ostringstream ss; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - if (it - this->values.begin() != 0) ss << ","; - ss << *it; + for (const double &v : this->values) { + if (&v != &this->values.front()) + ss << ","; + serialize_single_value(ss, v); } return ss.str(); } @@ -373,14 +387,14 @@ public: { std::vector vv; vv.reserve(this->values.size()); - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + for (const double v : this->values) { std::ostringstream ss; - ss << *it; + serialize_single_value(ss, v); vv.push_back(ss.str()); } return vv; } - + bool deserialize(const std::string &str, bool append = false) override { if (! append) @@ -388,25 +402,49 @@ public: std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { - std::istringstream iss(item_str); - double value; - iss >> value; - this->values.push_back(value); + boost::trim(item_str); + if (item_str == "nil") { + if (NULLABLE) + this->values.push_back(nil_value()); + else + std::runtime_error("Deserializing nil into a non-nullable object"); + } else { + std::istringstream iss(item_str); + double value; + iss >> value; + this->values.push_back(value); + } } return true; } - ConfigOptionFloats& operator=(const ConfigOption *opt) + ConfigOptionFloatsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } +protected: + void serialize_single_value(std::ostringstream &ss, const double v) const { + if (std::isfinite(v)) + ss << v; + else if (std::isnan(v)) { + if (NULLABLE) + ss << "nil"; + else + std::runtime_error("Serializing NaN"); + } else + std::runtime_error("Serializing invalid number"); + } + private: friend class cereal::access; template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; +using ConfigOptionFloats = ConfigOptionFloatsTempl; +using ConfigOptionFloatsNullable = ConfigOptionFloatsTempl; + class ConfigOptionInt : public ConfigOptionSingle { public: @@ -447,35 +485,45 @@ private: template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; -class ConfigOptionInts : public ConfigOptionVector +template +class ConfigOptionIntsTempl : public ConfigOptionVector { public: - ConfigOptionInts() : ConfigOptionVector() {} - explicit ConfigOptionInts(size_t n, int value) : ConfigOptionVector(n, value) {} - explicit ConfigOptionInts(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} + ConfigOptionIntsTempl() : ConfigOptionVector() {} + explicit ConfigOptionIntsTempl(size_t n, int value) : ConfigOptionVector(n, value) {} + explicit ConfigOptionIntsTempl(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} static ConfigOptionType static_type() { return coInts; } ConfigOptionType type() const override { return static_type(); } - ConfigOption* clone() const override { return new ConfigOptionInts(*this); } - ConfigOptionInts& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionInts &rhs) const { return this->values == rhs.values; } + ConfigOption* clone() const override { return new ConfigOptionIntsTempl(*this); } + ConfigOptionIntsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionIntsTempl &rhs) const { return this->values == rhs.values; } + // Could a special "nil" value be stored inside the vector, indicating undefined value? + bool nullable() const override { return NULLABLE; } + // Special "nil" value to be stored into the vector if this->supports_nil(). + static int nil_value() { return std::numeric_limits::max(); } + // A scalar is nil, or all values of a vector are nil. + virtual bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } + bool is_nil(size_t idx) const { return v->values[idx] == nil_value(); } - std::string serialize() const override { + std::string serialize() const override + { std::ostringstream ss; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - if (it - this->values.begin() != 0) ss << ","; - ss << *it; + for (const int &v : this->values) { + if (&v != &this->values.front()) + ss << ","; + serialize_single_value(ss, v); } return ss.str(); } - std::vector vserialize() const override + std::vector vserialize() const override { std::vector vv; vv.reserve(this->values.size()); - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + for (const int v : this->values) { std::ostringstream ss; - ss << *it; + serialize_single_value(ss, v); vv.push_back(ss.str()); } return vv; @@ -488,19 +536,40 @@ public: std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { - std::istringstream iss(item_str); - int value; - iss >> value; - this->values.push_back(value); + boost::trim(item_str); + if (item_str == "nil") { + if (NULLABLE) + this->values.push_back(nil_value()); + else + std::runtime_error("Deserializing nil into a non-nullable object"); + } else { + std::istringstream iss(item_str); + int value; + iss >> value; + this->values.push_back(value); + } } return true; } private: + void serialize_single_value(std::ostringstream &ss, const int v) const { + if (v == nil_value()) { + if (NULLABLE) + ss << "nil"; + else + std::runtime_error("Serializing NaN"); + } else + ss << v; + } + friend class cereal::access; template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; +using ConfigOptionInts = ConfigOptionIntsTempl; +using ConfigOptionIntsNullable = ConfigOptionIntsTempl; + class ConfigOptionString : public ConfigOptionSingle { public: @@ -603,64 +672,61 @@ private: template void serialize(Archive &ar) { ar(cereal::base_class(this)); } }; -class ConfigOptionPercents : public ConfigOptionFloats +template +class ConfigOptionPercentsTempl : public ConfigOptionFloatsTempl { public: - ConfigOptionPercents() : ConfigOptionFloats() {} - explicit ConfigOptionPercents(size_t n, double value) : ConfigOptionFloats(n, value) {} - explicit ConfigOptionPercents(std::initializer_list il) : ConfigOptionFloats(std::move(il)) {} + ConfigOptionPercentsTempl() : ConfigOptionFloatsTempl() {} + explicit ConfigOptionPercentsTempl(size_t n, double value) : ConfigOptionFloatsTempl(n, value) {} + explicit ConfigOptionPercentsTempl(std::initializer_list il) : ConfigOptionFloatsTempl(std::move(il)) {} + explicit ConfigOptionPercentsTempl(const std::vector& vec) : ConfigOptionFloatsTempl(vec) {} + explicit ConfigOptionPercentsTempl(std::vector&& vec) : ConfigOptionFloatsTempl(std::move(vec)) {} static ConfigOptionType static_type() { return coPercents; } ConfigOptionType type() const override { return static_type(); } - ConfigOption* clone() const override { return new ConfigOptionPercents(*this); } - ConfigOptionPercents& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionPercents &rhs) const { return this->values == rhs.values; } + ConfigOption* clone() const override { return new ConfigOptionPercentsTempl(*this); } + ConfigOptionPercentsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionPercentsTempl &rhs) const { return this->values == rhs.values; } std::string serialize() const override { std::ostringstream ss; - for (const auto &v : this->values) { - if (&v != &this->values.front()) ss << ","; - ss << v << "%"; + for (const double &v : this->values) { + if (&v != &this->values.front()) + ss << ","; + this->serialize_single_value(ss, v); + if (! std::isnan(v)) + ss << "%"; } std::string str = ss.str(); return str; } - + std::vector vserialize() const override { std::vector vv; vv.reserve(this->values.size()); - for (const auto v : this->values) { + for (const double v : this->values) { std::ostringstream ss; - ss << v; - std::string sout = ss.str() + "%"; - vv.push_back(sout); + this->serialize_single_value(ss, v); + if (! std::isnan(v)) + ss << "%"; + vv.push_back(ss.str()); } return vv; } - bool deserialize(const std::string &str, bool append = false) override - { - if (! append) - this->values.clear(); - std::istringstream is(str); - std::string item_str; - while (std::getline(is, item_str, ',')) { - std::istringstream iss(item_str); - double value; - // don't try to parse the trailing % since it's optional - iss >> value; - this->values.push_back(value); - } - return true; - } + // The float's deserialize function shall ignore the trailing optional %. + // bool deserialize(const std::string &str, bool append = false) override; private: friend class cereal::access; - template void serialize(Archive &ar) { ar(cereal::base_class(this)); } + template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; +using ConfigOptionPercents = ConfigOptionPercentsTempl; +using ConfigOptionPercentsNullable = ConfigOptionPercentsTempl; + class ConfigOptionFloatOrPercent : public ConfigOptionPercent { public: @@ -887,18 +953,28 @@ private: template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; -class ConfigOptionBools : public ConfigOptionVector +template +class ConfigOptionBoolsTempl : public ConfigOptionVector { public: - ConfigOptionBools() : ConfigOptionVector() {} - explicit ConfigOptionBools(size_t n, bool value) : ConfigOptionVector(n, (unsigned char)value) {} - explicit ConfigOptionBools(std::initializer_list il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); } + ConfigOptionBoolsTempl() : ConfigOptionVector() {} + explicit ConfigOptionBoolsTempl(size_t n, bool value) : ConfigOptionVector(n, (unsigned char)value) {} + explicit ConfigOptionBoolsTempl(std::initializer_list il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); } + explicit ConfigOptionBoolsTempl(const std::vector& vec) : ConfigOptionVector(vec) {} + explicit ConfigOptionBoolsTempl(std::vector&& vec) : ConfigOptionVector(std::move(vec)) {} static ConfigOptionType static_type() { return coBools; } ConfigOptionType type() const override { return static_type(); } - ConfigOption* clone() const override { return new ConfigOptionBools(*this); } - ConfigOptionBools& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionBools &rhs) const { return this->values == rhs.values; } + ConfigOption* clone() const override { return new ConfigOptionBoolsTempl(*this); } + ConfigOptionBoolsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionBoolsTempl &rhs) const { return this->values == rhs.values; } + // Could a special "nil" value be stored inside the vector, indicating undefined value? + bool nullable() const override { return NULLABLE; } + // Special "nil" value to be stored into the vector if this->supports_nil(). + static unsigned char nil_value() { return std::numeric_limits::max(); } + // A scalar is nil, or all values of a vector are nil. + virtual bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } + bool is_nil(size_t idx) const { return v->values[idx] == nil_value(); } bool& get_at(size_t i) { assert(! this->values.empty()); @@ -911,19 +987,20 @@ public: std::string serialize() const override { std::ostringstream ss; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - if (it - this->values.begin() != 0) ss << ","; - ss << (*it ? "1" : "0"); - } + for (const unsigned char &v : this->values) { + if (&v != &this->values.front()) + ss << ","; + this->serialize_single_value(ss, v); + } return ss.str(); } std::vector vserialize() const override { std::vector vv; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - std::ostringstream ss; - ss << (*it ? "1" : "0"); + for (const unsigned char v : this->values) { + std::ostringstream ss; + this->serialize_single_value(ss, v); vv.push_back(ss.str()); } return vv; @@ -936,16 +1013,37 @@ public: std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { - this->values.push_back(item_str.compare("1") == 0); + boost::trim(item_str); + if (item_str == "nil") { + if (NULLABLE) + this->values.push_back(nil_value()); + else + std::runtime_error("Deserializing nil into a non-nullable object"); + } else + this->values.push_back(item_str.compare("1") == 0); } return true; } +protected: + void serialize_single_value(std::ostringstream &ss, const unsigned char v) const { + if (v == nil_value()) { + if (NULLABLE) + ss << "nil"; + else + std::runtime_error("Serializing NaN"); + } else + ss << (v ? "1" : "0"); + } + private: friend class cereal::access; template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } }; +using ConfigOptionBools = ConfigOptionBoolsTempl; +using ConfigOptionBoolsNullable = ConfigOptionBoolsTempl; + // Map from an enum integer value to an enum name. typedef std::vector t_config_enum_names; // Map from an enum name to an enum integer value. @@ -1096,6 +1194,8 @@ public: t_config_option_key opt_key; // What type? bool, int, string etc. ConfigOptionType type = coNone; + // If a type is nullable, then it accepts a "nil" value (scalar) or "nil" values (vector). + bool nullable = false; // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor. Slic3r::clonable_ptr default_value; void set_default_value(const ConfigOption* ptr) { this->default_value = Slic3r::clonable_ptr(ptr); } @@ -1107,45 +1207,65 @@ public: ConfigOption* create_default_option() const; template ConfigOption* load_option_from_archive(Archive &archive) const { - switch (this->type) { - case coFloat: { auto opt = new ConfigOptionFloat(); archive(*opt); return opt; } - case coFloats: { auto opt = new ConfigOptionFloats(); archive(*opt); return opt; } - case coInt: { auto opt = new ConfigOptionInt(); archive(*opt); return opt; } - case coInts: { auto opt = new ConfigOptionInts(); archive(*opt); return opt; } - case coString: { auto opt = new ConfigOptionString(); archive(*opt); return opt; } - case coStrings: { auto opt = new ConfigOptionStrings(); archive(*opt); return opt; } - case coPercent: { auto opt = new ConfigOptionPercent(); archive(*opt); return opt; } - case coPercents: { auto opt = new ConfigOptionPercents(); archive(*opt); return opt; } - case coFloatOrPercent: { auto opt = new ConfigOptionFloatOrPercent(); archive(*opt); return opt; } - case coPoint: { auto opt = new ConfigOptionPoint(); archive(*opt); return opt; } - case coPoints: { auto opt = new ConfigOptionPoints(); archive(*opt); return opt; } - case coPoint3: { auto opt = new ConfigOptionPoint3(); archive(*opt); return opt; } - case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; } - case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; } - case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; } - default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); - } + if (this->nullable) { + switch (this->type) { + case coFloats: { auto opt = new ConfigOptionFloatsNullable(); archive(*opt); return opt; } + case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } + case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; } + case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; } + default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); + } + } else { + switch (this->type) { + case coFloat: { auto opt = new ConfigOptionFloat(); archive(*opt); return opt; } + case coFloats: { auto opt = new ConfigOptionFloats(); archive(*opt); return opt; } + case coInt: { auto opt = new ConfigOptionInt(); archive(*opt); return opt; } + case coInts: { auto opt = new ConfigOptionInts(); archive(*opt); return opt; } + case coString: { auto opt = new ConfigOptionString(); archive(*opt); return opt; } + case coStrings: { auto opt = new ConfigOptionStrings(); archive(*opt); return opt; } + case coPercent: { auto opt = new ConfigOptionPercent(); archive(*opt); return opt; } + case coPercents: { auto opt = new ConfigOptionPercents(); archive(*opt); return opt; } + case coFloatOrPercent: { auto opt = new ConfigOptionFloatOrPercent(); archive(*opt); return opt; } + case coPoint: { auto opt = new ConfigOptionPoint(); archive(*opt); return opt; } + case coPoints: { auto opt = new ConfigOptionPoints(); archive(*opt); return opt; } + case coPoint3: { auto opt = new ConfigOptionPoint3(); archive(*opt); return opt; } + case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; } + case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; } + case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; } + default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); + } + } } template ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const { - switch (this->type) { - case coFloat: archive(*static_cast(opt)); break; - case coFloats: archive(*static_cast(opt)); break; - case coInt: archive(*static_cast(opt)); break; - case coInts: archive(*static_cast(opt)); break; - case coString: archive(*static_cast(opt)); break; - case coStrings: archive(*static_cast(opt)); break; - case coPercent: archive(*static_cast(opt)); break; - case coPercents: archive(*static_cast(opt)); break; - case coFloatOrPercent: archive(*static_cast(opt)); break; - case coPoint: archive(*static_cast(opt)); break; - case coPoints: archive(*static_cast(opt)); break; - case coPoint3: archive(*static_cast(opt)); break; - case coBool: archive(*static_cast(opt)); break; - case coBools: archive(*static_cast(opt)); break; - case coEnum: archive(*static_cast(opt)); break; - default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); - } + if (this->nullable) { + switch (this->type) { + case coFloats: archive(*static_cast(opt)); break; + case coInts: archive(*static_cast(opt)); break; + case coPercents: archive(*static_cast(opt));break; + case coBools: archive(*static_cast(opt)); break; + default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); + } + } else { + switch (this->type) { + case coFloat: archive(*static_cast(opt)); break; + case coFloats: archive(*static_cast(opt)); break; + case coInt: archive(*static_cast(opt)); break; + case coInts: archive(*static_cast(opt)); break; + case coString: archive(*static_cast(opt)); break; + case coStrings: archive(*static_cast(opt)); break; + case coPercent: archive(*static_cast(opt)); break; + case coPercents: archive(*static_cast(opt)); break; + case coFloatOrPercent: archive(*static_cast(opt)); break; + case coPoint: archive(*static_cast(opt)); break; + case coPoints: archive(*static_cast(opt)); break; + case coPoint3: archive(*static_cast(opt)); break; + case coBool: archive(*static_cast(opt)); break; + case coBools: archive(*static_cast(opt)); break; + case coEnum: archive(*static_cast(opt)); break; + default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); + } + } // Make the compiler happy, shut up the warnings. return nullptr; } @@ -1263,6 +1383,7 @@ public: protected: ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type); + ConfigOptionDef* add_nullable(const t_config_option_key &opt_key, ConfigOptionType type); }; // An abstract configuration store. @@ -1347,6 +1468,9 @@ public: void load(const boost::property_tree::ptree &tree); void save(const std::string &file) const; + // Set all the nullable values to nils. + void null_nullables(); + private: // Set a configuration value from a string. bool set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &str, bool append); @@ -1444,6 +1568,9 @@ public: return true; } + // Remove options with all nil values, those are optional and it does not help to hold them. + size_t remove_nil_options(); + // Allow DynamicConfig to be instantiated on ints own without a definition. // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. const ConfigDef* def() const override { return nullptr; }; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index a9b8a827f1..af2479e5a1 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1783,47 +1783,7 @@ std::string Print::output_filename(const std::string &filename_base) const DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders(); return this->PrintBase::output_filename(m_config.output_filename_format.value, ".gcode", filename_base, &config); } -/* -// Shorten the dhms time by removing the seconds, rounding the dhm to full minutes -// and removing spaces. -static std::string short_time(const std::string &time) -{ - // Parse the dhms time format. - int days = 0; - int hours = 0; - int minutes = 0; - int seconds = 0; - if (time.find('d') != std::string::npos) - ::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds); - else if (time.find('h') != std::string::npos) - ::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds); - else if (time.find('m') != std::string::npos) - ::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds); - else if (time.find('s') != std::string::npos) - ::sscanf(time.c_str(), "%ds", &seconds); - // Round to full minutes. - if (days + hours + minutes > 0 && seconds >= 30) { - if (++ minutes == 60) { - minutes = 0; - if (++ hours == 24) { - hours = 0; - ++ days; - } - } - } - // Format the dhm time. - char buffer[64]; - if (days > 0) - ::sprintf(buffer, "%dd%dh%dm", days, hours, minutes); - else if (hours > 0) - ::sprintf(buffer, "%dh%dm", hours, minutes); - else if (minutes > 0) - ::sprintf(buffer, "%dm", minutes); - else - ::sprintf(buffer, "%ds", seconds); - return buffer; -} -*/ + DynamicConfig PrintStatistics::config() const { DynamicConfig config; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 7d451a4cbc..e147e10871 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2228,6 +2228,30 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("mm"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0)); + + // Declare retract values for filament profile, overriding the printer's extruder profile. + for (const char *opt_key : { + // floats + "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_restart_extra", "retract_before_travel", + // bools + "retract_layer_change", "wipe", + // percents + "retract_before_wipe"}) { + auto it_opt = options.find(opt_key); + assert(it_opt != options.end()); + def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type); + def->label = it_opt->second.label; + def->full_label = it_opt->second.full_label; + def->tooltip = it_opt->second.tooltip; + def->sidetext = it_opt->second.sidetext; + def->mode = it_opt->second.mode; + switch (def->type) { + case coFloats : def->set_default_value(new ConfigOptionFloatsNullable (static_cast(it_opt->second.default_value.get())->values)); break; + case coPercents : def->set_default_value(new ConfigOptionPercentsNullable(static_cast(it_opt->second.default_value.get())->values)); break; + case coBools : def->set_default_value(new ConfigOptionBoolsNullable (static_cast(it_opt->second.default_value.get())->values)); break; + default: assert(false); + } + } } void PrintConfigDef::init_sla_params() @@ -2987,7 +3011,7 @@ std::string FullPrintConfig::validate() } case coFloats: case coPercents: - for (double v : static_cast(opt)->values) + for (double v : static_cast*>(opt)->values) if (v < optdef->min || v > optdef->max) { out_of_range = true; break; @@ -3000,7 +3024,7 @@ std::string FullPrintConfig::validate() break; } case coInts: - for (int v : static_cast(opt)->values) + for (int v : static_cast*>(opt)->values) if (v < optdef->min || v > optdef->max) { out_of_range = true; break; diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 3d467d7f8f..26e6c4e1e5 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -400,6 +400,10 @@ const std::vector& Preset::filament_options() "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode", + // Retract overrides + "filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel", + "filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe", + // Profile compatibility "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 00c1f81685..7c9f7af4e9 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -72,6 +72,8 @@ PresetBundle::PresetBundle() : this->filaments.default_preset().config.option("filament_settings_id", true)->values = { "" }; this->filaments.default_preset().compatible_printers_condition(); this->filaments.default_preset().inherits(); + // Set all the nullable values to nils. + this->filaments.default_preset().config.null_nullables(); this->sla_materials.default_preset().config.optptr("sla_material_settings_id", true); this->sla_materials.default_preset().compatible_printers_condition(); From 0a5f8aa2e8e57939451cdb8508141bfb856dd68b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 23 Jul 2019 15:13:47 +0200 Subject: [PATCH 392/627] An attempt to silence the Wreorder warning on OSX build server Clang apparently cares about order of command line arguments, so -Wall should come before -Wno-whatever --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f2332ce0f..ba291e4509 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,7 +163,7 @@ if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUXX) endif() if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") - add_compile_options(-Wall) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall" ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder" ) # On GCC and Clang, no return from a non-void function is a warning only. Here, we make it an error. From 0d10d8aba706ef08cfd8f15c8edcaf0c24ac0b14 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 23 Jul 2019 15:14:08 +0200 Subject: [PATCH 393/627] 1st installment for selection's undo/redo snapshots --- src/libslic3r/Technologies.hpp | 4 + src/slic3r/GUI/GLCanvas3D.cpp | 65 ++++++ src/slic3r/GUI/GUI_ObjectList.cpp | 98 ++++++++- src/slic3r/GUI/Selection.cpp | 322 ++++++++++++++++++++++++++++++ src/slic3r/GUI/Selection.hpp | 46 +++++ 5 files changed, 534 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index ae43369d22..2d9078c6d3 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -41,4 +41,8 @@ #define ENABLE_TEXTURES_FROM_SVG (1 && ENABLE_1_42_0_ALPHA7) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#define ENABLE_SELECTION_UNDO_REDO 1 +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + #endif // _technologies_h_ diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 38c0315abf..4f1279798b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1723,8 +1723,16 @@ void GLCanvas3D::select_all() void GLCanvas3D::deselect_all() { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + m_selection.remove_all(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_selection.clear(); m_selection.set_mode(Selection::Instance); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxGetApp().obj_manipul()->set_dirty(); m_gizmos.reset_all_states(); m_gizmos.update_data(); @@ -5661,7 +5669,15 @@ void GLCanvas3D::_update_selection_from_hover() if (m_hover_volume_idxs.empty()) { if (!ctrl_pressed && (m_rectangle_selection.get_state() == GLSelectionRectangle::Select)) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + m_selection.remove_all(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_selection.clear(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return; } @@ -5678,6 +5694,55 @@ void GLCanvas3D::_update_selection_from_hover() } } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + bool selection_changed = false; + if (state == GLSelectionRectangle::Select) + { + bool contains_all = true; + for (int i : m_hover_volume_idxs) + { + if (!m_selection.contains_volume((unsigned int)i)) + { + contains_all = false; + break; + } + } + + // the selection is going to be modified (Add) + if (!contains_all) + { + wxGetApp().plater()->take_snapshot(_(L("Selection - Add - _update_selection_from_hover()"))); + selection_changed = true; + } + } + else + { + bool contains_any = false; + for (int i : m_hover_volume_idxs) + { + if (m_selection.contains_volume((unsigned int)i)) + { + contains_any = true; + break; + } + } + + // the selection is going to be modified (Remove) + if (contains_any) + { + wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - _update_selection_from_hover()"))); + selection_changed = true; + } + } + + if (!selection_changed) + return; + + Plater::SuppressSnapshots suppress(wxGetApp().plater()); +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + if ((state == GLSelectionRectangle::Select) && !ctrl_pressed) m_selection.clear(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a2ddba376a..882636f1c8 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2759,26 +2759,75 @@ void ObjectList::update_selections_on_canvas() const int sel_cnt = GetSelectedItemsCount(); if (sel_cnt == 0) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + selection.remove_all(); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ selection.clear(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); return; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + std::vector volume_idxs; + Selection::EMode mode = Selection::Volume; + auto add_to_selection = [this, &volume_idxs](const wxDataViewItem& item, const Selection& selection, int instance_idx, Selection::EMode& mode) +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool as_single_selection) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { const ItemType& type = m_objects_model->GetItemType(item); const int obj_idx = m_objects_model->GetObjectIdByItem(item); if (type == itVolume) { const int vol_idx = m_objects_model->GetVolumeIdByItem(item); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + std::vector idxs = selection.get_volume_idxs_from_volume(obj_idx, std::max(instance_idx, 0), vol_idx); + volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ selection.add_volume(obj_idx, vol_idx, std::max(instance_idx, 0), as_single_selection); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } else if (type == itInstance) { const int inst_idx = m_objects_model->GetInstanceIdByItem(item); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + mode = Selection::Instance; + std::vector idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx); + volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ selection.add_instance(obj_idx, inst_idx, as_single_selection); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + { + mode = Selection::Instance; + std::vector idxs = selection.get_volume_idxs_from_object(obj_idx); + volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); + } +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ selection.add_object(obj_idx, as_single_selection); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ }; // stores current instance idx before to clear the selection @@ -2786,6 +2835,14 @@ void ObjectList::update_selections_on_canvas() if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) + add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode); + else + add_to_selection(item, selection, instance_idx, mode); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, true); else @@ -2794,14 +2851,53 @@ void ObjectList::update_selections_on_canvas() wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); wxGetApp().plater()->canvas3D()->render(); return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } - +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + else + { + wxDataViewItemArray sels; + GetSelections(sels); + + for (auto item : sels) + { + add_to_selection(item, selection, instance_idx, mode); + } + } + + if (selection.contains_all_volumes(volume_idxs)) + { + // remove + volume_idxs = selection.get_missing_volume_idxs_from(volume_idxs); + if (volume_idxs.size() > 0) + { +// std::cout << "ObjectList- > Selection-Remove " << volume_idxs.size() << " volumes" << std::endl; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Selection - Remove - update_selections_on_canvas()"))); + selection.remove_volumes(mode, volume_idxs); + } + } + else + { + // add + volume_idxs = selection.get_unselected_volume_idxs_from(volume_idxs); +// std::cout << "ObjectList- > Selection-Add " << volume_idxs.size() << " volumes - mode: " << ((mode == Selection::Volume) ? "Volume" : "Instance") << std::endl; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Selection - Add - update_selections_on_canvas()"))); + selection.add_volumes(mode, volume_idxs, sel_cnt == 1); + } +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxDataViewItemArray sels; GetSelections(sels); selection.clear(); for (auto item: sels) add_to_selection(item, selection, instance_idx, false); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); wxGetApp().plater()->canvas3D()->render(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index b990f28b89..4dfe445cb5 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -140,11 +140,26 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier; needs_reset |= is_any_modifier() && !volume->is_modifier; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if !ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (needs_reset) clear(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // !ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!already_contained || needs_reset) { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + wxGetApp().plater()->take_snapshot(_(L("Selection - Add - add()"))); + + if (needs_reset) + clear(); +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + if (!keep_instance_mode) m_mode = volume->is_modifier ? Volume : Instance; } @@ -163,7 +178,16 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec } case Instance: { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + Plater::SuppressSnapshots suppress(wxGetApp().plater()); + add_instance(volume->object_idx(), volume->instance_idx(), as_single_selection); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ do_add_instance(volume->object_idx(), volume->instance_idx()); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ break; } } @@ -177,6 +201,15 @@ void Selection::remove(unsigned int volume_idx) if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + if (!contains_volume(volume_idx)) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove()"))); +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + GLVolume* volume = (*m_volumes)[volume_idx]; switch (m_mode) @@ -202,13 +235,32 @@ void Selection::add_object(unsigned int object_idx, bool as_single_selection) if (!m_valid) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + std::vector volume_idxs = get_volume_idxs_from_object(object_idx); + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection - Add - add_object()"))); +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // resets the current list if needed if (as_single_selection) clear(); m_mode = Instance; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + do_add_volumes(volume_idxs); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ do_add_object(object_idx); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ update_type(); this->set_bounding_boxes_dirty(); @@ -219,6 +271,12 @@ void Selection::remove_object(unsigned int object_idx) if (!m_valid) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_object()"))); +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + do_remove_object(object_idx); update_type(); @@ -230,13 +288,32 @@ void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, if (!m_valid) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + std::vector volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx); + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection - Add - add_instance()"))); +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // resets the current list if needed if (as_single_selection) clear(); m_mode = Instance; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + do_add_volumes(volume_idxs); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ do_add_instance(object_idx, instance_idx); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ update_type(); this->set_bounding_boxes_dirty(); @@ -247,6 +324,12 @@ void Selection::remove_instance(unsigned int object_idx, unsigned int instance_i if (!m_valid) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_instance()"))); +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + do_remove_instance(object_idx, instance_idx); update_type(); @@ -258,12 +341,28 @@ void Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int if (!m_valid) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + std::vector volume_idxs = get_volume_idxs_from_volume(object_idx, instance_idx, volume_idx); + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection - Add - add_volume()"))); +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // resets the current list if needed if (as_single_selection) clear(); m_mode = Volume; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + do_add_volumes(volume_idxs); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { GLVolume* v = (*m_volumes)[i]; @@ -273,6 +372,9 @@ void Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int do_add_volume(i); } } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ update_type(); this->set_bounding_boxes_dirty(); @@ -283,6 +385,12 @@ void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) if (!m_valid) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_volume()"))); +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { GLVolume* v = (*m_volumes)[i]; @@ -294,11 +402,76 @@ void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) this->set_bounding_boxes_dirty(); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO +void Selection::add_volumes(EMode mode, const std::vector& volume_idxs, bool as_single_selection) +{ + if (!m_valid) + return; + + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection - Add - add_volumes()"))); + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = mode; + for (unsigned int i : volume_idxs) + { + if (i < (unsigned int)m_volumes->size()) + do_add_volume(i); + } +// do_add_volumes(volume_idxs); + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove_volumes(EMode mode, const std::vector& volume_idxs) +{ + if (!m_valid) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_volumes()"))); + + m_mode = mode; + for (unsigned int i : volume_idxs) + { + if (i < (unsigned int)m_volumes->size()) + do_remove_volume(i); + } + + update_type(); + this->set_bounding_boxes_dirty(); +} +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + void Selection::add_all() { if (!m_valid) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + unsigned int count = 0; + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + if (!(*m_volumes)[i]->is_wipe_tower) + ++count; + } + + if ((unsigned int)m_list.size() == count) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection - Add - add_all()"))); +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + m_mode = Instance; clear(); @@ -312,6 +485,24 @@ void Selection::add_all() this->set_bounding_boxes_dirty(); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO +void Selection::remove_all() +{ + if (!m_valid) + return; + + if (is_empty()) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_all()"))); + + m_mode = Instance; + clear(); +} +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + void Selection::set_deserialized(EMode mode, const std::vector> &volumes_and_instances) { if (! m_valid) @@ -439,6 +630,47 @@ bool Selection::is_sla_compliant() const return true; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO +bool Selection::contains_all_volumes(const std::vector& volume_idxs) const +{ + for (unsigned int i : volume_idxs) + { + if (m_list.find(i) == m_list.end()) + return false; + } + + return true; +} + +bool Selection::contains_any_volume(const std::vector& volume_idxs) const +{ + for (unsigned int i : volume_idxs) + { + if (m_list.find(i) != m_list.end()) + return true; + } + + return false; +} + +bool Selection::matches(const std::vector& volume_idxs) const +{ + unsigned int count = 0; + + for (unsigned int i : volume_idxs) + { + if (m_list.find(i) != m_list.end()) + ++count; + else + return false; + } + + return count == (unsigned int)m_list.size(); +} +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + bool Selection::requires_uniform_scale() const { if (is_single_full_instance() || is_single_modifier() || is_single_volume()) @@ -1253,6 +1485,81 @@ void Selection::paste_from_clipboard() } } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO +std::vector Selection::get_volume_idxs_from_object(unsigned int object_idx) const +{ + std::vector idxs; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + if ((*m_volumes)[i]->object_idx() == object_idx) + idxs.push_back(i); + } + + return idxs; +} + +std::vector Selection::get_volume_idxs_from_instance(unsigned int object_idx, unsigned int instance_idx) const +{ + std::vector idxs; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + const GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) + idxs.push_back(i); + } + + return idxs; +} + +std::vector Selection::get_volume_idxs_from_volume(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx) const +{ + std::vector idxs; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + const GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == object_idx) && (v->volume_idx() == volume_idx)) + { + if ((instance_idx != -1) && (v->instance_idx() == instance_idx)) + idxs.push_back(i); + } + } + + return idxs; +} + +std::vector Selection::get_missing_volume_idxs_from(const std::vector& volume_idxs) const +{ + std::vector idxs; + + for (unsigned int i : m_list) + { + std::vector::const_iterator it = std::find(volume_idxs.begin(), volume_idxs.end(), i); + if (it == volume_idxs.end()) + idxs.push_back(i); + } + + return idxs; +} + +std::vector Selection::get_unselected_volume_idxs_from(const std::vector& volume_idxs) const +{ + std::vector idxs; + + for (unsigned int i : volume_idxs) + { + if (m_list.find(i) == m_list.end()) + idxs.push_back(i); + } + + return idxs; +} +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + void Selection::update_valid() { m_valid = (m_volumes != nullptr) && (m_model != nullptr); @@ -1499,6 +1806,18 @@ void Selection::do_add_volume(unsigned int volume_idx) (*m_volumes)[volume_idx]->selected = true; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO +void Selection::do_add_volumes(const std::vector& volume_idxs) +{ + for (unsigned int i : volume_idxs) + { + if (i < (unsigned int)m_volumes->size()) + do_add_volume(i); + } +} +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::do_add_instance(unsigned int object_idx, unsigned int instance_idx) { for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) @@ -1518,6 +1837,9 @@ void Selection::do_add_object(unsigned int object_idx) do_add_volume(i); } } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::do_remove_volume(unsigned int volume_idx) { diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 915a8c8555..2a96985c35 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -235,7 +235,19 @@ public: void add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection = true); void remove_volume(unsigned int object_idx, unsigned int volume_idx); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + void add_volumes(EMode mode, const std::vector& volume_idxs, bool as_single_selection = true); + void remove_volumes(EMode mode, const std::vector& volume_idxs); +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + void add_all(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + void remove_all(); +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // To be called after Undo or Redo once the volumes are updated. void set_deserialized(EMode mode, const std::vector> &volumes_and_instances); @@ -265,6 +277,17 @@ public: bool is_sla_compliant() const; bool contains_volume(unsigned int volume_idx) const { return m_list.find(volume_idx) != m_list.end(); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + // returns true if the selection contains all the given indices + bool contains_all_volumes(const std::vector& volume_idxs) const; + // returns true if the selection contains at least one of the given indices + bool contains_any_volume(const std::vector& volume_idxs) const; + // returns true if the selection contains all and only the given indices + bool matches(const std::vector& volume_idxs) const; +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + bool requires_uniform_scale() const; // Returns the the object id if the selection is from a single object, otherwise is -1 @@ -314,13 +337,36 @@ public: const Clipboard& get_clipboard() const { return m_clipboard; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + // returns the list of idxs of the volumes contained into the object with the given idx + std::vector get_volume_idxs_from_object(unsigned int object_idx) const; + // returns the list of idxs of the volumes contained into the instance with the given idxs + std::vector get_volume_idxs_from_instance(unsigned int object_idx, unsigned int instance_idx) const; + // returns the idx of the volume corresponding to the volume with the given idxs + std::vector get_volume_idxs_from_volume(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx) const; + // returns the list of idxs of the volumes contained in the selection but not in the given list + std::vector get_missing_volume_idxs_from(const std::vector& volume_idxs) const; + // returns the list of idxs of the volumes contained in the given list but not in the selection + std::vector get_unselected_volume_idxs_from(const std::vector& volume_idxs) const; +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + private: void update_valid(); void update_type(); void set_caches(); void do_add_volume(unsigned int volume_idx); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_SELECTION_UNDO_REDO + void do_add_volumes(const std::vector& volume_idxs); +#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void do_add_instance(unsigned int object_idx, unsigned int instance_idx); void do_add_object(unsigned int object_idx); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#endif // ENABLE_SELECTION_UNDO_REDO +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void do_remove_volume(unsigned int volume_idx); void do_remove_instance(unsigned int object_idx, unsigned int instance_idx); void do_remove_object(unsigned int object_idx); From 0ae46b06358ef878f98a840b6ffe3266304d2e17 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 22 Jul 2019 13:25:17 +0200 Subject: [PATCH 394/627] SLA gizmo and undo/redo: 'autogenerated points' state is now correctly undone/redone --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 40 ++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 2 + 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 8027906a9b..6875a5b885 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -82,7 +82,9 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S editing_mode_reload_cache(); } - if (m_editing_mode_cache.empty() && m_model_object->sla_points_status != sla::PointsStatus::UserModified) + if (m_editing_mode_cache.empty() + && m_model_object->sla_points_status != sla::PointsStatus::UserModified + && m_model_object->sla_points_status != sla::PointsStatus::None) get_data_from_backend(); if (m_state == On) { @@ -1122,9 +1124,13 @@ void GLGizmoSlaSupports::on_start_dragging() void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) { + if (m_editing_mode) + editing_mode_discard_changes(); + ar(m_clipping_plane_distance, m_clipping_plane_normal, - m_current_mesh_object_id + m_current_mesh_object_id, + m_editing_mode_cache ); } @@ -1134,7 +1140,8 @@ void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const { ar(m_clipping_plane_distance, m_clipping_plane_normal, - m_current_mesh_object_id + m_current_mesh_object_id, + m_editing_mode_cache ); } @@ -1177,16 +1184,16 @@ void GLGizmoSlaSupports::editing_mode_discard_changes() // If the points were autogenerated, they may not be on the ModelObject yet. // Because the user probably messed with the cache, we will get the data // from the backend again. - - if (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated) + /*if (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated) { get_data_from_backend(); - else { - m_editing_mode_cache.clear(); - for (const sla::SupportPoint& point : m_model_object->sla_support_points) - m_editing_mode_cache.emplace_back(point, false); - } - m_editing_mode = false; - m_unsaved_changes = false; + } + else + editing_mode_reload_cache();*/ + + m_editing_mode_cache = m_old_cache; + + m_editing_mode = false; + m_unsaved_changes = false; } @@ -1196,7 +1203,13 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() // If there are no changes, don't touch the front-end. The data in the cache could have been // taken from the backend and copying them to ModelObject would needlessly invalidate them. if (m_unsaved_changes) { + // In order to make the undo/redo snapshot, we must first temporarily restore + // the editing cache to the state before the edits were made. + std::vector backup = m_editing_mode_cache; + m_editing_mode_cache = m_old_cache; wxGetApp().plater()->take_snapshot(_(L("Support points edit"))); + m_editing_mode_cache = std::move(backup); + m_model_object->sla_points_status = sla::PointsStatus::UserModified; m_model_object->sla_support_points.clear(); for (const CacheEntry& cache_entry : m_editing_mode_cache) @@ -1216,6 +1229,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() void GLGizmoSlaSupports::editing_mode_reload_cache() { m_editing_mode_cache.clear(); + for (const sla::SupportPoint& point : m_model_object->sla_support_points) m_editing_mode_cache.emplace_back(point, false); @@ -1271,6 +1285,8 @@ void GLGizmoSlaSupports::switch_to_editing_mode() editing_mode_reload_cache(); m_unsaved_changes = false; m_editing_mode = true; + m_old_cache = m_editing_mode_cache; + select_point(NoPoints); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index fbc438f1d6..5ef1e6aea0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -98,6 +98,8 @@ private: float m_new_point_head_diameter; // Size of a new point. float m_minimal_point_distance = 20.f; mutable std::vector m_editing_mode_cache; // a support point and whether it is currently selected + std::vector m_old_cache; // to restore after discarding changes or undo/redo + float m_clipping_plane_distance = 0.f; mutable float m_old_clipping_plane_distance = 0.f; mutable Vec3d m_old_clipping_plane_normal; From 7bc893d99a56efdaab2fa8cfed1cc46b136a6e57 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 23 Jul 2019 16:12:29 +0200 Subject: [PATCH 395/627] Update all extriders nozzle diameters according to the single_extruder_multi_material flag --- src/slic3r/GUI/Tab.cpp | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index b52f9dd463..cb8bb0b6d3 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1892,8 +1892,35 @@ void TabPrinter::build_fff() extruders_count_changed(extruders_count); init_options_list(); // m_options_list should be updated before UI updating update_dirty(); - if (opt_key == "single_extruder_multi_material") // the single_extruder_multimaterial was added to force pages + if (opt_key == "single_extruder_multi_material") { // the single_extruder_multimaterial was added to force pages on_value_change(opt_key, value); // rebuild - let's make sure the on_value_change is not skipped + + if (boost::any_cast(value) && m_extruders_count > 1) { + SuppressBackgroundProcessingUpdate sbpu; + std::vector nozzle_diameters = static_cast(m_config->option("nozzle_diameter"))->values; + const double frst_diam = nozzle_diameters[0]; + + for (auto cur_diam : nozzle_diameters) { + // if value is differs from first nozzle diameter value + if (fabs(cur_diam - frst_diam) > EPSILON) { + const wxString msg_text = _(L("Single Extruder Multi Material is selected, \n" + "and all extruders must have the same diameter.\n" + "Do you want to change the diameter for all extruders to first extruder nozzle diameter value?")); + auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); + + if (dialog->ShowModal() == wxID_YES) { + DynamicPrintConfig new_conf = *m_config; + for (size_t i = 1; i < nozzle_diameters.size(); i++) + nozzle_diameters[i] = frst_diam; + + new_conf.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameters)); + load_config(new_conf); + } + break; + } + } + } + } } else { update_dirty(); @@ -2318,7 +2345,7 @@ void TabPrinter::build_unregular_pages() optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value) { - if (m_extruders_count > 1 && opt_key.find_first_of("nozzle_diameter") != std::string::npos) + if (m_config->opt_bool("single_extruder_multi_material") && m_extruders_count > 1 && opt_key.find_first_of("nozzle_diameter") != std::string::npos) { SuppressBackgroundProcessingUpdate sbpu; const double new_nd = boost::any_cast(value); From 4245b61afc5ce4edf746efa96e317b1efc6c47eb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 23 Jul 2019 16:17:37 +0200 Subject: [PATCH 396/627] Added SETTINGS_SELECTED_ON_SIDEBAR flag to undo/redo --- src/slic3r/GUI/GUI_ObjectList.cpp | 35 ++++++++++++++++++++++++------- src/slic3r/GUI/GUI_ObjectList.hpp | 6 +++++- src/slic3r/GUI/Plater.cpp | 8 +++++++ src/slic3r/Utils/UndoRedo.hpp | 1 + 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a2ddba376a..cc7c6c888c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2638,7 +2638,8 @@ void ObjectList::update_selections() const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); wxDataViewItemArray sels; - m_selection_mode = smInstance; + if (m_selection_mode & smSettings == 0) + m_selection_mode = smInstance; // We doesn't update selection if SettingsItem for the current object/part is selected // if (GetSelectedItemsCount() == 1 && m_objects_model->GetItemType(GetSelection()) == itSettings ) @@ -2664,6 +2665,12 @@ void ObjectList::update_selections() else if (selection.is_single_full_object() || selection.is_multiple_full_object()) { const Selection::ObjectIdxsToInstanceIdxsMap& objects_content = selection.get_content(); + if (m_selection_mode & smSettings) + { + wxDataViewItem obj_item = m_objects_model->GetItemById(objects_content.begin()->first); + sels.Add(m_objects_model->GetSettingsItem(obj_item)); + } + else { for (const auto& object : objects_content) { if (object.second.size() == 1) // object with 1 instance sels.Add(m_objects_model->GetItemById(object.first)); @@ -2688,10 +2695,23 @@ void ObjectList::update_selections() for (const auto& inst : instances) sels.Add(m_objects_model->GetItemByInstanceId(object.first, inst)); } - } + } } } else if (selection.is_any_volume() || selection.is_any_modifier()) { + if (m_selection_mode & smSettings) + { + const auto idx = *selection.get_volume_idxs().begin(); + const auto gl_vol = selection.get_volume(idx); + if (gl_vol->volume_idx() >= 0) { + // Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids + // are not associated with ModelVolumes, but they are temporarily generated by the backend + // (for example, SLA supports or SLA pad). + wxDataViewItem vol_item = m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx()); + sels.Add(m_objects_model->GetSettingsItem(vol_item)); + } + } + else { for (auto idx : selection.get_volume_idxs()) { const auto gl_vol = selection.get_volume(idx); if (gl_vol->volume_idx() >= 0) @@ -2700,7 +2720,7 @@ void ObjectList::update_selections() // (for example, SLA supports or SLA pad). sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx())); } - m_selection_mode = smVolume; + m_selection_mode = smVolume; } } else if (selection.is_single_full_instance() || selection.is_multiple_full_instance()) { @@ -2744,7 +2764,7 @@ void ObjectList::update_selections() } } - if (sels.size() == 0) + if (sels.size() == 0 || m_selection_mode & smSettings) m_selection_mode = smUndef; select_items(sels); @@ -3522,15 +3542,16 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const void ObjectList::update_after_undo_redo() { - m_prevent_list_events = true; m_prevent_canvas_selection_update = true; Plater::SuppressSnapshots suppress(wxGetApp().plater()); // Unselect all objects before deleting them, so that no change of selection is emitted during deletion. - this->UnselectAll(); + unselect_objects();//this->UnselectAll(); m_objects_model->DeleteAll(); +// m_prevent_list_events = true; + size_t obj_idx = 0; while (obj_idx < m_objects->size()) { add_object_to_list(obj_idx, false); @@ -3544,7 +3565,7 @@ void ObjectList::update_after_undo_redo() update_selections(); m_prevent_canvas_selection_update = false; - m_prevent_list_events = false; +// m_prevent_list_events = false; } ModelObject* ObjectList::object(const int obj_idx) const diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 5f5d5d6253..c3fb5ba804 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -66,14 +66,17 @@ struct ItemForDelete class ObjectList : public wxDataViewCtrl { +public: enum SELECTION_MODE { smUndef = 0, smVolume = 1, smInstance = 2, - smLayer = 4 + smLayer = 4, + smSettings = 8, // used for undo/redo } m_selection_mode {smUndef}; +private: struct dragged_item_data { void init(const int obj_idx, const int subobj_idx, const ItemType type) { @@ -302,6 +305,7 @@ public: void init_objects(); bool multiple_selection() const ; bool is_selected(const ItemType type) const; + void set_selection_mode(SELECTION_MODE mode) { m_selection_mode = mode; } void update_selections(); void update_selections_on_canvas(); void select_item(const wxDataViewItem& item); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 9e818adfa1..f615e49581 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3729,6 +3729,8 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) snapshot_data.printer_technology = this->printer_technology; if (this->view3D->is_layers_editing_enabled()) snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; + if (this->sidebar->obj_list()->is_selected(itSettings)) + snapshot_data.flags |= UndoRedo::SnapshotData::SETTINGS_SELECTED_ON_SIDEBAR; //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config. // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. if (this->printer_technology == ptFFF) { @@ -3795,7 +3797,10 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator top_snapshot_data.printer_technology = this->printer_technology; if (this->view3D->is_layers_editing_enabled()) top_snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; + if (this->sidebar->obj_list()->is_selected(itSettings)) + top_snapshot_data.flags |= UndoRedo::SnapshotData::SETTINGS_SELECTED_ON_SIDEBAR; bool new_variable_layer_editing_active = (new_flags & UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE) != 0; + bool new_settings_selected_on_sidebar = (new_flags & UndoRedo::SnapshotData::SETTINGS_SELECTED_ON_SIDEBAR) != 0; // Disable layer editing before the Undo / Redo jump. if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); @@ -3828,6 +3833,9 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator tab_print->update_dirty(); } } + // set settings mode for ObjectList on sidebar + if (new_settings_selected_on_sidebar) + this->sidebar->obj_list()->set_selection_mode(ObjectList::SELECTION_MODE::smSettings); this->update_after_undo_redo(temp_snapshot_was_taken); // Enable layer editing after the Undo / Redo jump. if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index c178025601..a3a130efdc 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -38,6 +38,7 @@ struct SnapshotData // Bitmask of various binary flags to be stored with the snapshot. enum Flags { VARIABLE_LAYER_EDITING_ACTIVE = 1, + SETTINGS_SELECTED_ON_SIDEBAR = 2, }; }; From 6ea3a8e2b46b52c8719862f7b31a8633bf53c3ce Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 23 Jul 2019 17:15:34 +0200 Subject: [PATCH 397/627] WIP: Nullable config values. Fixed compare operator for float vectors. --- src/libslic3r/Config.hpp | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index d8759c6ebe..e6953739a1 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -188,6 +188,8 @@ public: virtual size_t size() const = 0; // Is this vector empty? virtual bool empty() const = 0; + // Is the value nil? That should only be possible if this->nullable(). + virtual bool is_nil(size_t idx) const = 0; protected: // Used to verify type compatibility when assigning to / from a scalar ConfigOption. @@ -363,14 +365,20 @@ public: static ConfigOptionType static_type() { return coFloats; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionFloatsTempl(*this); } - bool operator==(const ConfigOptionFloatsTempl &rhs) const { return this->values == rhs.values; } + bool operator==(const ConfigOptionFloatsTempl &rhs) const { vectors_equal(this->values, rhs.values); } + bool operator==(const ConfigOption &rhs) const override { + if (rhs.type() != this->type()) + throw std::runtime_error("ConfigOptionFloatsTempl: Comparing incompatible types"); + assert(dynamic_cast*>(&rhs)); + return vectors_equal(this->values, static_cast*>(&rhs)->values); + } // Could a special "nil" value be stored inside the vector, indicating undefined value? bool nullable() const override { return NULLABLE; } // Special "nil" value to be stored into the vector if this->supports_nil(). static double nil_value() { return std::numeric_limits::quiet_NaN(); } // A scalar is nil, or all values of a vector are nil. - virtual bool is_nil() const override { for (auto v : this->values) if (! std::isnan(v)) return false; return true; } - bool is_nil(size_t idx) const { return std::isnan(v->values[idx]); } + bool is_nil() const override { for (auto v : this->values) if (! std::isnan(v)) return false; return true; } + bool is_nil(size_t idx) const override { return std::isnan(v->values[idx]); } std::string serialize() const override { @@ -436,6 +444,18 @@ protected: } else std::runtime_error("Serializing invalid number"); } + static bool vectors_equal(const std::vector &v1, const std::vector &v2) { + if (NULLABLE) { + if (v1.size() != v2.size()) + return false; + for (auto it1 = v1.begin(), it2 = v2.begin(); it1 != v1.end(); ++ it1, ++ it2) + if (! ((std::isnan(*it1) && std::isnan(*it2)) || *it1 == *it2)) + return false; + return true; + } else + // Not supporting nullable values, the default vector compare is cheaper. + return v1 == v2; + } private: friend class cereal::access; @@ -503,8 +523,8 @@ public: // Special "nil" value to be stored into the vector if this->supports_nil(). static int nil_value() { return std::numeric_limits::max(); } // A scalar is nil, or all values of a vector are nil. - virtual bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } - bool is_nil(size_t idx) const { return v->values[idx] == nil_value(); } + bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } + bool is_nil(size_t idx) const override { return v->values[idx] == nil_value(); } std::string serialize() const override { @@ -973,8 +993,8 @@ public: // Special "nil" value to be stored into the vector if this->supports_nil(). static unsigned char nil_value() { return std::numeric_limits::max(); } // A scalar is nil, or all values of a vector are nil. - virtual bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } - bool is_nil(size_t idx) const { return v->values[idx] == nil_value(); } + bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } + bool is_nil(size_t idx) const override { return v->values[idx] == nil_value(); } bool& get_at(size_t i) { assert(! this->values.empty()); From 804e1b19397b5a1d05e4aa99171911277b649c8f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 23 Jul 2019 17:09:19 +0200 Subject: [PATCH 398/627] Fixed ObjectList::last_volume_is_deleted() to avoid clearing volume[0].config after undo/redo --- src/slic3r/GUI/GUI_ObjectList.cpp | 4 ++-- src/slic3r/GUI/wxExtensions.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index cc7c6c888c..047b7e8ed6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1741,7 +1741,7 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) ItemType type; m_objects_model->GetItemInfo(item, type, obj_idx, idx); - if (type & itUndef) + if (type == itUndef) return; if (type & itSettings) @@ -3101,7 +3101,7 @@ void ObjectList::change_part_type() void ObjectList::last_volume_is_deleted(const int obj_idx) { - if (obj_idx < 0 || obj_idx >= m_objects->size() || (*m_objects)[obj_idx]->volumes.empty()) + if (obj_idx < 0 || obj_idx >= m_objects->size() || (*m_objects)[obj_idx]->volumes.size() != 1) return; auto volume = (*m_objects)[obj_idx]->volumes.front(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 6300ada318..3fc404864a 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1508,7 +1508,7 @@ ItemType ObjectDataViewModel::GetItemType(const wxDataViewItem &item) const if (!item.IsOk()) return itUndef; ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); - return node->m_type; + return node->m_type < 0 ? itUndef : node->m_type; } wxDataViewItem ObjectDataViewModel::GetItemByType(const wxDataViewItem &parent_item, ItemType type) const From 18965f5c222781e9a7a51568b45aa3a5c99bb377 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 24 Jul 2019 09:47:01 +0200 Subject: [PATCH 399/627] Do not store into undo/redo stack remove all from selection command --- src/slic3r/GUI/Selection.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 4dfe445cb5..f3b85b7b29 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -425,7 +425,6 @@ void Selection::add_volumes(EMode mode, const std::vector& volume_ if (i < (unsigned int)m_volumes->size()) do_add_volume(i); } -// do_add_volumes(volume_idxs); update_type(); this->set_bounding_boxes_dirty(); @@ -495,7 +494,9 @@ void Selection::remove_all() if (is_empty()) return; - wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_all()"))); +//##################################################################################################################################################################################### +// wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_all()"))); +//##################################################################################################################################################################################### m_mode = Instance; clear(); From 7e7550b416f8c14996ac9fb58b410483ac4a3d97 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 24 Jul 2019 10:11:17 +0200 Subject: [PATCH 400/627] Custom bed texture applied to custom bed shapes --- src/slic3r/GUI/3DBed.cpp | 144 +++++++++++++++++++++++++++++----- src/slic3r/GUI/3DBed.hpp | 9 ++- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- 3 files changed, 129 insertions(+), 26 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 89b7caedfb..041c555e08 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -250,7 +250,7 @@ Point Bed3D::point_projection(const Point& point) const return m_polygon.point_projection(point); } -void Bed3D::render(GLCanvas3D* canvas, float theta, float scale_factor) const +void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor) const { m_scale_factor = scale_factor; @@ -274,7 +274,7 @@ void Bed3D::render(GLCanvas3D* canvas, float theta, float scale_factor) const default: case Custom: { - render_custom(); + render_custom(canvas, theta > 90.0f); break; } } @@ -309,7 +309,7 @@ void Bed3D::calc_triangles(const ExPolygon& poly) Polygons triangles; poly.triangulate(&triangles); - if (!m_triangles.set_from_triangles(triangles, GROUND_Z, m_type != Custom)) + if (!m_triangles.set_from_triangles(triangles, GROUND_Z, true)) printf("Unable to create bed triangles\n"); } @@ -381,27 +381,26 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const return type; } -void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom) const +void Bed3D::render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const { std::string filename = !m_custom_texture.empty() ? m_custom_texture : resources_dir() + "/icons/bed/" + key + ".svg"; std::string model_path = resources_dir() + "/models/" + key; - // use higher resolution images if graphic card and opengl version allow - GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); - if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) { m_texture.reset(); if (boost::algorithm::iends_with(filename, ".svg")) { + // use higher resolution images if graphic card and opengl version allow + GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != filename)) { // generate a temporary lower resolution texture to show while no main texture levels have been compressed if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8)) { - render_custom(); + render_default(); return; } } @@ -409,7 +408,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom // starts generating the main texture, compression will run asynchronously if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size)) { - render_custom(); + render_default(); return; } } @@ -420,7 +419,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom { if (!m_temp_texture.load_from_file(filename, false, GLTexture::None, false)) { - render_custom(); + render_default(); return; } } @@ -428,13 +427,13 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom // starts generating the main texture, compression will run asynchronously if (!m_texture.load_from_file(filename, true, GLTexture::MultiThreaded, true)) { - render_custom(); + render_default(); return; } } else { - render_custom(); + render_default(); return; } } @@ -451,9 +450,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom } else if (m_requires_canvas_update && m_texture.all_compressed_data_sent_to_gpu()) { - if (canvas != nullptr) - canvas->stop_keeping_dirty(); - + canvas.stop_keeping_dirty(); m_requires_canvas_update = false; } @@ -506,7 +503,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom if (bottom) glsafe(::glFrontFace(GL_CW)); - render_prusa_shader(bottom); + render_texture(bottom); if (bottom) glsafe(::glFrontFace(GL_CCW)); @@ -516,7 +513,7 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom } } -void Bed3D::render_prusa_shader(bool transparent) const +void Bed3D::render_texture(bool transparent) const { if (m_shader.get_shader_program_id() == 0) m_shader.init("printbed.vs", "printbed.fs"); @@ -566,7 +563,114 @@ void Bed3D::render_prusa_shader(bool transparent) const } } -void Bed3D::render_custom() const +void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const +{ + if (m_custom_texture.empty()) + { + render_default(); + return; + } + else + { + if ((m_texture.get_id() == 0) || (m_texture.get_source() != m_custom_texture)) + { + m_texture.reset(); + + if (boost::algorithm::iends_with(m_custom_texture, ".svg")) + { + // use higher resolution images if graphic card and opengl version allow + GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); + if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_custom_texture)) + { + // generate a temporary lower resolution texture to show while no main texture levels have been compressed + if (!m_temp_texture.load_from_svg_file(m_custom_texture, false, false, false, max_tex_size / 8)) + { + render_default(); + return; + } + } + + // starts generating the main texture, compression will run asynchronously + if (!m_texture.load_from_svg_file(m_custom_texture, true, true, true, max_tex_size)) + { + render_default(); + return; + } + } + else if (boost::algorithm::iends_with(m_custom_texture, ".png")) + { + // generate a temporary lower resolution texture to show while no main texture levels have been compressed + if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_custom_texture)) + { + if (!m_temp_texture.load_from_file(m_custom_texture, false, GLTexture::None, false)) + { + render_default(); + return; + } + } + + // starts generating the main texture, compression will run asynchronously + if (!m_texture.load_from_file(m_custom_texture, true, GLTexture::MultiThreaded, true)) + { + render_default(); + return; + } + } + else + { + render_default(); + return; + } + } + else if (m_texture.unsent_compressed_data_available()) + { + // sends to gpu the already available compressed levels of the main texture + m_texture.send_compressed_data_to_gpu(); + + // the temporary texture is not needed anymore, reset it + if (m_temp_texture.get_id() != 0) + m_temp_texture.reset(); + + m_requires_canvas_update = true; + } + else if (m_requires_canvas_update && m_texture.all_compressed_data_sent_to_gpu()) + { + canvas.stop_keeping_dirty(); + m_requires_canvas_update = false; + } + } + + unsigned int triangles_vcount = m_triangles.get_vertices_count(); + if (triangles_vcount > 0) + { + if (m_vbo_id == 0) + { + glsafe(::glGenBuffers(1, &m_vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDepthMask(GL_FALSE)); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + if (bottom) + glsafe(::glFrontFace(GL_CW)); + + render_texture(bottom); + + if (bottom) + glsafe(::glFrontFace(GL_CCW)); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glDepthMask(GL_TRUE)); + } +} + +void Bed3D::render_default() const { m_texture.reset(); @@ -587,14 +691,12 @@ void Bed3D::render_custom() const glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); // draw grid - unsigned int gridlines_vcount = m_gridlines.get_vertices_count(); - // we need depth test for grid, otherwise it would disappear when looking the object from below glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glLineWidth(3.0f * m_scale_factor)); glsafe(::glColor4f(0.2f, 0.2f, 0.2f, 0.4f)); glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data())); - glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)gridlines_vcount)); + glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines.get_vertices_count())); glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 7a89444439..9a03bab77e 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -109,7 +109,7 @@ public: bool contains(const Point& point) const; Point point_projection(const Point& point) const; - void render(GLCanvas3D* canvas, float theta, float scale_factor) const; + void render(GLCanvas3D& canvas, float theta, float scale_factor) const; void render_axes() const; private: @@ -117,9 +117,10 @@ private: void calc_triangles(const ExPolygon& poly); void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); EType detect_type(const Pointfs& shape) const; - void render_prusa(GLCanvas3D* canvas, const std::string& key, bool bottom) const; - void render_prusa_shader(bool transparent) const; - void render_custom() const; + void render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const; + void render_texture(bool transparent) const; + void render_custom(GLCanvas3D& canvas, bool bottom) const; + void render_default() const; void reset(); }; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 540b1bc8dd..aa4f062661 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3919,7 +3919,7 @@ void GLCanvas3D::_render_bed(float theta) const #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); #endif // ENABLE_RETINA_GL - m_bed.render(const_cast(this), theta, scale_factor); + m_bed.render(const_cast(*this), theta, scale_factor); } void GLCanvas3D::_render_axes() const From 395e794b9e6bc2d429caa71089a447773b51c96e Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 24 Jul 2019 11:04:04 +0200 Subject: [PATCH 401/627] Refactoring in Bed3D --- src/slic3r/GUI/3DBed.cpp | 302 ++++++++++++++------------------------- src/slic3r/GUI/3DBed.hpp | 2 +- 2 files changed, 105 insertions(+), 199 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 041c555e08..a41b39a7ee 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -383,9 +383,46 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const void Bed3D::render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const { - std::string filename = !m_custom_texture.empty() ? m_custom_texture : resources_dir() + "/icons/bed/" + key + ".svg"; + if (!bottom) + { + std::string filename = resources_dir() + "/models/" + key + "_bed.stl"; + if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) + { + Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); + if (key == "mk2") + // hardcoded value to match the stl model + offset += Vec3d(0.0, 7.5, -0.03); + else if (key == "mk3") + // hardcoded value to match the stl model + offset += Vec3d(0.0, 5.5, 2.43); + else if (key == "sl1") + // hardcoded value to match the stl model + offset += Vec3d(0.0, 0.0, -0.03); - std::string model_path = resources_dir() + "/models/" + key; + m_model.center_around(offset); + + // update extended bounding box + calc_bounding_boxes(); + } + + if (!m_model.get_filename().empty()) + { + glsafe(::glEnable(GL_LIGHTING)); + m_model.render(); + glsafe(::glDisable(GL_LIGHTING)); + } + } + + render_texture(m_custom_texture.empty() ? resources_dir() + "/icons/bed/" + key + ".svg" : m_custom_texture, bottom, canvas); +} + +void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& canvas) const +{ + if (filename.empty()) + { + m_texture.reset(); + return; + } if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) { @@ -454,112 +491,77 @@ void Bed3D::render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom m_requires_canvas_update = false; } - if (!bottom) + if (m_triangles.get_vertices_count() > 0) { - filename = model_path + "_bed.stl"; - if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { - Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); - if (key == "mk2") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 7.5, -0.03); - else if (key == "mk3") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 5.5, 2.43); - else if (key == "sl1") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 0.0, -0.03); + if (m_shader.get_shader_program_id() == 0) + m_shader.init("printbed.vs", "printbed.fs"); - m_model.center_around(offset); - - // update extended bounding box - calc_bounding_boxes(); - } - - if (!m_model.get_filename().empty()) + if (m_shader.is_initialized()) { - glsafe(::glEnable(GL_LIGHTING)); - m_model.render(); - glsafe(::glDisable(GL_LIGHTING)); - } - } + m_shader.start_using(); + m_shader.set_uniform("transparent_background", bottom); + m_shader.set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - if (triangles_vcount > 0) - { - if (m_vbo_id == 0) - { - glsafe(::glGenBuffers(1, &m_vbo_id)); + if (m_vbo_id == 0) + { + glsafe(::glGenBuffers(1, &m_vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDepthMask(GL_FALSE)); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + + if (bottom) + glsafe(::glFrontFace(GL_CW)); + + unsigned int stride = m_triangles.get_vertex_data_size(); + + GLint position_id = m_shader.get_attrib_location("v_position"); + GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords"); + + // show the temporary texture while no compressed data is available + GLuint tex_id = (GLuint)m_temp_texture.get_id(); + if (tex_id == 0) + tex_id = (GLuint)m_texture.get_id(); + + glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW)); + + if (position_id != -1) + { + glsafe(::glEnableVertexAttribArray(position_id)); + glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset())); + } + if (tex_coords_id != -1) + { + glsafe(::glEnableVertexAttribArray(tex_coords_id)); + glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset())); + } + + glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count())); + + if (tex_coords_id != -1) + glsafe(::glDisableVertexAttribArray(tex_coords_id)); + + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + + if (bottom) + glsafe(::glFrontFace(GL_CCW)); + + glsafe(::glDisable(GL_BLEND)); + glsafe(::glDepthMask(GL_TRUE)); + + m_shader.stop_using(); } - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDepthMask(GL_FALSE)); - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - if (bottom) - glsafe(::glFrontFace(GL_CW)); - - render_texture(bottom); - - if (bottom) - glsafe(::glFrontFace(GL_CCW)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glDepthMask(GL_TRUE)); - } -} - -void Bed3D::render_texture(bool transparent) const -{ - if (m_shader.get_shader_program_id() == 0) - m_shader.init("printbed.vs", "printbed.fs"); - - if (m_shader.is_initialized()) - { - m_shader.start_using(); - m_shader.set_uniform("transparent_background", transparent); - m_shader.set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg")); - - unsigned int stride = m_triangles.get_vertex_data_size(); - - GLint position_id = m_shader.get_attrib_location("v_position"); - GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords"); - - // show the temporary texture while no compressed data is available - GLuint tex_id = (GLuint)m_temp_texture.get_id(); - if (tex_id == 0) - tex_id = (GLuint)m_texture.get_id(); - - glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); - - if (position_id != -1) - { - glsafe(::glEnableVertexAttribArray(position_id)); - glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset())); - } - if (tex_coords_id != -1) - { - glsafe(::glEnableVertexAttribArray(tex_coords_id)); - glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset())); - } - - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count())); - - if (tex_coords_id != -1) - glsafe(::glDisableVertexAttribArray(tex_coords_id)); - - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - - m_shader.stop_using(); } } @@ -570,104 +572,8 @@ void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const render_default(); return; } - else - { - if ((m_texture.get_id() == 0) || (m_texture.get_source() != m_custom_texture)) - { - m_texture.reset(); - if (boost::algorithm::iends_with(m_custom_texture, ".svg")) - { - // use higher resolution images if graphic card and opengl version allow - GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); - if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_custom_texture)) - { - // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if (!m_temp_texture.load_from_svg_file(m_custom_texture, false, false, false, max_tex_size / 8)) - { - render_default(); - return; - } - } - - // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_svg_file(m_custom_texture, true, true, true, max_tex_size)) - { - render_default(); - return; - } - } - else if (boost::algorithm::iends_with(m_custom_texture, ".png")) - { - // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_custom_texture)) - { - if (!m_temp_texture.load_from_file(m_custom_texture, false, GLTexture::None, false)) - { - render_default(); - return; - } - } - - // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_file(m_custom_texture, true, GLTexture::MultiThreaded, true)) - { - render_default(); - return; - } - } - else - { - render_default(); - return; - } - } - else if (m_texture.unsent_compressed_data_available()) - { - // sends to gpu the already available compressed levels of the main texture - m_texture.send_compressed_data_to_gpu(); - - // the temporary texture is not needed anymore, reset it - if (m_temp_texture.get_id() != 0) - m_temp_texture.reset(); - - m_requires_canvas_update = true; - } - else if (m_requires_canvas_update && m_texture.all_compressed_data_sent_to_gpu()) - { - canvas.stop_keeping_dirty(); - m_requires_canvas_update = false; - } - } - - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - if (triangles_vcount > 0) - { - if (m_vbo_id == 0) - { - glsafe(::glGenBuffers(1, &m_vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDepthMask(GL_FALSE)); - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - - if (bottom) - glsafe(::glFrontFace(GL_CW)); - - render_texture(bottom); - - if (bottom) - glsafe(::glFrontFace(GL_CCW)); - - glsafe(::glDisable(GL_BLEND)); - glsafe(::glDepthMask(GL_TRUE)); - } + render_texture(m_custom_texture, bottom, canvas); } void Bed3D::render_default() const diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 9a03bab77e..71b319b0ab 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -118,7 +118,7 @@ private: void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); EType detect_type(const Pointfs& shape) const; void render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const; - void render_texture(bool transparent) const; + void render_texture(const std::string& filename, bool bottom, GLCanvas3D& canvas) const; void render_custom(GLCanvas3D& canvas, bool bottom) const; void render_default() const; void reset(); From 0592ae65eeff51038aac647c68b009b1314c765c Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 23 Jul 2019 18:17:57 +0200 Subject: [PATCH 402/627] Completed setting of the item focus in ObjectList after undo/redo --- src/slic3r/GUI/GUI_ObjectList.cpp | 40 ++++++++++++++++++++++++------ src/slic3r/GUI/GUI_ObjectList.hpp | 10 ++++++-- src/slic3r/GUI/Plater.cpp | 41 +++++++++++++++++++++++++------ src/slic3r/Utils/UndoRedo.cpp | 2 +- src/slic3r/Utils/UndoRedo.hpp | 9 ++++++- 5 files changed, 82 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 047b7e8ed6..14c6db6541 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2633,12 +2633,28 @@ bool ObjectList::is_selected(const ItemType type) const return false; } +t_layer_height_range ObjectList::get_selected_layers_range() const +{ + t_layer_height_range layer_range = { 0.0, 0.0 }; + const wxDataViewItem& item = GetSelection(); + if (!item) + return layer_range; + + const ItemType type = m_objects_model->GetItemType(item); + if (type & itSettings && m_objects_model->GetItemType(m_objects_model->GetParent(item)) != itLayer) + return layer_range; + + return type & itLayer ? m_objects_model->GetLayerRangeByItem(item) : + type & itSettings ? m_objects_model->GetLayerRangeByItem(m_objects_model->GetParent(item)) : + layer_range; +} + void ObjectList::update_selections() { const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); wxDataViewItemArray sels; - if (m_selection_mode & smSettings == 0) + if ( ( m_selection_mode & (smSettings|smLayer|smLayerRoot) ) == 0) m_selection_mode = smInstance; // We doesn't update selection if SettingsItem for the current object/part is selected @@ -2665,10 +2681,21 @@ void ObjectList::update_selections() else if (selection.is_single_full_object() || selection.is_multiple_full_object()) { const Selection::ObjectIdxsToInstanceIdxsMap& objects_content = selection.get_content(); - if (m_selection_mode & smSettings) + if (m_selection_mode & (smSettings | smLayer | smLayerRoot)) { - wxDataViewItem obj_item = m_objects_model->GetItemById(objects_content.begin()->first); - sels.Add(m_objects_model->GetSettingsItem(obj_item)); + auto obj_idx = objects_content.begin()->first; + wxDataViewItem obj_item = m_objects_model->GetItemById(obj_idx); + if (m_selection_mode & smSettings) + { + if (m_selected_layers_range.first <= EPSILON && m_selected_layers_range.second <= EPSILON) + sels.Add(m_objects_model->GetSettingsItem(obj_item)); + else + sels.Add(m_objects_model->GetSettingsItem(m_objects_model->GetItemByLayerRange(obj_idx, m_selected_layers_range))); + } + else if (m_selection_mode & smLayerRoot) + sels.Add(m_objects_model->GetLayerRootItem(obj_item)); + else if (m_selection_mode & smLayer) + sels.Add(m_objects_model->GetItemByLayerRange(obj_idx, m_selected_layers_range)); } else { for (const auto& object : objects_content) { @@ -3550,8 +3577,6 @@ void ObjectList::update_after_undo_redo() unselect_objects();//this->UnselectAll(); m_objects_model->DeleteAll(); -// m_prevent_list_events = true; - size_t obj_idx = 0; while (obj_idx < m_objects->size()) { add_object_to_list(obj_idx, false); @@ -3559,13 +3584,12 @@ void ObjectList::update_after_undo_redo() } #ifndef __WXOSX__ - selection_changed(); +// selection_changed(); #endif /* __WXOSX__ */ update_selections(); m_prevent_canvas_selection_update = false; -// m_prevent_list_events = false; } ModelObject* ObjectList::object(const int obj_idx) const diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index c3fb5ba804..1c30a4d974 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -73,10 +73,14 @@ public: smVolume = 1, smInstance = 2, smLayer = 4, - smSettings = 8, // used for undo/redo - } m_selection_mode {smUndef}; + smSettings = 8, // used for undo/redo + smLayerRoot = 16, // used for undo/redo + }; private: + SELECTION_MODE m_selection_mode {smUndef}; + t_layer_height_range m_selected_layers_range; + struct dragged_item_data { void init(const int obj_idx, const int subobj_idx, const ItemType type) { @@ -305,6 +309,8 @@ public: void init_objects(); bool multiple_selection() const ; bool is_selected(const ItemType type) const; + t_layer_height_range get_selected_layers_range() const; + void set_selected_layers_range(const t_layer_height_range range) { m_selected_layers_range = range; } void set_selection_mode(SELECTION_MODE mode) { m_selection_mode = mode; } void update_selections(); void update_selections_on_canvas(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7653a5833a..3d1e7b97ce 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3729,8 +3729,16 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) snapshot_data.printer_technology = this->printer_technology; if (this->view3D->is_layers_editing_enabled()) snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; - if (this->sidebar->obj_list()->is_selected(itSettings)) - snapshot_data.flags |= UndoRedo::SnapshotData::SETTINGS_SELECTED_ON_SIDEBAR; + if (this->sidebar->obj_list()->is_selected(itSettings)) { + snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR; + snapshot_data.layer_range = this->sidebar->obj_list()->get_selected_layers_range(); + } + else if (this->sidebar->obj_list()->is_selected(itLayer)) { + snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR; + snapshot_data.layer_range = this->sidebar->obj_list()->get_selected_layers_range(); + } + else if (this->sidebar->obj_list()->is_selected(itLayerRoot)) + snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR; //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config. // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. if (this->printer_technology == ptFFF) { @@ -3797,10 +3805,22 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator top_snapshot_data.printer_technology = this->printer_technology; if (this->view3D->is_layers_editing_enabled()) top_snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; - if (this->sidebar->obj_list()->is_selected(itSettings)) - top_snapshot_data.flags |= UndoRedo::SnapshotData::SETTINGS_SELECTED_ON_SIDEBAR; + if (this->sidebar->obj_list()->is_selected(itSettings)) { + top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR; + top_snapshot_data.layer_range = this->sidebar->obj_list()->get_selected_layers_range(); + } + else if (this->sidebar->obj_list()->is_selected(itLayer)) { + top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR; + top_snapshot_data.layer_range = this->sidebar->obj_list()->get_selected_layers_range(); + } + else if (this->sidebar->obj_list()->is_selected(itLayerRoot)) + top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR; bool new_variable_layer_editing_active = (new_flags & UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE) != 0; - bool new_settings_selected_on_sidebar = (new_flags & UndoRedo::SnapshotData::SETTINGS_SELECTED_ON_SIDEBAR) != 0; + bool new_selected_settings_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR) != 0; + bool new_selected_layer_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR) != 0; + bool new_selected_layerroot_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR) != 0; + + t_layer_height_range layer_range = it_snapshot->snapshot_data.layer_range; // Disable layer editing before the Undo / Redo jump. if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); @@ -3833,9 +3853,14 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator tab_print->update_dirty(); } } - // set settings mode for ObjectList on sidebar - if (new_settings_selected_on_sidebar) - this->sidebar->obj_list()->set_selection_mode(ObjectList::SELECTION_MODE::smSettings); + // set selection mode for ObjectList on sidebar + this->sidebar->obj_list()->set_selection_mode(new_selected_settings_on_sidebar ? ObjectList::SELECTION_MODE::smSettings : + new_selected_layer_on_sidebar ? ObjectList::SELECTION_MODE::smLayer : + new_selected_layerroot_on_sidebar ? ObjectList::SELECTION_MODE::smLayerRoot : + ObjectList::SELECTION_MODE::smUndef); + if (new_selected_settings_on_sidebar || new_selected_layer_on_sidebar) + this->sidebar->obj_list()->set_selected_layers_range(/*top_snapshot_data.*/layer_range); + this->update_after_undo_redo(temp_snapshot_was_taken); // Enable layer editing after the Undo / Redo jump. if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index eba839ffb1..9b12be00eb 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -36,7 +36,7 @@ namespace Slic3r { namespace UndoRedo { -SnapshotData::SnapshotData() : printer_technology(ptUnknown), flags(0) +SnapshotData::SnapshotData() : printer_technology(ptUnknown), flags(0), layer_range({0.0, 0.0}) { } diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index a3a130efdc..85ad11b828 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -9,6 +9,9 @@ #include +typedef double coordf_t; +typedef std::pair t_layer_height_range; + namespace Slic3r { class Model; @@ -38,8 +41,12 @@ struct SnapshotData // Bitmask of various binary flags to be stored with the snapshot. enum Flags { VARIABLE_LAYER_EDITING_ACTIVE = 1, - SETTINGS_SELECTED_ON_SIDEBAR = 2, + SELECTED_SETTINGS_ON_SIDEBAR = 2, + SELECTED_LAYERROOT_ON_SIDEBAR = 4, + SELECTED_LAYER_ON_SIDEBAR = 8, }; + + t_layer_height_range layer_range; }; struct Snapshot From 1a3fc0994b64c2a8939bf91ff54c1160da6e6358 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 24 Jul 2019 12:32:38 +0200 Subject: [PATCH 403/627] Fix for https://github.com/prusa3d/PrusaSlicer/commit/0592ae65eeff51038aac647c68b009b1314c765c --- src/slic3r/GUI/GUI_ObjectList.cpp | 17 +++++++---------- src/slic3r/GUI/GUI_ObjectList.hpp | 8 ++++---- src/slic3r/GUI/Plater.cpp | 11 +++++------ src/slic3r/Utils/UndoRedo.cpp | 2 +- src/slic3r/Utils/UndoRedo.hpp | 3 +-- 5 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 14c6db6541..8d5c3fda98 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2633,20 +2633,17 @@ bool ObjectList::is_selected(const ItemType type) const return false; } -t_layer_height_range ObjectList::get_selected_layers_range() const +int ObjectList::get_selected_layers_range_idx() const { - t_layer_height_range layer_range = { 0.0, 0.0 }; const wxDataViewItem& item = GetSelection(); if (!item) - return layer_range; + return -1; const ItemType type = m_objects_model->GetItemType(item); if (type & itSettings && m_objects_model->GetItemType(m_objects_model->GetParent(item)) != itLayer) - return layer_range; + return -1; - return type & itLayer ? m_objects_model->GetLayerRangeByItem(item) : - type & itSettings ? m_objects_model->GetLayerRangeByItem(m_objects_model->GetParent(item)) : - layer_range; + return m_objects_model->GetLayerIdByItem(type & itLayer ? item : m_objects_model->GetParent(item)); } void ObjectList::update_selections() @@ -2687,15 +2684,15 @@ void ObjectList::update_selections() wxDataViewItem obj_item = m_objects_model->GetItemById(obj_idx); if (m_selection_mode & smSettings) { - if (m_selected_layers_range.first <= EPSILON && m_selected_layers_range.second <= EPSILON) + if (m_selected_layers_range_idx < 0) sels.Add(m_objects_model->GetSettingsItem(obj_item)); else - sels.Add(m_objects_model->GetSettingsItem(m_objects_model->GetItemByLayerRange(obj_idx, m_selected_layers_range))); + sels.Add(m_objects_model->GetSettingsItem(m_objects_model->GetItemByLayerId(obj_idx, m_selected_layers_range_idx))); } else if (m_selection_mode & smLayerRoot) sels.Add(m_objects_model->GetLayerRootItem(obj_item)); else if (m_selection_mode & smLayer) - sels.Add(m_objects_model->GetItemByLayerRange(obj_idx, m_selected_layers_range)); + sels.Add(m_objects_model->GetItemByLayerId(obj_idx, m_selected_layers_range_idx)); } else { for (const auto& object : objects_content) { diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 1c30a4d974..5c971c40f8 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -78,8 +78,8 @@ public: }; private: - SELECTION_MODE m_selection_mode {smUndef}; - t_layer_height_range m_selected_layers_range; + SELECTION_MODE m_selection_mode {smUndef}; + int m_selected_layers_range_idx; struct dragged_item_data { @@ -309,8 +309,8 @@ public: void init_objects(); bool multiple_selection() const ; bool is_selected(const ItemType type) const; - t_layer_height_range get_selected_layers_range() const; - void set_selected_layers_range(const t_layer_height_range range) { m_selected_layers_range = range; } + int get_selected_layers_range_idx() const; + void set_selected_layers_range_idx(const int range_idx) { m_selected_layers_range_idx = range_idx; } void set_selection_mode(SELECTION_MODE mode) { m_selection_mode = mode; } void update_selections(); void update_selections_on_canvas(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3d1e7b97ce..067d13b8ab 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3731,11 +3731,11 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; if (this->sidebar->obj_list()->is_selected(itSettings)) { snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR; - snapshot_data.layer_range = this->sidebar->obj_list()->get_selected_layers_range(); + snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx(); } else if (this->sidebar->obj_list()->is_selected(itLayer)) { snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR; - snapshot_data.layer_range = this->sidebar->obj_list()->get_selected_layers_range(); + snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx(); } else if (this->sidebar->obj_list()->is_selected(itLayerRoot)) snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR; @@ -3807,11 +3807,11 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator top_snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; if (this->sidebar->obj_list()->is_selected(itSettings)) { top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR; - top_snapshot_data.layer_range = this->sidebar->obj_list()->get_selected_layers_range(); + top_snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx(); } else if (this->sidebar->obj_list()->is_selected(itLayer)) { top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR; - top_snapshot_data.layer_range = this->sidebar->obj_list()->get_selected_layers_range(); + top_snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx(); } else if (this->sidebar->obj_list()->is_selected(itLayerRoot)) top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR; @@ -3820,7 +3820,6 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator bool new_selected_layer_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR) != 0; bool new_selected_layerroot_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR) != 0; - t_layer_height_range layer_range = it_snapshot->snapshot_data.layer_range; // Disable layer editing before the Undo / Redo jump. if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); @@ -3859,7 +3858,7 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator new_selected_layerroot_on_sidebar ? ObjectList::SELECTION_MODE::smLayerRoot : ObjectList::SELECTION_MODE::smUndef); if (new_selected_settings_on_sidebar || new_selected_layer_on_sidebar) - this->sidebar->obj_list()->set_selected_layers_range(/*top_snapshot_data.*/layer_range); + this->sidebar->obj_list()->set_selected_layers_range_idx(it_snapshot->snapshot_data.layer_range_idx); this->update_after_undo_redo(temp_snapshot_was_taken); // Enable layer editing after the Undo / Redo jump. diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 9b12be00eb..daf6752b71 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -36,7 +36,7 @@ namespace Slic3r { namespace UndoRedo { -SnapshotData::SnapshotData() : printer_technology(ptUnknown), flags(0), layer_range({0.0, 0.0}) +SnapshotData::SnapshotData() : printer_technology(ptUnknown), flags(0), layer_range_idx(-1) { } diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 85ad11b828..37cc5a049f 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -37,6 +37,7 @@ struct SnapshotData PrinterTechnology printer_technology; // Bitmap of Flags (see the Flags enum). unsigned int flags; + int layer_range_idx; // Bitmask of various binary flags to be stored with the snapshot. enum Flags { @@ -45,8 +46,6 @@ struct SnapshotData SELECTED_LAYERROOT_ON_SIDEBAR = 4, SELECTED_LAYER_ON_SIDEBAR = 8, }; - - t_layer_height_range layer_range; }; struct Snapshot From 42c89407954456df07f88ea6864f3843abfd87c5 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 24 Jul 2019 12:39:01 +0200 Subject: [PATCH 404/627] Fixed is_nil() method on ConfigOptions. PlaceholderParser was extended to support external config. The external config has a lowest priority when looking up a variable. --- src/libslic3r/Config.hpp | 8 +++++--- src/libslic3r/PlaceholderParser.cpp | 10 +++++++--- src/libslic3r/PlaceholderParser.hpp | 8 ++++++-- src/libslic3r/Print.cpp | 6 ++---- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index e6953739a1..64fc69dd55 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -378,7 +378,7 @@ public: static double nil_value() { return std::numeric_limits::quiet_NaN(); } // A scalar is nil, or all values of a vector are nil. bool is_nil() const override { for (auto v : this->values) if (! std::isnan(v)) return false; return true; } - bool is_nil(size_t idx) const override { return std::isnan(v->values[idx]); } + bool is_nil(size_t idx) const override { return std::isnan(this->values[idx]); } std::string serialize() const override { @@ -524,7 +524,7 @@ public: static int nil_value() { return std::numeric_limits::max(); } // A scalar is nil, or all values of a vector are nil. bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } - bool is_nil(size_t idx) const override { return v->values[idx] == nil_value(); } + bool is_nil(size_t idx) const override { return this->values[idx] == nil_value(); } std::string serialize() const override { @@ -633,6 +633,7 @@ public: ConfigOption* clone() const override { return new ConfigOptionStrings(*this); } ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionStrings &rhs) const { return this->values == rhs.values; } + bool is_nil(size_t idx) const override { return false; } std::string serialize() const override { @@ -847,6 +848,7 @@ public: ConfigOption* clone() const override { return new ConfigOptionPoints(*this); } ConfigOptionPoints& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionPoints &rhs) const { return this->values == rhs.values; } + bool is_nil(size_t idx) const override { return false; } std::string serialize() const override { @@ -994,7 +996,7 @@ public: static unsigned char nil_value() { return std::numeric_limits::max(); } // A scalar is nil, or all values of a vector are nil. bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } - bool is_nil(size_t idx) const override { return v->values[idx] == nil_value(); } + bool is_nil(size_t idx) const override { return this->values[idx] == nil_value(); } bool& get_at(size_t i) { assert(! this->values.empty()); diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 2bfe9b7454..604afad204 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -62,7 +62,7 @@ namespace Slic3r { -PlaceholderParser::PlaceholderParser() +PlaceholderParser::PlaceholderParser(const DynamicConfig *external_config) : m_external_config(external_config) { this->set("version", std::string(SLIC3R_VERSION)); this->apply_env_variables(); @@ -608,6 +608,7 @@ namespace client } struct MyContext { + const DynamicConfig *external_config = nullptr; const DynamicConfig *config = nullptr; const DynamicConfig *config_override = nullptr; size_t current_extruder_id = 0; @@ -628,6 +629,8 @@ namespace client opt = config_override->option(opt_key); if (opt == nullptr) opt = config->option(opt_key); + if (opt == nullptr && external_config != nullptr) + opt = external_config->option(opt_key); return opt; } @@ -1255,6 +1258,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const { client::MyContext context; + context.external_config = this->external_config(); context.config = &this->config(); context.config_override = config_override; context.current_extruder_id = current_extruder_id; @@ -1266,8 +1270,8 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override) { client::MyContext context; - context.config = &config; - context.config_override = config_override; + context.config = &config; + context.config_override = config_override; // Let the macro processor parse just a boolean expression, not the full macro language. context.just_boolean_expression = true; return process_macro(templ, context) == "true"; diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index 22c790e6b5..f682c3252f 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -12,7 +12,7 @@ namespace Slic3r { class PlaceholderParser { public: - PlaceholderParser(); + PlaceholderParser(const DynamicConfig *external_config = nullptr); // Return a list of keys, which should be changed in m_config from rhs. // This contains keys, which are found in rhs, but not in m_config. @@ -35,6 +35,8 @@ public: DynamicConfig& config_writable() { return m_config; } const DynamicConfig& config() const { return m_config; } const ConfigOption* option(const std::string &key) const { return m_config.option(key); } + // External config is not owned by PlaceholderParser. It has a lowest priority when looking up an option. + const DynamicConfig* external_config() const { return m_external_config; } // Fill in the template using a macro processing language. // Throws std::runtime_error on syntax or runtime error. @@ -50,7 +52,9 @@ public: void update_timestamp() { update_timestamp(m_config); } private: - DynamicConfig m_config; + // config has a higher priority than external_config when looking up a symbol. + DynamicConfig m_config; + const DynamicConfig *m_external_config; }; } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index af2479e5a1..ac2e4f151e 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -545,9 +545,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co m_ranges.reserve(in.size()); // Input ranges are sorted lexicographically. First range trims the other ranges. coordf_t last_z = 0; - for (const std::pair &range : in) { -// for (auto &range : in) { - if (range.first.second > last_z) { + for (const std::pair &range : in) + if (range.first.second > last_z) { coordf_t min_z = std::max(range.first.first, 0.); if (min_z > last_z + EPSILON) { m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr); @@ -559,7 +558,6 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co last_z = range.first.second; } } - } if (m_ranges.empty()) m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr); else if (m_ranges.back().second == nullptr) From 0a04a6d92a4eef83b44f7884447472b200d45293 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 24 Jul 2019 13:51:39 +0200 Subject: [PATCH 405/627] Add remove all from selection command to undo/redo stack only when the redo stack is empty --- src/slic3r/GUI/Selection.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index f3b85b7b29..33099f9da8 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -495,8 +495,9 @@ void Selection::remove_all() return; //##################################################################################################################################################################################### -// wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_all()"))); + if (!wxGetApp().plater()->can_redo()) //##################################################################################################################################################################################### + wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_all()"))); m_mode = Instance; clear(); From 1483a7fd51dcbafd7ece744efb5c420eb68bf393 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 24 Jul 2019 14:02:36 +0200 Subject: [PATCH 406/627] Render custom bed model on prusa beds --- src/slic3r/GUI/3DBed.cpp | 16 +++++++++++++--- src/slic3r/GUI/3DBed.hpp | 3 ++- src/slic3r/GUI/Plater.cpp | 17 ++++++++++------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index a41b39a7ee..3a8a669043 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -189,13 +189,14 @@ void Bed3D::Axes::render_axis(double length) const Bed3D::Bed3D() : m_type(Custom) , m_custom_texture("") + , m_custom_model("") , m_requires_canvas_update(false) , m_vbo_id(0) , m_scale_factor(1.0f) { } -bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture) +bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) { EType new_type = detect_type(shape); @@ -207,12 +208,21 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture) cst_texture = ""; } - if ((m_shape == shape) && (m_type == new_type) && (m_custom_texture == cst_texture)) + // check that the passed custom texture filename is valid + std::string cst_model(custom_model); + if (!cst_model.empty()) + { + if (!boost::algorithm::iends_with(custom_model, ".stl") || !boost::filesystem::exists(custom_model)) + cst_model = ""; + } + + if ((m_shape == shape) && (m_type == new_type) && (m_custom_texture == cst_texture) && (m_custom_model == cst_model)) // No change, no need to update the UI. return false; m_shape = shape; m_custom_texture = cst_texture; + m_custom_model = cst_model; m_type = new_type; calc_bounding_boxes(); @@ -385,7 +395,7 @@ void Bed3D::render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom { if (!bottom) { - std::string filename = resources_dir() + "/models/" + key + "_bed.stl"; + std::string filename = m_custom_model.empty() ? resources_dir() + "/models/" + key + "_bed.stl" : m_custom_model; if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 71b319b0ab..3571166daf 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -75,6 +75,7 @@ private: EType m_type; Pointfs m_shape; std::string m_custom_texture; + std::string m_custom_model; mutable BoundingBoxf3 m_bounding_box; mutable BoundingBoxf3 m_extended_bounding_box; Polygon m_polygon; @@ -103,7 +104,7 @@ public: const Pointfs& get_shape() const { return m_shape; } // Return true if the bed shape changed, so the calee will update the UI. - bool set_shape(const Pointfs& shape, const std::string& custom_texture); + bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model); const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; } bool contains(const Point& point) const; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2c9f41c566..de64e226e7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1851,7 +1851,7 @@ struct Plater::priv // triangulate the bed and store the triangles into m_bed.m_triangles, // fills the m_bed.m_grid_lines and sets m_bed.m_origin. // Sets m_bed.m_polygon to limit the object placement. - void set_bed_shape(const Pointfs& shape, const std::string& custom_texture); + void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model); bool can_delete() const; bool can_delete_all() const; @@ -2010,7 +2010,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values, - config->option("bed_custom_texture")->value); + config->option("bed_custom_texture")->value, + config->option("bed_custom_model")->value); }); // Preview events: @@ -2018,7 +2019,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values, - config->option("bed_custom_texture")->value); + config->option("bed_custom_texture")->value, + config->option("bed_custom_model")->value); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); }); @@ -3623,9 +3625,9 @@ bool Plater::priv::can_mirror() const return get_selection().is_from_single_instance(); } -void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture) +void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) { - bool new_shape = bed.set_shape(shape, custom_texture); + bool new_shape = bed.set_shape(shape, custom_texture, custom_model); if (new_shape) { if (view3D) view3D->bed_shape_changed(); @@ -4502,7 +4504,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) p->config->set_key_value(opt_key, config.option(opt_key)->clone()); if (opt_key == "printer_technology") this->set_printer_technology(config.opt_enum(opt_key)); - else if ((opt_key == "bed_shape") || (opt_key == "bed_custom_texture")) { + else if ((opt_key == "bed_shape") || (opt_key == "bed_custom_texture") || (opt_key == "bed_custom_model")) { bed_shape_changed = true; update_scheduled = true; } @@ -4537,7 +4539,8 @@ void Plater::on_config_change(const DynamicPrintConfig &config) if (bed_shape_changed) p->set_bed_shape(p->config->option("bed_shape")->values, - p->config->option("bed_custom_texture")->value); + p->config->option("bed_custom_texture")->value, + p->config->option("bed_custom_model")->value); if (update_scheduled) update(); From 48dc2bb7622ba6db4b251e9d9166a5c1ae246fe7 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 25 Jul 2019 08:43:21 +0200 Subject: [PATCH 407/627] Fixed positioning of bed model into the scene and changed bundled bed models --- resources/models/mk2_bed.stl | Bin 2484 -> 2484 bytes resources/models/mk3_bed.stl | Bin 91884 -> 91884 bytes resources/models/sl1_bed.stl | Bin 416084 -> 416084 bytes src/slic3r/GUI/3DBed.cpp | 14 ++------------ src/slic3r/GUI/3DScene.cpp | 8 +------- 5 files changed, 3 insertions(+), 19 deletions(-) diff --git a/resources/models/mk2_bed.stl b/resources/models/mk2_bed.stl index 07b7655ea6f601ec070d0c06f70265e35d2d1e5b..21fb29d0c36a512748bacf093bd9500c7c395f8e 100644 GIT binary patch literal 2484 zcmb7_ziSjx5XVRI%Ro9?i!Du|9Fav#v!{n$3M&zX6d`AImxPN-z`|?%OY|QQMax|w zm4AU)C`d}vWdDFU}UwD)m-{=Ov864}{J6GUMi{o_-PN^|N+U>=a4 z4~m6piMC=N=~^9}yc?uqfBy1l;Oj=ezI?pzY9;@;TA@F&5z~PrhK69 zfGBKA_&d{@t;(7Q>T|6Mn-c!VcaX@UN)Z}Wq~&J;37(R|rsOiARY#(U%1=B0d^P-k&k1X?^-u2!=N>mZR$#=NMkQ$*JDc?9~v2$9B2w1ci6=nrW}@cokm zb7e$Une6oCHq9o`l4$)|%!}2fydlh1R$1&Q3(;H3i|>o`2(7fsPZ-X)J+WPPgRzEP zh_E+Aw>hexoQtq#OY7sdf#hd5O0qe$Q>LIEUso36Li6yC~IsoA{@9#BWaa;deXA v-3awDBdW-*fBT}>0P;u@?8ASTsFrz;{B1Xby&qaD_a@pN>qe*#jZlaSReh_L literal 2484 zcma)-ziJgx5XMh1Q3#}vyuc~7i-s*_F{TQsqCP+n!64i~tkNCC#uQ1b;7g=S6ZRIq zfrzcx3&I7lbiOk?XTG!9WOE>!Vej{we>*$7_vT=B`0Dl1;k&qXFxx*mj?Z80#s_g{ ze-`6re7x~!Gd^5fjkh~%s~e%56hi9uc0X?)KRJ08LY$vBVqd;5_TOJ0vz-Z1lG95WPR(4R$1X9F z)X{pcLIADRs;v`K^dbl0T@1mWpb(|gV}xc^t9%xELj2>TmVaTt98aZ?gBaFfxig)K z{xTpacYppS)&EjCsbE{LMv0W`C{q%GPe$E^>|NA2sr4r%-7S-G6XBCstC&{j`Px50 zdmbE*>r<^{)FCaND<|UIZPVXONQPxXLEK$w+Mml!j^Vy!mz2uc9uT>sHtkMmr>#CP zsl^4BtJJS;HQZMls%e5fLBCjTTKoZZU)mqryQt5mvLbR;mIuG@+JTP diff --git a/resources/models/mk3_bed.stl b/resources/models/mk3_bed.stl index 1bd1a8faac668c98333114facd74187c54433f97..e494641168c630bdc4591c11c012bb2d7dc4f91d 100644 GIT binary patch literal 91884 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 J_h414{|_T}dxZc1 literal 91884 zcmb`wdAuG)nLXY?2#W#2A^}8}I3UY)V1yt`a=Y*Qh7bWmh9!h$k^n;j5)}MOL}ExI zAT#VD%wS+30y3!RAo2_Q?JJuM!WR)36lO$64G2MyVOSKBU!AHxRp;q`-~9aJ_kQH& zv8qm;=Xq-B+PXT+{{Q_Ates_tAG>qD{YH6(oOl2F$=z)6D?iGzqUe+-cefqAq9G%d zB2p?vt6UVo;8%K%o98m$EK_unn}{)M+&C08$+8{)=BXZXjVQ^HQWZr)7e#sHNf8}X zsu5+Y3LKZdx=r=S@Amf`>L1mFa^O$|W004kDF=>CFaPP_kAD(;^~NDH zy?>C`4nLUT{f1PO1BW6QgH(#9960`JxBc_4JrQcj)9)M_Ysr*7505p2;V9WG1LE=q z0x?7&Um^m>_6bq;N~P#v$0mQ+CZAn)nl7saAgPsVVo)fHFm5@Y_f zk}~{8ndxb{YLaC46kRp8kL5qOe648V_RIH*sT56?P#$VwN|j}AT>fywa{4OeTNFvo zvcG@+W0BkXs*|O1PUYdAiBkSnD`i)$KKZE|8(Ps<%C1_SzihKUT4{_T)Q3$wQjOD^ zm}FW(U(KJlW-Rkm{wVWtF`SrC9-~JkL_!xul9`?y=VZNWVycx*gleMsCaq-bt7+%G`1icSsdWtnsYlGI~ zB$I=PRIjY=$qsT*tHf^0IcW=yS&J6tf1H&M13kQWsQc1S&-Syef4X6)yW{g;^fJT4 zQ%w;}g&0l6@bFZ-eED&C=R>c04vpFUsiE#WJAU5q1r9|p6=H~n%<#Zbt~Wm4Z?`EG z?3l7yr(3LdiqH4^^E%z>zdq5+3=bTNU@F9DDu&lj=PlRGyMHs&b1()7%1Cv~mmS}j zz~}z|CR6fvKR(_gR9|uc!SJA!BA5y>nu_5;s|~OI*TFY`b6R!(jJx)I_JL0K)E7_q z5fHoH*Xdrh`CM=Lgq5A{j<^23m)#TV(20W+HeFbqap8IQDMC{X=iTl z_pN(|pAVLuvfItRbG+xE3{8eBMHHvpH2m(k&sXdI=DvMznK(IW_2{U{QIo^ElcR=N zw(~3d3?I4u!s^L+=iR3W%~#P`_NS4vC(ZaWXQ@dm)m=5rvaLS+!r;GsW^Q%$q6_ZU zl2Tu(N3!gr?+y+YtIn*R@09m3FL+l|DPNZTpZOoDKJd(^s?DZ-b>g$qs}wOtMl_~hIxFYvX2GB{BFk8FE!&7la5p^SWq4jku< zSuy+%yBzO1Q0B+%eW~Ywcc(79%;!tFjbIEkQ8ZmFSCYp+J;7|l( z)LNusPJ!bapF6&KWUW_xd8kJzBNZ)gBQ%CGIEW5fefr3J@CNrRM0xlc_srM4uh7ov zE6QyIW5BLx%0a7VKJd!m7N>^tAOd;e`GK-QD$0RF5sX19MN*{6HN;DuxFRL1avY7){0Sz)_C?P#z_o$gJ%g0U#B_1BW17iwdI*#Aqsp2aciQEP2W>+*{$EooO?B8CAHn*Jx8vHgcJkZ_Tm`#_WQAY^lwpn%^V+lIQ9nMOWtkv zS63{%CfYIMfGwjPr>u2hA3LZo@>Q+we{hG|j+!Idp&V~bSrP5%@2eRDUx`{>w&B0J z))o4aL*AUzM=P>JE9Ia^;M1^nMel0guKh}jBif-_sdxMPN_z(OYS56aD?UFW$1NwL z2ME^clVSQ+d-(7+f-%Tf5np_KzZxNy`&N6-hR7@QK8heWJY@TJMJ&E+Y}AnbSR28f z3|g_rfy|z$jnJMmtaxY-j`YpCtGc(JyG79t5xh(PsNdRJwKWOov?g=^NjvA?TsN

aKB&9zs@YzX<*!fcF8jLJ5ji>|2X~K~ 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 LB}?zYs#gCmVnz~> diff --git a/resources/models/sl1_bed.stl b/resources/models/sl1_bed.stl index b2cadde4bd50b42ae247da4abba4dfb0b74d1413..dfbf12845b88b2a32afb50fdd8cb6e92a4c1193e 100644 GIT binary patch literal 416084 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

^STM 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͢g>BY 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

vPy}ccX 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­}`MFNPt(BYELYOjR&g?m>&HKSL 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 QRWJ=TRyYih-J1#ScEbU4# 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 Z)5ZTmCFfLJK3fi{E3Okx#pR2_{|EY6!1w?F diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 3a8a669043..4f3d499b2b 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -398,18 +398,8 @@ void Bed3D::render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom std::string filename = m_custom_model.empty() ? resources_dir() + "/models/" + key + "_bed.stl" : m_custom_model; if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { - Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2)); - if (key == "mk2") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 7.5, -0.03); - else if (key == "mk3") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 5.5, 2.43); - else if (key == "sl1") - // hardcoded value to match the stl model - offset += Vec3d(0.0, 0.0, -0.03); - - m_model.center_around(offset); + // move the model a bit down to avoid z-fighting with the texture quad + m_model.set_offset(-0.03 * Vec3d::UnitZ()); // update extended bounding box calc_bounding_boxes(); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 9e3eaf41d3..dba5958466 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1736,13 +1736,7 @@ bool GLBed::on_init_from_file(const std::string& filename) m_filename = filename; - ModelObject* model_object = model.objects.front(); - model_object->center_around_origin(); - - TriangleMesh mesh = model.mesh(); - mesh.repair(); - - m_volume.indexed_vertex_array.load_mesh(mesh); + m_volume.indexed_vertex_array.load_mesh(model.mesh()); float color[4] = { 0.235f, 0.235f, 0.235f, 1.0f }; set_color(color, 4); From 9625fe8f5b9c39a713a804b28dcf8bc0e33225c1 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 25 Jul 2019 09:45:43 +0200 Subject: [PATCH 408/627] Render custom bed model on custom beds --- src/slic3r/GUI/3DBed.cpp | 47 ++++++++++++++++++++++++---------------- src/slic3r/GUI/3DBed.hpp | 1 + 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 4f3d499b2b..cebb17c236 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -394,24 +394,7 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const void Bed3D::render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const { if (!bottom) - { - std::string filename = m_custom_model.empty() ? resources_dir() + "/models/" + key + "_bed.stl" : m_custom_model; - if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) - { - // move the model a bit down to avoid z-fighting with the texture quad - m_model.set_offset(-0.03 * Vec3d::UnitZ()); - - // update extended bounding box - calc_bounding_boxes(); - } - - if (!m_model.get_filename().empty()) - { - glsafe(::glEnable(GL_LIGHTING)); - m_model.render(); - glsafe(::glDisable(GL_LIGHTING)); - } - } + render_model(m_custom_model.empty() ? resources_dir() + "/models/" + key + "_bed.stl" : m_custom_model); render_texture(m_custom_texture.empty() ? resources_dir() + "/icons/bed/" + key + ".svg" : m_custom_texture, bottom, canvas); } @@ -421,6 +404,7 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& if (filename.empty()) { m_texture.reset(); + render_default(); return; } @@ -565,14 +549,39 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& } } +void Bed3D::render_model(const std::string& filename) const +{ + if (filename.empty()) + return; + + if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) + { + // move the model a bit down to avoid z-fighting with the texture quad + m_model.set_offset(-0.03 * Vec3d::UnitZ()); + + // update extended bounding box + calc_bounding_boxes(); + } + + if (!m_model.get_filename().empty()) + { + glsafe(::glEnable(GL_LIGHTING)); + m_model.render(); + glsafe(::glDisable(GL_LIGHTING)); + } +} + void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const { - if (m_custom_texture.empty()) + if (m_custom_texture.empty() && m_custom_model.empty()) { render_default(); return; } + if (!bottom) + render_model(m_custom_model); + render_texture(m_custom_texture, bottom, canvas); } diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 3571166daf..ef731756db 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -120,6 +120,7 @@ private: EType detect_type(const Pointfs& shape) const; void render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const; void render_texture(const std::string& filename, bool bottom, GLCanvas3D& canvas) const; + void render_model(const std::string& filename) const; void render_custom(GLCanvas3D& canvas, bool bottom) const; void render_default() const; void reset(); From bc680b8376294955fdeb01e5d6838aa332e68de0 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 25 Jul 2019 10:38:18 +0200 Subject: [PATCH 409/627] Draw lighter gridlines when rendering default procedural texture on print bed when seen from above and custom model is present --- src/slic3r/GUI/3DBed.cpp | 40 ++++++++++++++++++++++++++-------------- src/slic3r/GUI/3DBed.hpp | 2 +- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index cebb17c236..ca48f7c376 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -241,6 +241,8 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour; reset(); + m_texture.reset(); + m_model.reset(); // Set the origin and size for painting of the coordinate system axes. m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z); @@ -404,7 +406,7 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& if (filename.empty()) { m_texture.reset(); - render_default(); + render_default(bottom); return; } @@ -421,7 +423,7 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& // generate a temporary lower resolution texture to show while no main texture levels have been compressed if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8)) { - render_default(); + render_default(bottom); return; } } @@ -429,7 +431,7 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& // starts generating the main texture, compression will run asynchronously if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size)) { - render_default(); + render_default(bottom); return; } } @@ -440,7 +442,7 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& { if (!m_temp_texture.load_from_file(filename, false, GLTexture::None, false)) { - render_default(); + render_default(bottom); return; } } @@ -448,13 +450,13 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& // starts generating the main texture, compression will run asynchronously if (!m_texture.load_from_file(filename, true, GLTexture::MultiThreaded, true)) { - render_default(); + render_default(bottom); return; } } else { - render_default(); + render_default(bottom); return; } } @@ -575,7 +577,7 @@ void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const { if (m_custom_texture.empty() && m_custom_model.empty()) { - render_default(); + render_default(bottom); return; } @@ -585,13 +587,15 @@ void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const render_texture(m_custom_texture, bottom, canvas); } -void Bed3D::render_default() const +void Bed3D::render_default(bool bottom) const { m_texture.reset(); unsigned int triangles_vcount = m_triangles.get_vertices_count(); if (triangles_vcount > 0) { + bool has_model = !m_model.get_filename().empty(); + glsafe(::glEnable(GL_LIGHTING)); glsafe(::glDisable(GL_DEPTH_TEST)); @@ -600,23 +604,31 @@ void Bed3D::render_default() const glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f)); - glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); - glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data())); - glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); + if (!has_model && !bottom) + { + // draw background + glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f)); + glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); + glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data())); + glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); + } + + glsafe(::glDisable(GL_LIGHTING)); // draw grid // we need depth test for grid, otherwise it would disappear when looking the object from below glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glLineWidth(3.0f * m_scale_factor)); - glsafe(::glColor4f(0.2f, 0.2f, 0.2f, 0.4f)); + if (has_model && !bottom) + glsafe(::glColor4f(0.75f, 0.75f, 0.75f, 1.0f)); + else + glsafe(::glColor4f(0.2f, 0.2f, 0.2f, 0.4f)); glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data())); glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines.get_vertices_count())); glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glDisable(GL_BLEND)); - glsafe(::glDisable(GL_LIGHTING)); } } diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index ef731756db..a3e4bcf801 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -122,7 +122,7 @@ private: void render_texture(const std::string& filename, bool bottom, GLCanvas3D& canvas) const; void render_model(const std::string& filename) const; void render_custom(GLCanvas3D& canvas, bool bottom) const; - void render_default() const; + void render_default(bool bottom) const; void reset(); }; From d6f03a2f52fcd952976dd540b424afa5f480627f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 25 Jul 2019 11:34:42 +0200 Subject: [PATCH 410/627] Bed model placed into the scene so that its origin goes into shape center --- src/slic3r/GUI/3DBed.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index ca48f7c376..a402fa82d4 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -558,8 +558,10 @@ void Bed3D::render_model(const std::string& filename) const if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { - // move the model a bit down to avoid z-fighting with the texture quad - m_model.set_offset(-0.03 * Vec3d::UnitZ()); + // move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad + Vec3d shift = m_bounding_box.center(); + shift(2) = -0.03; + m_model.set_offset(shift); // update extended bounding box calc_bounding_boxes(); From e86d40fe9854ca484a15c84c20a9b88f49bc6566 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 25 Jul 2019 11:35:16 +0200 Subject: [PATCH 411/627] Modified bundled bed models --- resources/models/mk2_bed.stl | Bin 2484 -> 2484 bytes resources/models/mk3_bed.stl | Bin 91884 -> 91884 bytes resources/models/sl1_bed.stl | Bin 416084 -> 416084 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/models/mk2_bed.stl b/resources/models/mk2_bed.stl index 21fb29d0c36a512748bacf093bd9500c7c395f8e..8a74a2349505b6023a03d2b20e7a07b80a2b0f70 100644 GIT binary patch literal 2484 zcmb7`u}&0W5XZ-GXkr5oV6ir+1Pw9S7^_Xr_zd)fqe393By?`#EBFXk`vivFT&b-I zr3D>i3qzz5&hNj&-2A`2j{~-V$;bTuGxN>tEbkBZ_lBPaTiNz-??cw#8SHKihA&^g zdh<4W{_$WlTYn+zS)m|F0|)_NLj^R->1Z{ z$>sP@bJEhY#P7-J7`pKK)||BVsBzi}WW`>rnVAySJF&80^J#OrXM4436zY=ttqVD*{@pRa@(D ze^_PVw!(<+H#9vf{t6F2`f0oT8SyPJQx@JS5%2jtf@5&COvf>NN;IofZ;U7_SWaZMXUMks<88W`U?q4R_wr(C zF>eXh_1v@CZ4j%Ad2!U6N5nOODGU6><65d~Z!pf)R=?W_Rs!)PWc4G|0^7Cpery|X z|3%;p>SltK;H8gd=33b8HkE9fzg3?m;;97Vm3MV*58~V=|1Ly#cF!N>#rXrR+_ggP z)3=CiM7W*M7@U=7#=5@kvf6F<;=$&@?=->Z@T$WUKD+LqYoQ0WMcD?ivXEExL#;eb zi1;}Odz)Z&b#1P2ZrkoI1S`QmOkriS>stCfxA!C7yTQA%*-aA~Bb}9N#s4$#&X4Oz j6YU}UwD)m-{=Ov864}{J6GUMi{o_-PN^|N+U>=a4 z4~m6piMC=N=~^9}yc?uqfBy1l;Oj=ezI?pzY9;@;TA@F&5z~PrhK69 zfGBKA_&d{@t;(7Q>T|6Mn-c!VcaX@UN)Z}Wq~&J;37(R|rsOiARY#(U%1=B0d^P-k&k1X?^-u2!=N>mZR$#=NMkQ$*JDc?9~v2$9B2w1ci6=nrW}@cokm zb7e$Une6oCHq9o`l4$)|%!}2fydlh1R$1&Q3(;H3i|>o`2(7fsPZ-X)J+WPPgRzEP zh_E+Aw>hexoQtq#OY7sdf#hd5O0qe$Q>LIEUso36Li6yC~IsoA{@9#BWaa;deXA v-3awDBdW-*fBT}>0P;u@?8ASTsFrz;{B1Xby&qaD_a@pN>qe*#jZlaSReh_L diff --git a/resources/models/mk3_bed.stl b/resources/models/mk3_bed.stl index e494641168c630bdc4591c11c012bb2d7dc4f91d..6aff36f0bcbda670a4d3ed645dcac8ff112d3462 100644 GIT binary patch literal 91884 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 literal 91884 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 J_h414{|_T}dxZc1 diff --git a/resources/models/sl1_bed.stl b/resources/models/sl1_bed.stl index dfbf12845b88b2a32afb50fdd8cb6e92a4c1193e..4a6980b09f5c2d2a1860fb6b31cc5c45e50f9c54 100644 GIT binary patch literal 416084 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�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 literal 416084 zcmb5X3!F|>`#!!(F@%N)IgL;bslhm#z2?~zm5P$6WD=!PL?Jnh$g8|bp;T{-L#3KB zhH`xNni(CYl19iO)sWtFG%-ade)qlCvz~RWXL!H=|MU3_`?L4D*7b?|z%JO$&My$cyiY3B;ZB~MS`HyZDI-sT)|kXJk_s^m`&>rp9XYFmq`z1i0p%o<;^ov$ zPLe5DlJcExApdOKU^re^>)*;k@(#(W_t4 zB}c@Bs;~Gdqq*eyP;Xq0|GVU61tb1>HkJmL zX~}t~s9kO+^W)x#?STkv8!EjV5WTWo3zijNDEn+$(DZg}DVrX)pg|m^w%|Y86O#%N zeSE=cYV*3W_E=JoCUXkXRPh_!9;OA83o(iuGO7n%w?EWJyzci{M%e{QPEl z6mJfNvM~uUhxuXU`>uVT*xPL%%`ZSi#*x~7ma0KHWW==gDZyELCMm9Ei*S2;afO!w!6Ef8OCrMd z-Lixk?t==?M7s>qb0UT+zK~|M$;p%x93Q5x%=o|_XVGS?nDOyKUYfk)y)?BX7#=OD z8u%GIheYP?OiCCZrdM*?RQ@zgMpvY%>faA{Rs$SS1J7RLmn=LtjET$qe~l=vQ5;<% zVo!&)y1p=V3>EmT^5ZwH^s~Qsi0X<*7mlbhS_c_L|5Gb+8UyK1 zk8`^wMtwUrF%68XcgxlO=pL`c)3Aut{okxxvv8{VYX9&k)(MVUJCEtvhb9slgWYHh z;z&gVzc*u3=gEw7M7W_6$GJh^%)rkkUL5~AjkT1ksw*xDcsye$vjZrP;Bxe&a^MV* zI5U`-glN70o6hf0UHx7%Jc@|y9}Y)sUotZT*VR`W(-i-yqudki7Z-u^3eP;{XCH2J z3}v3ph@##Yj&nO_@GN7}qfjT?QqM`I_GbjnK6d^nbLJ8^riE!K-XFX5Tw-ULvI6_; z&P28!a?b2rlJ6kg+2{APl;HPR8WvGjfT8R&m(TvPB!01T3C?zjh}=F4{QO^ewxl#p zIbSiN&)LsJJCvK@k_r)FpSgB3H(wk5`olEEe>NwGNSxuI95IBR`*Q#Fx?ghs1C+xm ziK}x~J9+%xcIx?mw+;rjry1wQA(4#2Gn_dQ=So`D7EYCoKKZiqwg2IsXy>bva{@ne z$og=X;UexH+D;CCyq%ghm(qaQ5@*rgwb%G5!>fmBb1B?eRPRN0P%WA{QO}}w?ovBz z%H%cu!db59zNp_jSWTr(oNE#NTS|5LOoRUN>*4dG3&%zMzAINpR&0MgOvBE#29ZPW zY5x|+uWQ+soO6YU6@TVMiu_jzVSnySy?f+azNj#ka!Bpr%-V>+c^5;abjNN$*_xyZ&rm4eYG1*OGf^e4E{wStU-FtA(>y``JsE zhB1{@>tr8u)i#6zu-O^ z(VVLx8wLdrG@lrj1J@$^TvmXqolSvfA-hJE6%3p@-_IOSE35?uuMh>9)`P8|}cXDOXoU2LO>&WI~ddn;7W(2ucNBzwA*F+`^UlGP@ z)>?Pfe&NHK@{#O*^6un4o7^v^v^kI-=jxiqRpjn=edQ%7LufUyYxw9LQ9pI;>d1&x zUc&?5uIIc1m^XN4XWhf_3%B5!NM_BKLxip?bH>r3-5o&R32(-CWVjm;BJJhrLEWEQ z(jKe5DthxRKcmN_2+x+d18@$BYS1EMI6?Ah<`<0vDh z@y(U(yYQZ2Anh4&X2AUp?t^q1yf1l;_9eLAK?J{dcQW@8VoYqeWWFH!e~OStb6!l%#`>F-rS;64fw z7*lwTQU^xqa}*-7ia&^$a}@5>xCPxEnVAPqy(81JkBhK#xVwkb`#GL>S+;BCyIUeg z+VAYh;`>K4qT+kF_--kw&_RUn&3gX$S7h(h;i9x zcLzvvlvw^ymDult#Y_*9)#i2Fnrab2I)>3vV6$@5=ruj-!vhMzrgLWscK zB%*Dec=Y046cNc&a$qldX*uHyW6O-IZrxk6qaM*Wovw%!MS`w%%k0F8V2fBI&8)Z(rf z(Ly}mYH_e&NE6xkf{xA(8F$6F*B0XZ&UXckd$f{iT{=3ua7+W!rt|p)MZxj>+H%b= z9n>K|*H8U$T7>5k+*9N3oaV$Xp9Mcpt|i~OAk9JG&f7uTs-n3BL-h_;`5EguJ>IL^ z{k#wl{JU~EYG)j}iT=0y_7!VL;(6n5k&OCAI%sy}ywcc&7-4qwfq%SxTVLKD z*;`fat*{_{Y*~NV`9D#A?mN*)a&}4B=5}|_2$Kf)k|Qm)CFs0()(?C{Veb4@1 zXM^60bN!Ta`%&Cg3h|s^OTasQJP%-~;&0yaNBpQy78=otwAE2MP3YZ=IcL}t1jZC+ zS;XqghSD{yXH@&l<*RY)Wva$gy#n!0dsSMP=cmfWVK2cG1fEOWa$wtL99!)dAAKQg za~JVlvnFc$(M7=-H+NKr&Se=Z>+2FuEO3$$;_pjZse)>E1?l}dI%gcr73Nlmvun=l z$a&-1#WT*pp?UtoTe^nr;hbK?Q^y*r8MnU^T)3pYlPg5Kr=qd#E>!7ROS}vB()b9= z!KWXpMK}q$Y1rDLeJ_M6UbWg!UM$00p)^{EXS18FuS92qvJFoLxEA552+vnK1#@D- z(+?u}*`8P!Ga&UIRkCHPW+%i>CWvOKbIR_dNJ{Fr`LQ$zv6~R?dC5ITxrkc{v6T>b z`av|mw}?-EDd@6r#z@ul`_1LfQSrRz{u0T2t#Tx5+#$-PDOYU za?epDBU2Br&@wVS{ook~&px4yOmX?OjLgW8lM5UKosjGv<;f=1)g`3InSAH`Xm<0D zB36&%o^kBT$$eJWG5>4R;AgYK@v}vc{7M<^l09wlN^jFB>HSf}YMpquS3V7%0Sp4s zyaTX5M%t5?=zB(d_v)OfV%mJ8)i>qPY1mmCVzNn4?l7+G(kgLjls@B_J@vaQuTB4f z_U!!E_ulqaDjPouQF^C6-Pse}=}vc?Tl3?Ur+83vzhv-r)V^iTY0Jh$?}ePQ*2_C_ ze^|q*Kh=w$ityxRpBZ6)?g{eL=mdnaC#?Je8*hGrMRRM`n7lUlDV8gXD09w$_6)}e z`^%Eab(i}2YcGf-p6)DybG3ZR+FWY(*|;`x*QIS#t2LyxgfnOUD&h_vzBtr|;<=6V zT>m^Iw*(Ek{8ad)qcOqrAjV|%9}Q1&=IqE!Kh%d800pRsJ23 z1;sBoXU^l7lAe3xnn>~ZouFyhLAko`j~X(g=o&e0avLx4lxohI$?ryeF=TZlyB426 zPhD~zIb7N`k?f!Mh9$Qr*8GRhkYDw?QC_%snCHf&H5*2!V-a{twWra1=RAn##M7T^ zEgH|YJi|>U<{AIpQF&fBtRFDa`4uv@W#+h=@^=a~%C;oO2ZYa_r!AUqv{?y>`JG#enJKNrF zLj`_Gowbn_Lzhsl_Fb_$`aDUaz9!woq3?6z(kMLr;+PNTtH8`xcz#6$#w0|;aRvoj|&915_OZ%lc2s}F@+Ri(?;^4^( z5q#cBUCU|nit~Bww0-$0PTT!&tnKq@+dX;j*j`g+%vjKM(A&&i(z6=t)i4x~4?a8R zM@p#8d1jc>dRr&kOYHf4;ce}FeO|`1`Xh8cZ&qaSSG%T_y?<&_( zeOeTG?GvOQDDgnF5DSLYR&v<~!F9Ax%zbi|KldEgJ0}%lyeJP6qE6=v)#Vkkz1LfI z2<1K#mV3Hif$YSQyoje;v{pT@e88LhRGNcuBrJe7t;henV-OMf-%)GMj!@b>i++0d z?k<_6;ayJ}UfhwPR0X9iI*q|3{dkY0A1GBp1i!b^55^3NNmx+!g6Q&yQ%P|U&C(V( z4I`65ISht!WimV4HrUXga&>?uMId`YiH?h?kbbech5n9|ZVoci-jA@Zh_&I490p<% zV#=>=)aUf~OW;az5P>lXL7E?RQQNkmb_u08nHOnIHSe!cJ|jeRLU0Wapj|kAVT9R{ z^Ly%@F-;Tcqe6t%M;Td$miyRH{NBtlV~c94UQ7DPx2x~jWMwZr;}{7`PJt^Ax^`zB z)q7NLS+#9O;C`{Po|J7LMJ(5d`|Rw&6=NgU=8DrWJ3xN6Qlo1cRZ+d$_mz(|WloWe z7ei&dRTN=g~) zxoGV}3EOGY$S6*WYr`G+oOm&BkoQ%L%bAtgA!ecA%Hf>!Fb$dVX)`~gS1+o`GDi;Q z$mimSM9H0vx2(XXO);HTa)**Ml(5B7R_ncAMJgB5o<%CjEu*J z`EfZ0c3MIuZyc5!@98ugOWltYGQ|9dl_wg>3(p82UtBjWj)?C+_?(G|RI-@n zn5I(6IrQyRa?A;q7HdI>=2I`0Z7N)>9@@Ft+3#ZQivDYStBHHVYbu{TAmw#mb#P>T zC}DHuj9d;~2L4w|ol*|%qx^C?tUS@K=b7*C^%h6_$xdpsx0_@D#m84iigOdCj)<=L zNOf7QqA!0;f6K{Lm|l!oXJpeS8z;WF3uXR|09JcswT}0>9Yj9R)Tf z-mlz7lG;u8UnIN#@j`mZhnxJlbKVKLLx{k76Cz`9imbM?z05qG6QC3jC3h~frsCLs za{02FQhw1VYU9m;oa7`(Adl~nvR--v+3Ei20ZJW917h!dzBW>K)~f}Wwh&j&tSR>l zZ7S=0Qi0|ZD>uYk73GqQbn`or)Xht&$$5rNg#K{B#(-KHWZV2(3_Zo4*))unhp$w{7vZw>T zk9p=$D4C?aPeCPhQR82{mJ>RJ=kv_6nGwzv$`f70(yf)%U(J58BOCXKRQn$Hb{12GjOmZ4i z8}^ikT5BFr-@acvGD{adj1cSBE!c1;Ay5j4QcjGCV|Qgs#p8$@9Fy;%l+*6?A6T=~ zYyQjVVDf4$Pdwg@WT7#crIl<3zC|x1ndRgc`$V56nI)DSOUqJDD=!^Tt*X4fq+ihN zz3W0Lr~P8(tt>CKX;05+AnzG7-Fx_RDHoDPVZ!iDk<@Kp6`)krm88C0EmhuIuq}P; ziB66r73*#HkDL7L?Lj2F`ByX^AL@`SpMU9PZ|YKCW7y_GV8O1mc|Egyj*mRev5-=XEsrd4tH>5ua3NxMzE3M3i0?a9|Xf+ ztgYUurlKE|{VT^WEzwe7loT6;86%|;BL#LPwgTw&zCr5-bWIXX& zv?~!dsc^0^^~{Wi{M5bO!q!B2vJKC(55|R|Qo7wAmp~JLcVn95zlRFgF4g2WxqvNw z^XrWacYu048o3Y849|VmAwmD4<qz`T_K|@S=)}3i&KVfWJo7H&o7#`a^>6hG z<+~_DHqSh_8(A;+1zU2iE0$(v)|p3bQl${()HojtG3v~YD(SP;n-;OAL9C|QTu2DA zeFR>gi=IBay()XKd-NCTyCx@z2#m>fEyF$7`tXc6<>Wc)7hJ*+xk^ zO1X)PGwggdtE|X-r!}#0fjfxtvWz@+BKKo%!Oy?wFAsLE9YhTj00|LDrwQCgqB8E_l+B(4+JmrRvmoW76~Xr;whrWf;rbQIf7D^ju^834veunNP(= zLQnlULEf&pI7VXX%G+5&Z_d;lJNGB^vj{CoCGB*0`sMfLY*6XbG&Nycn!Nk!9u6}z zKe8pFZ76aItOPoT=H1AuI%?n8-g4{fk-(M2TS>YrJZyz{A0le2ouv(MNVxT&M$AJN3DQO07#mXNrEagJzCfvp8z zaZZP@p>_b8&$VPLql~Rg<^vx_tWAuS*4y+LLAEl=*vcd#P^NFatt8f!-Sz4A(43;8 zNN(J2#`F(HW|aOFF&4y*767yZ36eWOT-TWOGHL-_G_v$YFF!NR(sOhL|DQAsh{!zm zY|DWy*GsVEh5=!EzP5udUsp!Ez&!(X6XqQC=ZTX6{dsSOepon^LD(G`>nyBp;>n&Z z*614MK5V~~+eOIi){_g9cnX*D^!YWGf=bPa*B$#SB`UvP@CZ*$cip*kCVOl zoVYy>>O2tb<_a|?h$vlIJx^lz zTrqWa!??x-(QY|RuJ~+#IuF#!STxUx_Ln8=-j(fV{<~RNKd6;KZ4LF^`_EAw@&<(K z6lz~=sQd!^%;os#zH`*~r$AUVA`)`tlmqjQ=+o z9U&Ic9MU^H*$IJh+2^tX7xB}EH-qu#HCFeMRtEJdsFiUwKX0ykGdNtUv3i|)J0dWY ztCfKkRQM%z?L0r_y(UzSL>peRqVtRu9qLk06H_<(YTUZcrob~C>WW7E^EB;mGr}}b zrxV)UdB*MzX?)h^))6GBg}h#4rP{s zky#qu4abF{%zKUKJK;cZZA>m|;tf-e>Ax^n8nNkz$D+}gt_XEPZoD~EZxbRKG4|)j z($}5>VbC1PNL6*oH*9Gxe;?jlEiBsV>WcnY>u0WP9$Gj#nhh~x=1BR$9nmhgmuz;p z=cqA3trOX+k?)<`p8vLU3VB;#OXyjx2KpJ~wGMSfs6!%F@uoC+faDA>JjrqfmSAgn z0?HX&gwZZtdkXE6*;Au_hcp;Xs>_$|pPznxgZ_%`XjwuxaOGl}WjH<16~(nq)Mw@B z^DBTX!|a{OdYNn=bN`I#rs9a~*_GvY>uPpL`u--Gxq^3j#8WW6ef%ju&~gT?!Kt=Zy|+xfw0EqJVH z&4!j!qAqOb@p|&!2RqBFSLHfsV6I$lJ?f*Z-icPrxHikRuRgs7%OX&N#Vbx5lFT>X zQB!W4Gq?+Cun=LjPCE$k>V7GYkd83vhYlVhutu>=LJa$?nH)8+o~*F>sH^h=T@h-U z=#<*Jzns^;y8PwdC!(mqvboAB*ga{Jzi#-tNLIZ!C|BM7JxGqn;_=IqUjqGoJv z4*8I}c~Xcl+B2QTrejxlyDprlI_9s6V(M9BNsuvbLWpqE7HwBmlM}1A(h-9pyA)l{KoEby(qt^6!%PN{*P{9qcLr8r(hJVY9-lOJu1 zjwcNkYpPP8P4!t*g*rA@ulBoaAfFgHJvh8eI=NzP)}de9`K--y5nBjR<(}!@Nzg11$fZ@Od9X z6C>V1jBfmMF#M8AYJOMZ`+!~zb!j}hOxkn?9eco|{chMBd7K+9nR(_h=<2-g>Q-I0 z*xNruU>fE*9NQR^cwz2x?{VVMbBQo6^UMg;o{@g!N3tBK*H+z1(sQ1#2JY9IIMkTA zh#t3}9W=S4oysD%IwDZ7hG<*6)aw<7()->1VeO(W%plB8qm*WXkraycQC6Gf+H4pM zr-53s)cZx;&mBs4Yf@XJ0rx2GJ_T zqTkq6Q?AYHr*w%-DBgLQXYJo-cM99I@cYDES;?J2Aewh%*N!FI_Qo^Q8EZ6S+#W_> z$GzRW566m_*t~-}IAmt}fCg!ywl8zw{0JjZCy1Ixj@kTJIv9n71R3NQoub5`kd_i=1#My+1#5AJ@ zftu0sy41s@OT8;Wmx}sRS2Mc#qVx@a65<*{pavB6sxIQa`x>b=^tb!1V}YXq1?wY> ziL8WXj#N`8MZ45Gu-QRmRd_%2fXOlk*Aj^&6RPNy;`RNBTZ>;1fqK=DjT`oKX5-=) zMi{vUYD|TAl+xff=@mo7?Ue|@av#nW>Q$}2){p6HX=H}08isOpsl;_wEB1Z4u4#CO zg3 zRJTSN$T!9iQx8Ji{?I?*mt=n^4YOuXMy@?_V8zQ6YvFdg}HToy<}#d71xZS-q}gp z@kojY)J`MXEjfmAG`!9Y4F9V=EaS6?Zkvk6JadzWKV>}pr9Aav#CrJSRddiisQ%Kb#(i?2{v37YsEKzi zc<>9R!K;u-+x2HT_m*mL&ID@QF>TimJt9yqk7!#{+`lkX#>iyrdkHl07QtBbM3{_I~)wstavT*BIU0Lr**7nt)u^jq*-ty07!Jf$vsQVk_MGbR+zl^wneBQ-$ zMaVl4;^yD32zE7|sLtM<7j?e0eLb#^a#;j}7w!)(+c-=O{*`q_I#-;=+-WP~x*|+l zh^Ov(Ec)l9DQaJ^DvEJsk)L&=k3!yo5WimDNM4)WNVS}IEI8Cf`T2)$33;@N6nti;BZJ8QPb^?2wW_E(nU}>g8?ayn<`HL28cz-p^Us*@5&$1ZG=#6yw4&G2v z9-%|)*1Cff+qe$QN%bdOtaHM9Lu=1#PQ}bovm+6i4qE3GXIaE;dvBp$!U*=Ji*0~C z1A7bw{iKns+nA&u(}_tAk|OMhNsq4aEt9<3k?L|#&r9WVn~!*0a>H9leIV+ud-v@~ z>W;kyyB)jRwSi_hm(O4|{Jg3m0>9Yj9R)U~9fT+wF;ITLs*=39*{t-; z@tgeKPp^yg{To>ibP@(jT_MgQrpU@3P35G|I(YfJHu;^C*M@!)F_c@5ZM&MudHw3i zr{6moAX~uH6(6dB@5IxN*Fqwn;qB>zTJ^6A0=9c`of8I>Ku=%LB z^M-l~zaWPJ%Ou3mug{ggZwR~_tEYz}$}kcT;f~;cZ4vUf6C1p|wHjtOJaTzjyi)KKu zRNgQhFqC<(*YiZZfk9v>Ev?r)2fAO4e5>~&-|FWlFd&f6;P4!HLWuob?GhqT(?n-A zVkdmDbHRpMB*W5r@>HK$2*`jibG!C!*sf8}aNeCpOo-I=o5C9kGTVF^B6$5^&IF?A zluC$Kq6;>RA_VH|f#cA@tWj&(3Vq62mnZSB;>*~QfQe}r* z4o1lW#<4|a)ee7NfTH1_CyVJ@3t?3hshT!#)WAhMltLCifBRfUzhlY#6UreZQHi)q-^vpmUVUIY%A;K^#!nh!md9M*M##V?h z@iO96xzY&sgvcHdIaA2Lz7%q=K3JR^`TQoM13 zUz(}yw##!rG|nw~-A;6Fi`~44F%ge!-us(!pJ}iBLFCC3*doXfLEZ^zGv1sZ{b_AG z_3prq4kHC~lK=HMf8p{jVaw7uf9n12(;EvC|Idd{qgI7ie%xFs?)(d-t;WfkZR_Hm-hT${L55He*JsZW( zGQ3`q-B0XhQj17`t)}cSs-LQ<`h>SZX?9Can1wp3|dRo+|^m5XkXCbojm;Vlp9+*MsQreH?LeXtS>4KkcjPUr?(YL&)Sk zzy8mWtjcFbvf36?u8Qw(q~_4ynXkJHA!G$**4-R2Qd}X}r*ql!T3U;oqLh*Eh72K= z&?5KefAf75x4bmf|GhN% z_q5?rhdVU&KqTe%;UObQGhq1is`sKUFT+YtwZ>iZh>TvH5unwlo@hg{R^P~X^?TzF z;w0V9HDmJp^q1ag9er$&`tbXcUXyRhJ_?vl4*Kq`5i3vp@Ufa|+3ihb z^G}JT*PWP7G2ee~PLQ|6=Z=ME{Sw~Yr`yBUu8|b-d&_zncS8kLNK$9|bJE~YmJfvp zvWYuo?b;e8iEzB8d8c;l-w{=Vd^8R^_Ch-HlQ2})?ky4HyO9uHQ?-?>-0FPS!9zsK z4R(iX!X{Ge7i(i5#Y^ z@An2*Zt5pjytLU%3}x9(h|ux{J!9+kFdQgdyIQtnMgdDEP(O%lBV;#awf{R}ZbUJ0 z^Z)t;Y3}sS9`}31++Bh^DH=1o$~<#d3dV(LxQNLQ9tfndkOpp)VNmgU?lPU;BCX}M zu{&R|9OhZ?6Tlw`-zyXMjf-)aXGS1rDrDBgZ|6$Aq+;Bc1@fd^1m5qA2xROa+SID4 zLlIZQiE=AalhC!+_dTL8k(W$e&_J+8qTkd*Uar~aptnO zig(pigYy<_cq*@-)H%^yE)1n}o9gF=xuu(ERI}JkG@L8zKM{FdoVH~*RsEg zBG!{5BaBS;)T}UGy-F#Rk;5Ru#Li<8Sz#$R{1z$A;X5>{6e8ctgy;WVnM}#QjzmUp zW-nk>3Na=j?m2R?+}3eUP=%y}*-d_rB(Ggc@g@}_ckI+@=o!K?8WE8lEsbRDUKHjq z@t!!#{=&YCeVSUB{I=axTl=R*9+`RO5#_SNqCY*BzCP{`z@|ZEp;ButkU?|#+}ofq zF7wQ3Aipf(wq|i|+#?Wj%3Q?beR6_=`%>gn;ZENoka>pagcbx&8X`(6)r@-r7vjX) zH@)k~`^D|#F9W%1mK#N!GVm>9(+FD-(ilCvbKLs{#za1&ZYCwAL-N(19^(g0un zw6m-`Og4PzPe#)Pz)Ds!; z$nHtgOpf?}mqCY!{JmZ5Eu5$2 z!yzjS`Cc{+>Q@dQF8h^~IMWTeaUoYt8g?6g!8Gi1Vw)cq#Gs3{Ei%E7Sw}txh~wqO zI9|w>^Ibhr=}NGxw`=TM5N~!-?I7{4`{@xqP_V6q^4ACm=AC%WktJ zartld&*6q`#xu2f&(E~xQ*NI7;o2zw@ABd1`r7vn zamv6CkIfU$iO7`e-Ii{s*q9dP1Y@!Yo*7oW^m6>RJ!H_iY2fbFUg9!&{vOat9`AlI{o2e_ z#hzQ(nxka&QYT(FropqDg(ZKuVNZq3JS>OdW#q2RE)ZfT@iBgxwxH`*gfP4X&O4%A z1o^X+-ET@-Pd+Wn@%jua5MvUe?Qb-Lh=Z!eHZpVn8?X?&mz8OH>U74lTrzLu=gu9>{$8$sy5%^!_a1p>;}5*%+jFAG%(H3a6u2zC%jOT3DQ|r0 z{c+#34zCYGA!Cnj5+pxG$Lbe(E1x)1;TL8YvN!&@=`^~)XOR%Qwl|YUwjA}ozrLQr zP{`7A5m(c_$eoYpctbuTj}8zoj%`E>;Uz`9pPw3|>UWtDAd9W{p&UPJ--Jlk5n96? z&fUV3i@cMU-lN7}PL?U~W#Ja`d*5qfh(I9x1~tVNST zgu{&!f8I1L{qc{-sXc_?^~3P&_&I;5&K2gCX3^o5Wt}D0s3&^O2r#bVhso>E=Tmh# zfK4aF@5H(LWRp~LqN}2;OJiABxMP7(rKRNYr2CARhIc_AW$P9#RPjm|fuUG?#>_gs z_bG2jueo&?N^8%ah#{7Ax=`&qJKK9ScH3r2?VYl)8Xb?O<+L*qe+aqhO-Mi`R4>a@4aPD;8Q)yM+ z7v6==tB>}cbjO0;GV)ADknv(2$sD-iyW07w#DPPe9kS_&K~DDw$zOAV8*WRHN!{sY zkh$cIyY81EAI`A!T(;r!#Gq^RWQJGm1(!jGoI7OGk!K@f>FxbB$2&nY1cu`D*w0xh zrRVbD&O5WeEO~rx`k;Z;9nKxL8nX9<*hpOJA3i$jt=iel@oa=?;ObLe8*aFDJU;lD zM-h)r>vhQXkSw)u_G-UxzbmN?NR!`kwQo3g=+Rz?zP~qABky=82$s-|A)%(4nR)ug zTf@wA(XS7yt!|+^ay!TF$Thh_W*){A*U^Xl7xR0na_E^sy;V(~TeZJz^aSmYfoEsf zbY>nRNccoyB z@-xp}CHt?5A$~u+%=>-v1M^mqmz{k=&8S$fE=I~7YW%&nli=+fyf zy0ESgVV-rU9BL!Y-XpHToObDkrRQ?%IFx4T=~IS5U@t+RM~I$K_4S0?vwtD})y^ke zmL77kT*e-!QA&t!LPROe(jzH1$px$pH|#B!?}xc6t$S8Hb>#ISzf1_aH{3J#hO-94 z+#iFyK17FilY8;I$w|jzymBG?579PHe0E0eARpf~gH&i*q26d6-agI#rvJV@_N|>bPpe{?xZ3A`j6% zmle3&!IPhDkIu-RLiI$aG2kI0!amc^Nh}>(E&cmg8W@*-E-OG5BAr;?oS)ugT|4y* zaUhc~xI4~uM7E)eu(g$?kcWtfWbdvxuhWL-Hb+G9(-qgK;tp9~n zY4=Wdauu$YFLm%+G{H&6;WJN}!eb)Y7M>p$0GX|e=PRD4ph#g@$ zEN^k$jTPmSbq1;D&smtx=huvp9sS&aJHt@OXtan^*A@3s&CE0$NM?U#M0Zn6855_- z^DlcWdeOnWkaH()o*(XnU7nq08|qm*k+EeMjm+5MXAWhUhkEz=)IY7%uPsJ+^X+(AOWHi3b zi~yO7$UGcq z64{M(D%w>RTya%JS=cWvw1P0qMPxK0npU_3&yo2Z-=Z|McS_?ijrT;B(U|DH)uh3p z_?g>)cTR@sSbl$H;^I)upk#zeL$_e!STF8t>lZHl&A=cy&Vok0QBzq;^ zqd4oeu(x9x*iIy2p}kd&mMzrtXXH7oNlXJ7ltRoWo@JBVYO2wU1lA<*ERjJe#9HE6 z&Uto!aCJJ3!Pq@foL>yZJ!bbSUThEd=adKJePzpnDkOVF1cpKeW%&_h_>>sRuqJg~ zCGwCB!VSeUgJx6G*?{gP`~89L(e<%=$%?nLFE!G!IDXX-QM+cExe zf!s=+(d;T0)NqWM(2`lRWSfmy=7wHr2@4L zh+5>e?t$Ua^}p}eH-H&U1M)?=k(h(##qy3)j3H zZBq{nA!-*Jui^W!MZaMSY3u%`pVw4N7cJgQtgf+{oSCa26sX3 zDRNS=rb&zZjZ{C-tqX^@b?^`&mTZX_j|Iq8B^&pzq9KmM zJ?V0mkw*t)u4Z)mC}O-uxGdJT$Lp!4gjl^M*TYcAPjwNu?XIQ%c(IjS`|B?rk0_&E z(&JI{QxVPM!W|!a9Ps(WMH}l}Vv-Zr;Gx*<*~l<8&%75gOi+EAFr$xm0GeAji{_Im zg~(z>4{dZl-&$Fk+p{x1{3Bwl;*q)PqUnZiX>RB?IpNe1fiY2@$cIq97>m_#T^V8W zuJ=@$#j2lk#~ldXj@{a0n5(*kHk5g<5y;Ch@iM~X5bH`K@b)3(w_?2Jy+#ldFfdF& z^Cc`t%xfWsa=C-wK3+$?bnjc9ccN=K7AtxYw0zkA)k|#8Fr&7F<}1>lt4GF#`g4c( zYSs^Z3j)p5F|I1z?_4caZH#V9UrcuoWcS??+8FTN17`hTKLaiww!~|o=H5Tu8&5Y0 zAR{)b@WY6?N!vx7@pQ9rHHg;jDb3nv*s-kD=jV*RABTPpwZ>$^+-B;zr3<b#+=W^Ja9W1xj<=p;v z&CcNJZ$}5m>BjU#1afOF=eB$c8rH4L#x;HvPBk-Fgt0Q!^NQJhm~qZ&@#tK53)|OK zDMU7|%dLItSVKAE_IH8{Nehsfe~fy=EA&UB7H&mEv`vB2$nX4XBzdt6(?I?%%1g;Z zbLxe%@8e6ntI1!6PJ>y#GzQU&B(Q(UU*9FI01?Kj;;>VKXx6~o4nAm@DMlY2-iICGI~DSjPhRaq)7BkuKt{x!!jyxxvdy#u=pULxdoQKT&-P0j3lzmR* zz2X<-)}n{c({r6I?-{wyoGbfdJ~huH+=68V7E$&;*wM&!Mt(E0rfmvbyZFW7Q1^Yw zFByDYSnDnV+XLGG(+=;ol&_ce(d`lEG}F8b9zyLi_p42J*E=Nw8POJz*d7=vH<^0= z(qlTnkN6dzh0r{G;9*q z6|$ghsQd!^94kkhPmPFE^Qja1mBLV14y=XKv!jvUY@ZWm28SJ;T!RsJE<4)R6|h@SrvLE^^PB%}M>jTyxw2kFO)2<)m*tJ+KyI}VO&hdT zFQ#1(oJqODFPJNrfsN;=zGGgA`y)dRH8Qgkb7k{OnmsCmKDW6DJb9Vg<+T>mz_dfX zfihmw5Me^`>TKTYmAdssE!0tBh28SJ%h|>jMBcLywcp(ve7kk9IwXnR27XIZXOa)o z81u|wV~c5jE(!)+c8{uc<%|Ho93SvnoDYX=Y+64mMS^2<#;E%bg2_SSFyu z#d~k;c{25@^VAcyYpLQu`I((=4z2i+hw3u0FJ66%yx`8x8wQPVS>DKd9dRecL{|9O zukFeY?dYYkqesp*vtx~f7WvyGzxuU}{D}U#sV5!I_QK27_`AOy8Cv0EOvJ1?(nfBe zziU>z2-IyOI`lapxd(q3^ZjXZf?Qsgzk7hVc7IG;uwh%A>#TX&#s?iUqLC@h-Vi&; z(Z7cG_BkwXL};$Fj){COpV3)9lDjSa%PKBs+wkw0BkuCTcMM6951-8Os%;~GWZ>5o z`J%`prO~!e%3k+Q_foqxP<-1a+ohI#PFi;0h+|Be<;`{VQ;ZjGIPF*t!yVMTaP|o} z?AOpIpu-EtP`XU1K9@;|_eNeMy}d6)N6(2kyl`DVsXoh%^}VL4{-KT8gvh45W$*i{ zLvU`1bP&vYPOab0FR8txz~zg7Fs{06+5N5Hh04Ce3uphUnFHJV#m6$Ca+)PTb~?x<6FxH)vLTAG3_A4$*K3qyox?RA#FbE7~ zSl)VHQuy3(`NYHn;SSk+;k}P}=1?wA<{@HW*PO9n!&{`EK(;cnyv;1CnafwN9vsdk zM}?53L$qhr?v!D;PW(ml?*qz>*7mkc? zI^#UoTYfb+Blzbz&Dk~x4267glHeA%mrKfWf^~Oj&bDEaBYPZkE5tZ;vmEpE3~%!M z|9UHK-sG3e*%V3n&z=Hge7l_OI)7a*FYCF@JKp%XHzJ#6vXl*x++KSN9L_d)a;3HQ z`n%-B!k5yUsa+n%h3(I?9n0HGC+wlxeP%*yc|nz`^42G)m%uuK>j$PMMBY`X!Tx)+pKZRmm*YhQ#zc}Lk|9T9OmgIZpNdJ&F$r;NdV2ai@(|mWJjC+t5$1Ma zBak8J^Xd?4Q( zS?rvG`MGx%XD3yR`@?k+2m3De?w;91T@&6dYuW0^P)FXo5ZiX#VcZhk$no+ib;Bp|powc%tFHW3^DF?kJ{GaJa&L*8`pz)q&?mQ<?~N z@)Z@ovl@B&EEhc;Pv3I(%L)+f^7N5iZ_`7bKBqx84e7?F0eSk!v_}Shn5%SSJ%DLg zbXkFYPLQNJJbg@K^anNL78s!wL}2Yd&W-nKWcDND-}ct1|TN|DXP%eV<66QnNxr9@hO*7o%^W7N^L%Flvw;xxLVr&n!@6FW? z>t62wwXb4i;ByM@K9TMD^cgwP7QmdT50VG|8;`VBV|SJYZd`gd8TW@w_Ji7zpywxP z@}keu)I(SIbgTz>AFbK^iN1=L4Na99{X41R0SBF%#(597a6|{XNoPf98EEVfgiu}s zd18-LSKT??$rU183k9?{usO+hY#Qu)W`+IVh#i3D)AqVuHDwOnf9?;M)g{s1z}OhL z)(ZbQUPJy>bd7ppVjGVoDHG_9_3T<~S77Z8+?cW^pCKRZd!u^z>0#v&u+z8p4gEeI zCr5Ui7M(%oLFVHZ-!?y#Y$79`nevwL&pznM7kzk+G8O~88kn;a?-R^>y=Op#33WOI zzZc>^7fzJRj$RRT_>OipJAl78YcU23 zV{7s_*FV@_Dwk$ETi>&3a#h7k2V$xh<2?Y1{VDsw4GZ? z%Jm%_D+r92W6D0*Iks}*jR?jPfqR2_=GMfti7oPR3ps8=hT_t2C}Ra-;$?(+uY1WC zbegEws)d|#OxvHc6$F2AEEK?Ufo}SvJ^P4!sd|-Ygt3C)^T6EiJBK@R-p?D02s*1R zxkhG?%;c{ts0AS|Y(Z>UlGgp&MV?i&smf|cwlWach#zwO)Ye^nwnX8Wj2~(8GqLCz zxpin|RbiM;!+a^3zRK_XcsGpzKhkuPq<-~7(cWMg=~&S6fpapjYI=x3`vzJE>Fjao z@nyj=@-WV0Ff%hFJWXJza6Asph@HGv(WqTRqjuESE&@Zj_6_BIt7~fqHhV-j3F=0jmb+hN`BF%0j(o+?r$dRf!oP?0Bsi#5k5Os5I>W~ z@{dq^Np=iq1rZN5n&!u{jFdY0`8o}QC}#x`L-2bSaggG#Kpw{UBxJsz9fKRwsNR=# zP5Ita7td)`-U=d?MoOcfA{meVnUOX5J<8QhOU_fP>2Fdq*9rnHAM(#Ch!|a%5Tx@` zL(kJXFSLTlyt6R$PtAHRv~LJy`r)j=GJX8Q2)0%*2xH%%ePDBqh59J4k6<*~G#Fb4 zji~!Vov!OhgN2p|XrX{;vduWyM!j&djr?@ula5F0{E7!6onIOrT7x>43!-Jp#i~~3 z#q!gAo1HX7m;I5tXWbcEUAT4=MU&d8owv1FM~Thr97ucgUduMKxj z!Wb(1hr^NldL#Y0JCkVlK|X}k-bed{K7N%ED+t#*BJ;NzYXA5)!9C!1O&n|73{%$FX0Ue;jh;!I)U|sSY^YtYr!wqVt}8@f zOhQyEPE);J>5=|dB@Vawi#F6Jx~pz8zdWCG%Fn~ zEo^)2D9E~SYsB0vNfP=KH>n>hR*(hHHg|0^Dv@oD(eQzth7i|1&{;irte$MJIoEUS zF)D3|ur`!67-)e(?1b^1)j4JLWZMMWj1)ki1qN|gN~^0LS6nLJ&OhSVX7FratS($j zi=X;eQ2XwvFYn2$FHQYuzc||RCG(}Ps4rPGX|Tp+s0%;Qw+R}H0p4X8I|kmpnD=@o z^H=3M^3E1TUc)%oS$j4@TM4wlu(P3_8!da7`P3ydXGeJ7?jq_BJ6}FqR^+|bx`Q$~ z;ap)Fn(M674*kif;UDXAAi_LjIXEUE+K?9RI`YxDk9;&5yNR%bf1ilh`x&j(1>NEH z{Qlqs`Dpx;?<|gaF=s?dg)`!J7NZS?5XcI{TFmJFcf@#?a?%!f_cF#+{K}cWxqI1I zVCeQBc64Ca(T3m5CB#tXy+%CqaTV2e-D+>&=pK$u1NRJLkAe0LLKHq)Q+>a+)N6XY zwRFE&1f#8;MiQ+!6Oy0Z@H$yRm=%DnE6kd~mJenv(RLc^$){mEzh^rA@7QLTHG?&k z7!&P~ho!3Mu!HIQ`gL-=m}hq(i!-{XnD015D-R(eegn0RI4teyjzcU5B3x^Zsk9Dq z4{k}e8JJ%z2R~bnS(lvIK{aT!V8d&bIylxG7)qDP_>H=atWs`#c3X7vNnH+O&7n)H zZ9%vk#sd}23>gj7kE5msPf|HJ@5WaY+G)7f9G%)FtDly=9Q=EkAC}zuzp}O&h$c_a z=bxiKSpIf!|A*xf)`G*Lxt30~_u;W-_C6)y?8EZ8zTr%ih|uyRULlJjEXj05SqiZq368hq8IpOT%+JAgUGh82<;o8S%IBm^=_9kfAL8Ta0 zTQ(!VQFoEwD73-AczGr=cRrEjWQ*4F^R^Fo?>tSrC9?CFu-pqzcW7gRwkKS>=Et=R z8im_SBe}C9gsxKPBHrk5p*$e6y<0Eo;8>+#8kjbDRJv!3+VS*q($5o53an4eGtVDr zNkY~fWUDse;R7K8OKzSSVbFTsJ#tg3{Htfx;0HR_W~P@#=DzwOwE=|!s}whl7B$mk z>7=itUCAE+A_Q2aAllRu|F7%H+-ZS`k_uFB6`l**Kk?3DYl%WRi*riQ-h_0J$FA^p zkvH9r#Q1f-Y_A-@j~=Z@NWyaOW6`7J82=_S}5rG z%H)gg&Ww9XckNiLjR4Pl_U9t3y@`t^OuGHHbdFbqylyGv{hHm7{ROo53)$ zmBDgg-qCu5JhmvQCXnq!i$d4(rRc#|;=D)4hC)0uaH@**c_y4oY<|(&g-dQ~gmd*x ztQVd|@?HKiw-Z~t=-UIVF$AzQWdspp_(3E6~$24 z<_4j685fbB=s;-~wX(yE0DHs2+QerWFeB6zqP2<;mG7>szFK^Z+8LPDWQre5QG4NK%}dbfPe&YcM=c;B?uTL0%|}-5EX<7sFdG1J9qBBC&2gr z&-0-B+@1H7-PzgMne#fwdf?hprwQ>mk9L{j7^#4+LF&e`s@>yrh9nP{fmTHDKq|CJ zMuvvoktOO7#nD#INFeKIXf3wMZ26^o%8H~3Wn}k()eXAl%r1(EFtDbNqj;jtK7H3_ z$BW%|&30|7S2SWMAPaz;j_apLWMl=>uaci4j-jCa5g}b;vNFEd24Wihn{s})5kmp( zKsc6ZYRXGN&qnC$9`j`?LGMi`_$_g=eQ$e{V?lPsw4r}*YH!i&LKFMmvo~Fj z@0xsJJN?W`0pHqH%!mk0Xod6Y)-)&WwKft{u?=Oz*Z*|(E^bUS4J2GV@&(l<`NBP1?~Gw@ zH~ZC_9m$imC@X8w6NUSn_!QyOMX?7O^bpTHc;0@c_*#==0V`&c|3E6mqfat{L_v;) z_YF|tQ6i{7j)iA3c`ZhYZ(g2aH`thy;VE*mkZJMC&LPE|Uxo@_r%b!;=6h^Rg|v&= zjuM?lij`xg*uN*;)gE~juRP*dLYxk_r4UQ#k-+F&i#u!(DvwWkavj`gb*FC_#+L7Ze|-+g(Htyv1BW6#YAq#as!m zw`xK~$0V?<3Z&o}Qx8&LDkM?#D9W)8Xg{jZVBw^uwCmbG*Y@5Ystn#GlD6?RSv-j=OF8`5wo+8{3 z|A7jVO1XKtqv+7-u=8k>t2WadT`ESqqzh(=7wWaA3v?;34tg$>v{(b6^ ze;@AEno2o|=W{kfIf>_i>NMGg^%b^+es`>2{K$ zPc*y2smyllD^o`#U2H3&$yYJ5B*=pBC~+vKn`?_jWBZ`u!d~7@%x|OOv4o-$0Mo*w z(!QdP1r8P>$LTMM>{ZOSr7 zJQ7rFB}VZed1`J!3S>#JJRaM7LMH_c`s zZ(=Ht=0F8n^P`%3lV!~&o9*6YvyH@vdyIrih#0wX<3yXE7ETZs)(;4v%?(m1NUNwa zw2yd9<+3ZmeBI*^1;d(IRI7NZ6{f;knF_pCNQNMZ!t3yw1yZ$A|1~)ldfw>GqvA~< z(_&IWm=~5|D#&jX$HI2sQ<`H@Pp3zs=-<_Iz9@O6^A=ezbIo)+Sb?eZ?@F<^T^Tv= zSWj2MJJn3MumYJD{ku|-FK>H5)1q=-yH=8Ss&(8IWLivh)K7Pd8%M7@Nn2YQ3M5}p z?Ok2A-!*oyfR*7LGaZ}7Q}F6X=I^Wgny5im1}PY_t^KLIleegatd&NQl!0(T@&(n@ zd!XgKW}#b-`6Ay!k^XMn?|cd<+5asX32OrOjBFP;9ySVudn+u zXFf4;HHeAh{W{HHA&W%`AE9wPZScOUsZW2 zp{mp~;i%obl43GH9t(8syUL>R>3XuzynKVML4}!9#pT__oW14c>5|7YFk*H}xA!?1m`5?_hal?fJE|Y$g2i-VtzaYGCw(_sQprX@#9RNT>R%E zBgP1FKD>fdY|}S{80is{m6=K`!0Uez3-CV`5rJcg z@HogUKw7t%?w2Xg!a4ScJ3dbY=`c=OBQvH7%?f!qy}FUiC-S=K`L>z;P|mh=;6qhjpFv}%Cd4XQ_dL)6WC zi-U^c-}Dh9Vp8lM4<0oL9z(sBc!S!@f{LBZ;zdsHY&-SbQG?(?YA5d5&Hk{N%Y%wh zDTBm-&xQw{qFQn3X#?lEXRsZ3Z@iI&-{)ucZS==A-{Rw)7$kU*&7rqinuw_TMiHsE^H)XcapV)eQ<%zq&YSszYXG-I9}1l@nID}K?M=|P z;N@zH5;EByQ^Ayw>LnZQeM6ky+}T|NK!rYbYY6lZc|55V->w%CJkJopgKObP?YPGW z3I{#0@pF5Uc3_LpPQxwC570cCx>3)$CV2S!OrJqDAM4*u1%JhPXFOsEb5e@yRIA_U zA2L6z){K#v)BFpM@3=^*&(S|*dyo>soGk0ND@A03t3~8X3yBf}wg;IX%*ow}ljXzJ zP8=$b`9T)w|7L!)H$3EjC{7%4Mcn>)qJ;1aAM?K2t&~u$pkhdnE7JT>L51Fxv#c-5 zCCK|8F0x}D#aKs52+1IBN3p`a{RyX!XIj>Z5zS;qWHa&cmbnHcgv^gY^H^sO)RQ*{ z)Du5_Zjwk=c%Cm}-ax-aaj{WN@r2`B$RcmI5DngM>g0WN-j_D;MSs+;yNt*uW*Ie} zZy^u9)j}+uMbTA|27*{AMo!ko7wgMDXBGvXB9C&&{HWLr%8Q(~%vJQz?$|mq?Rbon zMEhz;@Sp;7;(Nz-iTZD!&zq(0GbkaC9->uehgO^&UU_Dlf6wn6l0m2@uXR#;t;1Te zZLMaB%$(Q{{m0+Ae&~bDkMf_VEtfShOe1=nL>*!k*hxqxcP0@jAEYa57 zd=*ilExy&RWk-tH_vGh?V;7QdL*$B#R6q>Ts@gX~?j17Oey>9{i7XMbGCJNi5<`|% ze_(|CC4ZfLDVsc{L(CUUWilp6@Ob3QotPi~Hn@|*QzP@k^LC1VE_M8K$v+%YJIKx8RiGWkg?(keezX&}^ee|;dmsi2$7a!c-sFe==|p*L=UlsG zt2igK?^izU&ki{sBPZ+3XD`YA@t@lBDt~5BLa4x;$l5-qF4jY`9@o=xVbYji~!*SxNn^OJQ8!K0ZU zQ&Hu|%F>>fZ}&f0&tMw(IoCXf$pZaA);rN;y)&PzcaV>X`1&88_Ed+oie;U@zqV*k z@eQ62S;Q;*B&2qb=FuY?|KGI0&ZKw>5%z3%)^94JDNf4KR?xPuFqt1rh5QdO z)r(F#eTWh&Rgt`MQRLpZ4rASKOH73{5A9Zx1znK_4dvFXO#!^_ct4C9TK)c9;pB}Q zCgaF19628&-{%X5Y{bde}c7f(}RIVM-;oHNgX{;NTch6InhkNUzD6b=;wg%af(EP z1kddKKF$A_%+GRSeu@m8>>NI-*dIwGmLD5ct5<>om_M+Y@Cw|R2?wndha|U30koiG1t?!cc=X9bPw*{$29px1BRb!Xp zjpz;@?YtNadP8Tyzf*WuWg-|s|ZH$;Zz2p zWc7HvrO14(eW1pDv>IjQ(_LDq?i8>~rs9>mTZv*vKFL^mzNMiscqLGK_fPMuY1+w9 z(EUiLJeSJm2tH}gO${_B66v)vyi*OtQ2`Rk^QK!nVQOrNapUEJg0vIxQ5KL-)Y}Vp zwmZ-pC;W>7>=LE|j>)oKE7IBNKt$DnO$GQUct=@A4NK*(u%D;*V6Ua*&ysdb^t;xA zd?R|(?@x~nS|Obz513oF_m_>1n+h|PM?P8BhE`?7qrn(%Ce`Gn(!Z--vYjF+*QH3x z7buc4@=?euA;(0q0S`75S;2UBIxp^5`k3>@oVO46S7$n+1hXtaxCp(mt!`kNR{}n)pHDqZ$t@$VZuk6LL&u8ORTL6jf$cmHJEkaVwspE6wjg?~nZ5QDh)RK@jHy zJeEm2p~B;?^0SgB(U?1?1?5X>WTmXw-V?;hyA+#&c^RpwY<16h1~8*a|J%||9{+>1 zQ$DrhP7D?jRo*cx?!;i>b@Pr{@$1D$L||aJJ1_C5Ci0jCk%7EpZpWPzo|ro!^> z%oSl7-Z5XR^2Ex-^c&~Qrm+NB8Ki`ef}vPjWRLn=#X53ExhW1(B2~t&_9vEq)7^DI zE`(d*t&w>&kZOnS`QGnbt2VTzbm>Cdj8AVw8d}>c{e_K`5Yj{D7roAx^FlHR)#Rm_=Au*Ds;_MODx-hVr?M zrR5fDvq=eETotkl#=b!N&VRL)eTWo_YdOWio`Kwq*-MuEZOMw);^h6;jvAEEq3kvO z_=rjFek8I$H23L}?X+whFW)YnYfwVlXRZy|QzHvRs}Zp&&e}tLQ?8FCCJ^W*#FA@Rh7hj#onz~qXMn@GGX(~ZHy zjFF0Vv-hy_6^4`$5?$`|fsBlR!zNtLdik(+NR?e!b~n8JG&oquoAgdAF%4J5J}6H@G6KyQ&r2 z)^n}CoaH0mpNj%FUZ`(SLhAY`Dk*P&hM!}|FPy~|H?w~bV#oOyC(>WXP;jgi_KX%6 zwjvAZAzFK-C@~LzlI9e;7H)DyWL3^P8-tIo$U=IEdg7%r;+nkZy!l|H!4)AfWKu$1 zsJHVSJVU)5dj|G!?D@1pKfI_IxV(t$^G0K1XB3~PQCGM4*(YGC>l3hZ;~pZr<#~Hw z`L#AuG#Kl^v^{^d$OQ4<%+0Y}Gx8xp*PMF;GCxSy@T?CN9(RL^97?5g;^Rh%BSqe^ z5%#&3pTv3!lM+gMfTG|e@ARdW;@CB_PY)H}#AVtex0;j?w#ihS**8X5>8+>u6?7tNeQ6>d%k5gJu*Ddi|_e^F`D@? zS(&@O8Xo&N(KUP6P6o&zWPVIa=-Zkx_LSxQmo=yzmN(&jdosb740UXZm!D}Y32vjo(Ij_6dr$wX{SjnH7wcqUjnWYLEpY`YddSX!^(3foqkrjg}+9 z!`#gD?aloh3&yfucb*o$`4r_ZMMa6xXHdc8m1cn~>p0Oh8NS2LplT{gj6Q?R53)ey zRi*1dk$h^UJu7U1jbjFmgGlpO)`RwIqQU$~dqumkc9rrO{-{dU`IBi;u z6vzJBmC<0>6oV2%f`>;X9$n);|CU<5oBGwDB`58pG#5p}MRPMeV`V#_hP@vOTJHqC zf4i17Nb2ZgR%p^j&B9TEXKtqm&XB2AF;uJnYLHc!+uK!y8?C;zRy$U86Q2$&E=o2C zdTE}d2p((``$k@SscVpYnV8>J+)q|pXD9*~k~+xE@bl=gGPT3%h?@d}YT zK=5Erx;~f+4AQ9F13A)hKl*Gskmg;%iYyA0cyu&ekPxfvnP#t<|gkCuJiBvM^Qq2Ognq-Dz}l76@PYWU{{d&F-aXyp?_DsvWy zMgMMo3G;el!j74Ue%*GzLhd`2I0P4rbz3cOZ$Mjx|6*H$TBC&pxd zkP^~t51-M;sJ26^ekU^m4E2RGP{)djWgNnSTFZnWSJxq6y_dmQplPj{4C-@V0 za!i~OyG%+5X(yzFkV-;gh`hs8YU=znI#Omm-pU}6jC?;B=eOEIB!SS?MdC;w^OeT3 z?@S_jR1&Z6j_@yB$!+4*L1f8r%+SYNh9-v89jr%v_o(i>zz^wEtUxNsvIad+Mm7xg zD_wUtuiLOouG+%huJdKZ?P{eI`VP`PC$8ufe{v0uoMh5RagUGirxkiS*mAAl)mmhf z@cQ6&^U^DY$1WiygjABLNM1c8R63GRSX;6K_^G_zX>kj=ePc_5T|$-!8KXP1OUNsE z$2?wne@VhSgAs@Lx_RyM((@{3BG67)tEBoBLp&ahQ8oW?G(X6@R+@G~HSb_)x{ipE z*G}1*gVgL2-&>@T^zW)C(s(8Gc%{owy}@Im^zTaXZq6X*1&SH=D0wPm|B0HGMRv(7 zk7!v($38;^5>BW#uN9`kw~zV8VNFqbJJ;H5zMb)n%of<*+xbwdilXKzOIC}jdkgKP zZAZ=Zl7A?!W$mx)%1sp861gH|iA>t*ySsXdO})y<^tX}&{5?S4c>E35ei&S{QnV9N zP(-)^iKM=;IMtm~Ns%y5EUYZI&WLlc49pj~Da*>DNSOPckC5{3bp}y|6cx7b z|4*&Rt4fHKa`)B9lg@eCJ2>`6$^%F>A?@TTc!gzfY#lL@tbN})8DpaYX(yzhh`S@t z-(QVrBQI`AvoRHZ(V5?al|Jq#=5&m45^0?QS)xRucA~ydc2|T=y5{PR{^I<*C!OBI zOxlUR4YVH)Somv0EhE}}h_rbETF{H4wdL_1+Btf8s+b-^I< z){nyjuaz+=AtaoT7$O@K;))*set7KI%1WWBCZwHEZCOh;_7nFuim`hfI_mKC2~kZD z?a1V#DrNT(zmvb0*?%5&kZR&KMXX$|xH8Pi`YS44{L?GjE}eJOX?S&$KVttX_a0zO z9L&kG9$D5;)UOibtRW9Hc-N4XGVkEoIo-CY5yj-Au0FNVe`Z7sJzXX%MV>r&O6Ss7 z_sQQckhKWVPDocF`9$lb6c1pOyjL9fGsYmQ42p_!YM9rW%H#LP2jn4k{Rzb)BkiP* z-7y%-c4q*BW&o1g!l^X1#Wm!7S)qSdBY2l)4Mlpo5Zm|Dx=%1t6Q4oa2`MOwOSZnW zIA5x#Y)J7^F)#k+O#Y>{`ich%iUJk?-d0%jB^*Pv6Xt~!6@OW33d*u?PQHX#b;7xX73gDNk0+HHa!+hie$t z)*nTljgXrn@4zD>#Xr}o$Y~vO4WerMk7@qtaqV0Mm#w)e%gVhHCyrdSk^7R_~OKz)Ymo>(>3RUj)xzIkY5m`EeT>fp$r zxhozS_6>}4uN$&bw;?BWL?%#}L{;3F@uJfGQv>yXrV;KBS{)1$RVq%{gTa*>kCmeM z2IP<9p`@v?QKvS$@zp$X%1dRc8xc`8IG}92^vVrC2e%`tRNpQ3;{7}7oUx^t-B{xO zfJ;#Y`6wi<)Dv#e8|PQDu|*CF6@2VbP^_^BRhO|pKSWQJNmO~M=sWt!GjT}+w`Hm_ z{G@0{3X_BK)cm`th#^HMQt(&2KEubl3^S(z%bK>^Pcgkm=i0XrRa%j7_c>qE!Siv} zA3Ld5ul`#^-bepV&NGQBq^MF;9g2>ib~A~n`sE>cVyoJ${}j_WwjXKX`@}Og?Fa_8UjhJc5e6phBNv1@A6ug{G^hw-Zq{q(Yl^U75iG4$9!8 zfX5;Wh>kVn^wb(+&%0L~R3Pnyq!o=NrJBjU?==%|e=^re^_}(fA^q=6gK&a~ zN_4GS)swTX)e~!osIt=j^o4veVk)GotdGh>$|kwZ#HRH#jddR^1Is4Q^%UQrExr{QHX~?zZc{W2wlUW`$J+K>vr?Ly zqMbNW47rf9qX8*Y8EQw`P(V(W)oECFIW6ar*k)=$SNSX7;4M3Ri33>iJ;g*S zS7V>Or~n^@?Z7KSJ!xKFIdj5%J9n|kM`0@Fm_mL)t)HJtl!cGXwbw)z;G-aNQrzgT ze0rCZzHvH^lv#AG&b)h9d#{CbeSog=t^$8YZ9hF!4yFCtzMtG<&`xpXsI|1Ko65Vo zz%CeXr}*06ZvWJQeq7GxkIW6QFzOQtfoI(^q!kibO(QiLJ`n zn}nJnt5JvIJhNvbu(W22cx{Wtw)NUF+p@M&{Et^!472^g_#ZkiEW=di&mSPO$0`4b zSSwuTo-c{fJlDB;O#KE2zE z8}F6kyKa%@>3r;^Iw^y$53B%f&YeD^V@-dBC)D2nKbXA(+L6s2z`xL;ld z$7H=gwu4Fc){$+ua1`R`RW%0NB}pDx;8`L3lr{Kq~(>b3K=}9K1BzucM`DKFU;d zeY}v|^n6wM@AFj!&oXo>9&u3Zu~IxQ(W8SmFX6B1fQ${Ngj=AN3oI+iLWd+m8lp8Bb~!^BwxkyByQnVj;7 zDwEC1SX*Asexj%adT?5Xa;s*OH(e+ZZ_T5msCTsU=qw`yfhO@29O1#S6E`oQ!A$kf-$jXD>q^;&IhGJ|X`o;}DwLBZ!`yc0Yr^f& z$6TInlWKV>ifT1J82KH~=wnvsvX$cL^!_ri?4-Tw;-&zn3j1`qJ7-X3tPaf|GXGf> z9VP#w|ivM+w~@}OcWSx46(pRzeciO9{oLOns{i+7FR zc2>y0&$C3IwM$nk6UdBVdHQ$Nj$yA(kV$2gKN)?7TdR*%DxlIV|F)K5;nMc8bIAuC zr{Z|@tT0$IDc&^0zi};gHj8boB`ajy)Z#4PLcB0i_w?BVhf z%}a1@aA;s*e_rP?s#Z{2BKdAEwQoial_NWn0`iJ_k9L}Z3X?1AJNKlWn9*MjCGwv4 zwo;QOsJ;Y@THduXp=gq{TANl{?4ud%@IOoirb0@LRynhZ%KA0`v>T10J6N@XpXa-L z2a(`1S+Ze7pS>QW&+rW9tAAHL=8bC2MZJ6PbDky!3z;#GDlrisE)#`Tb;Qp%nv1$jsiQQ+@YlV9S z3~I|G!;lywKX+*f;&|V`)q5~RnbC6xF}%EEuGO6sc-=gOx*xh`Ny6(_CQIhse^z|| zQ8!s~Kyg{P3~^rnuJWfqKPOBeU!S&q-^qJ<^R6Ck4?9Nn#;0k773S^$?ERDwuxqs6b+MMt@LZ}bjYij^jYppHN@xi@6`=gj5t_Gq7|F8#eeA4mxF%p-ac5Z-P*{Z;>DbU zhIeD$ue-YcC4U5atux|a-5=!Dp12LKrgxN(P@=ef6vw38j+7naZ{Bo}8PmMM)%L3l zc8z@N2P2<+#ENtkA4M~2de=cS4Xh^HkGlqoT_60n^YsMcAc0FmeoWibS9xmap*^qEQUTt7ZkR5-CN_Ji=x9n1^MK(%F^rgoIvk|_B zSMyTk6i}QCj2GSdZjPOOJuVjc4^()J8LLlRA1O*#dBIMovxToaL45 z!j&@&UM;CmhCeS%`p$%XY_Mw-+2P$jfvkbuW!|##61l5h)jsy8ULNgBd#O0R^}Xm* zmeqc4pV*Cqy34ngvUhGgv(6tkK`D?iqgai*>ezh_caw!v%S$}7_NR5BGLSK|tbfi< zv!CA8NgkR_wSu@*gLkj@r#}6LTPtMDENk+TbUU_jdpUh$d5LEd7jN(P7De(0)q+Kf?M9Te|3Z zDcZ)-OrJQf?vqm5-5uh*RJ0^!yLj}`lo%g#WGGs%4zgtLkOEU74W`E{r6}Kp`fkJd z?JiJ^H9aPBL?%?2?9%+77zck1&q`{b| zN*WdA=jck7)$HMc;-#NfI{78l$dH=Q(w~ra)Mc|tEKFsRVUxvc;@8I`?K)4GoEJW|CK&U$N=H zG5f9B#7B|cqmIagY!}-tRq64NVu7rRNyEgCrKO#N_C@>1 zNBn}nSJ>0ZdlKersM$w+NKt`m)pp-8dsxsvwWhPMhGwmDCPj*@yTV2FFxA_2d?oJT zn$%+Jt%UxMxVBidYk9K@KT)D_w6Y`9)E1Ij{H%Ie{o`NNflX6$5%p9 zi=WNh^N$7<+aArfn~Z`yuWywEx4JwvBkYX=B&_}Ey%S>dgkksOFY*a>kP?1r{XP=urTumgX4v ziv`QmzpL*L{w^9zcHvbh3I^MFY5Q-q3+LZ8!9~yco|fsa6N8mJ-}Hx!43>LrL{ZX_ zHZYZI2^Ngg!N*=1SSw70H8d6X{5CZpgM=FrNBWqr0{x?To#NwM1}mT$EPcjH)ss?| z*oF4=mKFGYY9KvWhF0)Zyj>Y8r@0GGI;Y7(`bZV_dFD|~ny6wm?-eJWjSoKOHz{(& z)Ct4H_Cm^&C&y#qRGO%A?;80g%bTE?zaqstJAc+3BW99KDyGu4auu|OtYe8$JYn*W za;-#9(QH2{@^AQ4SAP56h+OV)mllbweISjCZ|}TkGZvGPG3-LgMDABy&|28 z6};1+=_>uZx>iT}-z(;<_}nSlr&Z7bg2^ee#)aris3u?Jm#d2x`)zZa4_X=u_rjCT z7V5h%hg07Lib}ImoQfk`dwq8{^{e&NuN>-Em@le1dbZcEu(xBr#u(L{BF=?+`=5J% zahC7t@3!1yt;h#9a92oLd3UfNwS|hbxv%*bt}aMzDTNnV%DbYpA{e^rygJzD;4k)P zyH%}_YPvm-rPjiA&6o`HShA>rFNA7>YRlRd-qb!vv)4C??aS-+e2C#gKFTAl_@0kw z^16L`rs^el#yif>@^a#Kcz;|gkIuqWsP@wHdLq(USgS)r5<-L{&0c@HTk05YNM|9n zg*jO~`tV+nJNjq^_devV^zUjcA@38U$b9MFRSg%OE9Lw~kr$sK&#H%LuTe)0MCJ?CZfroOR=*Z9 zX?C=sz&9DH)g7#V|4`BSh`F|eGXP_q$-0js#lAV!lCPAcbq0vajq401=VkXCB=WZ( zbu#3VfJbeubK|~h=8L~e5;I!R7|dVyH3|*pyl4KmDA@Due`*3;t1Tc;hH zd`Ibq`sHk#?zKX~Lu39^JVRtw2GsAfY6pF1Xle`9@ek8_z)Q=6ibY>^6L)3SakhS_ zYNc5_&Nty=vYX0mxi|7A$wNPF@E`hitB+@_NNpjHMK<)0taGNm6d}H4AHTp_A%lf# zDj|NEGjVDy+3f-mBkMQ#2R``gb*oZXo-W)+Kt%^wT^q@z^e8uyh$p(S#yeUCN-R>OE2rot4*ggKH0i zWgweHwu1wTi^?B#lQTN2c*2_P;%`pPW0?$AL60ZNAAaoaL1MnhW?9zrmn~7t^2rnA zeGl{HZ`ri6ZQSongZZ*@|BVy-Np*m{C-HZ9=DxM=bB_69e9%{VOfu|&y?sQbrYTO33e*l7 zqcjt`7c^eq;K;VTTKj1X-2&SQX$)A zD&&OmVrz$~vE{!{Q!E+9)SABD^t|PYhN;GE6Jn*?_@$)hEScK{8BJTd2OX!u_HT z<>irK?$>?jcdl0ymC>aD8OCZ|D>J7>uXf$hf)pJ{fdm+`WPGe^YgzN{znsVDU)NK% zL;?)SFwDu_AC!fcsr^AzAQ^_l7+Hh{Ij@_yA;Xv-W6ldHG0S=&sQ8Z*c%~p3rin4~ zW<`p;tq-+3O$si(;qc!6q~?cQMosVbp_*!y9WQr9)ZEdrdPRdJ!{}U?6J4tXRpr5| zRmB$t=(D_!e(*JXc#s|F z*|ft;>-j6Ura2>qoAeph%4`QjB{AuvSgSOeSdTvD36|AX}`;R!oZIu02yYoWG4bo z%Mah1Vjm?hf|v>^7$nB%J(&KqoVIL=y=w(2AifqVOpawK+2YLZUtB&(Uq#3(MV!0h z(|dQw0+Bx%vRhjDhT@~x>RMYN>0_RMDm!aMbJ30o#WA@ywyOPEFpddF0p^*o!Ce8( zMU)UlSNW^qU^|@F0Cc_u6>N>Ib0RB?Py5SXmY#I>zClqig00HGQ)HwFL^iA{3$GLR)!}yk`H|g8cOX6NS`P0X~YncorvBvjkEA+lAB?(pY{v z4`f=6inyeMF3-!mx_W$6@uMCl1;05iu>zSWI%ZoOmn9>$w~JhY6l32_Y1i#8U54VI z47H)CN%Yx2$;p9cM4urCh1?alNpn+{)t`u}cecbiCsRzK3i&9ct1N3L#o8MC`#NXd zvx+6t6czJhSCff~r|5MiA61R$%(;6nISq5_y0PdqYlRB^PE@2-T1-WGsd-&l;iM(> zo0FqVVJdznrdFruQ#rije))%r^6-Mjvhg2(+Wg*LSg4ghGmX#in_5#;NLStdea@@0 z1r$UN#RYgboFb;nBSmA}yU6ds#JcQzB@-yBb#Tn@4`i@Bs)^5d6qWZozds7r%8PTZ z2q(>8dGWEfKT$-^tlV3^S8`!2#}kcvu~l%L$}7XYh9QD0)~+VUekbIIFHCm$-s*_* zNN}0dR=bi>^0L)NPTh9cptiiauXCfb9V#E=PyPBMYR8#QVe;#hetGbTRgO#HE{X9s ztj6|Y9*>3Pk!5_PlCsEtznryt>n#dneifp*_-d)YBpG+%KVRN-_Dw^+aDduE9*ev! z{Cu}q`OS6bzO5}K?`?%xD`2otO;-0=Ma4bDP56nMV8!-u_9#a?F%S)m_q;U!Mb?G| z_SJM5x;!4wHO-?|SPDigrts+Jdm2w=c}>3hNd=b5s%N0%D7 zK)tfFdv7d5QIIRrJeNs>F^QzgwyYrSyh zi-;wQBOkwm!$<)H80V|GGR%qIwN$HqmsZ$|^Ql%;lM4?lbJxhYhMFtGoXC?7^^!@{ zOG@5NWkBh=_qlB{ddV1yvz0?@8Sni}vkdAD$!iw7{R+##vT5fBSTeGppgW}^nW0+$ z&gGFMASue1nMS+q{O2k3F{fg6h#0d9(tx3P6wJa*MYVf;&Jg0f0z|CiTi;;G3~K9v zz7qo#i6zUYwIw4JvSbEh7Sm^9?9KfnWTO~bje=ALOJ>k#MGO1vZp4zcCzcFTAsL3m z7_Bo9OSY)RQ27ki3R8vkC>m!ZCvT;CWhyV`?iD zohQWOP=5uQ>&}#rS1Y81xVKYuN2#Mb9;#W9#z6I*fn*pmW@Pif7_heWPgQuVHu|4;!{!LzX!>t;el4NSJ_p5o&oUKj${}zW|q~ke6+YQ_QycG zgDnjO@?)sht>FKYpKYMbu39U}H}V3v9cEmzU9^fbV~kqGL7qz=^BYIgTx!D zFFEN{B%3=-)#YuX54+8D-(*^$<|Vj_qZOPN5@+1<(Ch;mV#aX8b|AZEt}S6+Sca*< zbswxRwgag&q8hXN$_iu$P>9xK4$Y;W5%kz<@M^%ZkwwbQIQbBb3`w+WiK77$T;|A7 z?eP+Fb5?a3xq)`vAr@I?5wL zLz7oSMh$Z!Z*T7u-|=m^1TkbO?c)HK#`}(%S3|XBJsc#F*nc9{1gRvGM4C=9coLSS zw2OZ}*WkPiUJV$tTX{8P)cBYgHO);ZUacj?j~ZS#&VFd`Tqn6S{ zW?E5$B|~+>$P9nO@kCn8;T@B&ZayWJzhBR}>z+b}f@5tp%x&(^v{z*zV@7KLje3aa zGHab~L|fn)w5&p#Da-nudLj>6|BCn^Q6a`FsP^JAP!9~YcidWxI^skDNB zU%6+zI6Jge>|^~l8!QLeSdm6w>xAj62Xu*!^c=Zxg3aB;d_lSb-G zG*V+K*!osfmgw7c@}Z0gMV7-EG?sAhUvVp3?3gf zl}7wVeHs(v&O1{psmP1^4AN)VKJrN(Jybk+WvLxS>m|rqA;HDAMs42f93;ciYK;-g z%5=8(W^D=}0fx3eroGR$#zV!CYfJ5#gQhr`FRINl^6SNmQIBQYW5_m&`;TU#u!d|W zHNDeALE35Lr8qHzEYCKP9U1pNO@MK))N~cwWqCwZb@CT}@_H4~;6Y^-rU@`4!!ReZ ztSKBWK71om#6&2|GfjXY8OG0z&WXP0DgNLe^o;obQSk>g0fuCl$&ytdmTUm|)PM7L zQUK3oa9##Wwu*d`4|+V?zMo<>Vhi!Qnb+!as}wtLe;;v!o?0IJ^5VO@R}pD2ESsX& zQ51oD*7g(j1*#RWWT?QLs9#N(8YoA8$@8{v4kT6jIK=iK=fxwq$CAyW^~5^6M~EKm zGn3AA`Bm|1m z=G{2OjPbV`+R&plzGV@87OUwqtx(^6o?0u^?+4GXaIO&Nb>!Nn01{wGhOtL5k9_Gu zy9~~!{l$q1wBByeSsqu0IZ-sIe>*uhX*>y|eFMx3DKYki=8<7%sb7_*el;_l6mX{C zb@TdF6Y_SFTB@f=r!QgNy-%$4nA-CIuFS0Mq(%*8`yE2mpFw?>_PjVc4__05AA_83 z^*hLX6f2Mh!<;N@3fUk()3%KJX49$MGF+c`iYwE<^PQ!&3|cQ4N9!fe(|QT-l1%Tz zG#Kr~K@2ao-=fr(wTgW2BuCG3I+E`lR3J-+YSnl7zr4a?>|g^LyF3vtE}tPOAb7qYW-g)=Mp`G zJNOxd(s4vJN73FjH#$9LCsGfgxbYz_%_Q7Bc8$MSu7CcE)Au$^9CC2pF)Na4Y;?xl zrVOvVe1oWij!0|am@4YC;r_&meW)jLJTH%9GZlEBy}rZ>kFL8DMMtrnAgY;n%zfwyQm7rs;E_#H_(pqRvbrBwx_6wPWh&my z_S-*wa=+Y41gJ;O5w8Xu9P)8@CiOful6nQ!sSQ#us_b*YClwXEdhY!dAoZYDiBI0^ z5w6Uo%1|7|(_S1kr1N-pN%N`{`D8?#sGS}35F1puBQ>bCf`6y4;sqz|E%Xe|AZApv zZ|aQ4;PGCe$BZ^7{zI1RDb~}-pkuYM0vSBs`SV7G))eRGQ{q1c-bi!zAT{r-nL$?P zoh;|iJJ~HrVN+~CTSvmwd>yCK$2{7ZwWUaywvHOAQ@OPQYES>J-Z*?e=95pSBjnSG zXGMC3gllBxY^iOzA)-~qY$xgX*8z`xGbuX$dI>w&!hdGgTDs>*(RqqHQFOc#^8c;K z0S1qs!B;81bJV=k&Z31=94sAUt>LN>@#i->iZ{crI{o4f8>Aj`cjnqsGm83FFQ>fB znfypwq~(yJLk7>X-Z&*h2~kIWbS#Eu;~rm!-)G3=tgF7I+)nxbP; zEAHKy?rJSY)S}3)P2Vj=vN1o)>>p={0i8IuaQ#w)e_*#-35}ymTu5 zg3#reiZ+A0ixy6wz$9ABKqd~UHl*DwYyYlpV$COYoEb#XAwPy{lO>x@+}+u)1nSO$OOFWa3PUt~FiVF+WbT4-*rI>>IZ{Z02%ztp{@= z8)2g8swSt~Ntv`#bebr-)K%_1;H#^NI?RdU0;F{kOMjW>^dm3ec-QbMn0K)DzK(h) z&Yp zI~1npSNX4=vY&r17`c}esUpqijv2vY9#@dZBkqoPXNs5V&Ka-_oi8i&@9wcxbX{q2 ztaMM2`ZkTCkS{WE$jVvP92%*2kX=#MXr<6G)i`=~awQe}3>iGjI$W-f7)LR^e{D(M zoWR{76UXZTnw6uKBJx*rKkYCLeo#f|PHmuKpCKP-mhnFMta+t6GPk`tw64gYU?VHt|PMe zNOiGrPziZ+I_*aRofmhQ)=2uca}``$9c#_9YR`%kwY#2mhL2b6&}Y&HZSqgw@_?)0 zHtASvG>SH9FX}E%cQ(>FXYP-675oHi29M^VU-l9ETBkUNFDVvTpW)|j;mdJq zJqhSKnv1ULCteXTPM6Cn=A@3>h6<$XEUU$l5n_DriGizECL7$His<;DyOL&7bT5#i zMf-`d6|PQpaP}tBB=<>$0&rw_A9t6LgHGPJ3otj-~UJcndRPeD!pRuOr zfI{-WEq&<8CEq(i_6^nid*)Zi;xZrGkjd#ig~`5oYX03+JWXX>rZRX{MW1n3Sc2u5 zIdz{{t(_A|_Rg8{w@`Gth7kwO`jWzbj!RnlNhaAEJn)wj-Tp5p`zMp4Lvk+l(oUb= z%_LuXWb6D}L$zy(6rI&P;Ko$rol54_G#^K6`lR62-=N(k;|wX7gVPG_!H`xBf{Og0 zB6Nlnyw9l>e9R10VeJ9d-rPh{Vaoa5S_xj=N?xILuE99}^_@O=d_t#U;} zflQnccg_0t=lkS}gH=U;qUa2g4GA|S>a68|Wwm{A%8TOq+1&;chihbJw6qd?inr6d zuUG~vc$R6gw@`z-EB0}2dJ?U}{kg%6`i5ojvFD+QW@J1YOXpb`GI1UYu+kvv$jcN( zVrzOl#+mA`L+cC&eDNh(_~XJ>WTD!m&t{OF{x5l!Y~7+R-FuH@({}nyxFNen5ksby zmqqiN$O&IxxW&rbBj=O_zKYY)^;l)u`KxVwRk43^-W)V#3WS)FlLtZcE@bzxq9&w34_XjGl^C2@+Z_xaurC7 zSyuYBINAJ9OD2;yM7(PWHLv>k+t6(Xw2xL&+om`_e%VL%ruh}-m6(3FyJyY{v`Hrm z>_UU(Kg4-;?VoG(D?P5bESWnh@l(r@vnm8v)Os)(794RjKgOevt1!s0l2pdu#H$UY zGQ1Iv;*w!1mvB?QO)YB{F>yC`FA3ZvCJvb~RI8^zD^BlxT1Kpx;v89^2vF@I6j?IN ziAM1MI?4xQ4m*7tt7w9$=Ue#GIv>kIG7Qz0_2RfDvI==M`f8FTIGSLWiqS6TrQZz9 zkD;0dLQ>pQqLiFDg{~DaW2nHKXogH|c-ap0ZLsU2t=}?CpP|}h%pN;?%(*Y4yZmVB z?px?HB)B{h?M^J2$CV*V#-lbWJnjnVGa9LZN~5RtXnDDAb9b3P|K+xxB8T$w$S{)_ z+duv;Irp(Aq%*g_%~4KA#S=M4-do+P5aP!a!KIkEw=S2HpMTL!_I<6HZRX|iYGzJb zD8A@9iZyy{z+__vfHW9VX|#{?zbNT{qP6_#@t*^D1{pP`9XRLqX>Q3bV8D^+~ z5~J^gnt#{>niiGw+O;z1Gt8Iw*fLvfpz?U_HMo!C&NO&8Op{?oc?Nw}&<~`h&vd^0 zHefaXuHGLbC?4c$+BF$CT$9C$GUNABl|r%ZdMlP~S;uJSXFaXf*2*Rl3HTu+U)mqk zUVO--XT33U>pz_w>u8$G6=}*W^&#Sag3-G9eU*?97xZ(v^X-=6^04-S^l(~}aVeo7 z0S5dSGd~=;TV1Pr$qxDDq#pzA$qv~#0}6BR?p6}%LAB2r7f#V&Cf zx*hr$73%7eS71qAfgSb=%W+~+N_Doc{*)Hr)?Qe(*tP8D`w?Q-pln))n=oGfF>7jU=@V(RH=*b= zzEh~coXD!Uz&-c4G*ivLt9xFMQPc`9L#NWSGu2~$sTJegyS$(Ljr<|w+#vbo!v3U; zG43qGq|#o`BFZ9pkUVjJuEDD%v@GI}>p9w0P~Kh~G^_Eh;jwXZ2gzS+v(G*}Q_Qt6 zpAYtxG33z!&!k?WcxS=5uLg-`4JOZDiRAgKHhKQSGpInW%(YGs6cy1OMUk~a z;!OXp?%?G{4Mlba#lsHJ6Gb~?i7lt7dkef8??=V8pAux^)r&s)@%os&MWD-|HaknJ*68@{=yl>5*DU9~C6S>du)U{;l(}tMJIM z{H(-Z>iHw)HKFkw2`(0&^)tDv1RK(o@|8-~1%SeUHmz8xnTFg9XXMA7E zq_&buxA#xq#<8SI?DEw7c7Eob7|}?!%b0qDW6^p1ASw+0%Zi|D)p*#g*9xz+cbrcx zzeele57Q3TkAEF?GC!awNP~y!Xsfri4QA zyn@I4)S7%$SK9S?ZqzAPfee;DW(DR1m7;V#EqE02O!Y>*7QavL&C@7*A8WLd{&CW_WmQg+m8u+dWcLE1oYhK}1hYY1binvm0fM_fnXyF^L*o9xFZ@T-sT@qq#i!b+kc-MRfSc zpILpZFDkY8E|dSNks2H5K&km3o>^P-V}G7Q--Dz;{)_JUirCmVTJh?TLvzsr{8-#W zN`d?rtvWwn+WvB9b6Jwgz%#w_)`iMI{)>DY79L{%yReCzOy%I2ybPL=1>1rA7g>4t zNwvq%tu3p57HyDW`{r!$=RG&xZ3og^SX+vTglE`BL)(;@WY}e*w%&gam0gE~1?ZkYPF%_C!-r+4q`AdL`0%ztLD5=`4ufGSTUd7fg-;ncZIel zY{Qa#sAG`xdgra-VrOxm-HWI#%oo+B;<-J2#AaFn=}eu%cMB?hh&*f4{+;+HXd9)eEkjLKPrbf+G^ofttNA(4J@8miQ7N*$@Dn%s$j8cVNw*eBEw&KMYxPi2;kz`=-oLw(NPkF`IY}UVfhsPFse~@TI)@^tO%fPbLyKFgG6)z^MVzwR9b1Jkjc5RJ9+!SlZ5>b_T z=%CABY1WFZg0!`WYatX;JXJYj>s2bF;@677(y6dKQ*nOM)YuAR$BPX&Hqlo-vQ`k0 zi!7x*5{Y7M#p+mFCOLI0QN>>}^u;}RYV5_)pv)oL|Y0b1IT&`HijV zIfs0auHv;<9Y4;p7L1%27)`yT0re70Rq(obz2pGJkDg6&C;#nA3LvMjhP)rGW9`vO zYW;??`7R;iil{vnZTZf7ESliKoM`6b2>C+ehT?itQUqgo@zq5IucYgoOa@DnQ>@U( zeAh51qUdNXW7KH1mZ4Lzf_FAFkEMTC_q;;0#^NOT#i>Vrad^#p;WG9END3sXKur{O zpZmpcvpPGqDVn*V055{5R!@}rZ<1usHkq}U0HfG&z+fR=Mcx(;4s#0MY$A(oC!aM) zP64TfYO*ze*k@|JL{Vm#uSry0SoFSg@6K0gHb%ZWu?&n$6*}hU*v-CJ9PNjJzWWHE2rS^-?*6`8pJ@>dPtVw=> zTHQ(EsZmj2C6%}$kLN-~*g3LEeONJHWUF?Ep6K;T$~V*#Z!N>SR;b7=JITNO6nnre zpfK(SUMr8s;%iluwmQEK+Oc>v*qz9*0{26B3fct-JmGJ+p5s#eUo2T>nO8#B1X(h? zhVC7-Lw7I~=`GXX$54G|`V5IP@0iK{Vc~r}s?{QrRlacexHFpgTHdwNlo`@z zn3HSIAT*b%6+Dt@&Wqm+`gisIh$cIOmuNR`N72phPMm)C=~S#vtW?2oO?o6qhV5Tp zT9hFI^zNO+r5aqC$E%t2853^55#hF)Sbg@HspBOgKW65%p-w}QEQOdr{@XDxB-~71 ztvkKTTT_&y^K~`_@QiNxt?jsceuaH`+%R!tEm;mME9uXh+TFEIz*=E#>8l6`H=I-H zWB0uaIjLT97p?Vmp|w7ofAU(NUf;uYKk`WW_z+R+$1j~;Pv#he8**j*1-Z6&3xBb) zY-hQp^GH#2JdNx0hlHDVtm4L}_G?a0Fh3u6BHTO$5^g!9=u2^`N0wON93XEYxZ;Ji z!qqR!T0$P0`jL00EZ_84q|cC1!>?EJ{aLfU$e*3=^dmlszsdCaH*!o!iIGphy6r`W zdFjr>>FOItfA^!>R22O-T0C(wHV}J={3p`)Yx4Mc?)n9?T}X*p*48hYi|#{9J6CDF z1SzU6V>kPgO3iWArXu0%Xz}@}*w{Q;#X$lL%gEiPBC@G@Qv9szBd&VB}#R_rMkRRh3CSF<^RGgbzTU@8rqEu=JUmwkqq1uWj z60P&{prZAvCSo+L77Y)Up$R}vmA8GSn+mUpF(%6Cm)84}iz$DP$dVx?Myt)MCOcLB zXe4T6w{%%DO@JX8hH6@e`(cptOrWtGlTPs`bN8nk1ka7nUNPzV)}PiZT+2c(w5{AvC40o1DG$8VJ3Z6WpinO`B*S`4j1(KlZ%rCSGUFAG3*$d_Nk+_< z*G@e8torJ7dueogIhI#HfIj23g5-yc%d_^sGihal)?V`FuXT6E zFkkG`6e)H^y8T_t_VNMR^TK?QJ~PW$L~Al%jEaV ztA?w9xSoh=%c9t4?R4xj+%ZAojE^Go* zDxriXUC;<3O(3B}O73oQRlpJrVvkBFN_j1jS3wamAYhj$QdNl35g{TdLGDh11yE2F zQ0#!BpdbiQu^|8N*}Zf3J5k>MFVBPSb9c_Pot@o1=X}ojBrZ`)a)9ue3cOl^V^iNj z;c<8+N$Or|RR8D(-jV+gcKNAxSS z>Anl=E1zKrrrp4X*Q=)+U3!{3>(qJ{;l`y(Pd|_q+Jq-9Zn{Q61Gxt=otR6Lt7deI z{?$xqAfNsyvgXm1xo(wPY>G}wnKw(8R_eB4*XkB6AC9$rrHYmeSrQww4`O46yumz+ zL_;3lzpl|Qz0lIz3d<-H|f zm5p_iZOBBk{)pwRE{s9`<6WXjU(HKor3v!6^|-s6zEu}{9~^0HQFPp%-NtwL3LZau zYLD5%lKGrRoIZ=Q8HOtp`;L%k|D@04j9-fQF`GU^ELQ#8Q$Z}&pbcS3FuNpSn5KWS zWFZZ9B9=_7vP_?`M0gBBAwitK5JKZZDhiE zy=IvH;O1?SdK=%fC?O`!m}7%gJnJr9>GlpzBUt-*u6TZIiHU7<^rKg7_746HHk^1h z^$-YC%N&`QfL2?Rk*p(e`jPW4Fpf;;d%))o%wC?-ASc*Ot!?|e`f0U_ag>&9r$Gs z+hy@;7U$I&tlB|D3%&7A%oR~>%&S=(o4WX<#`+gX^#BvcrI=S^jtx5;qH+%c;pQ}f zMUkjB=GB;EQ|i_0HhZ<(=jbbc`o!Z>OrJ582A{lEIeH4Nc{+U0`8x9~*w_3&$Ql0P z#$5dx;-qrD?5)mT?9fv`m9L#6Q`e-{S#Gc0yFTz~S+QrEok-X&tS%e!)%l%<&-gtXmd%eEp znF(I?IYGuk!>ci)CikNu-|)_G#xVUv^rP5}Lz{fc)YQeLY$oop-!Ig8mw)JuJN>A` zTpCktk}c7+t<>hby6La*wE45N*pcU*$wAM&8rOuFOo;V*Z+qU#pm|bHkO{uIG`|2y+sjGiK_4BoSCLHb3_SXm)|q=WS&!WWZ7oq|OrNo|Qg>`P;5da<+;gGJ5Q(C1C7vO)wUld3tcxZm z8X^_G-&&X5o#{%?xD9hG`?;aSq$WQenMOP^f0dZJOes^ZO`i=0eRevcQ@(;+@un`3 z;5NiE8q#O+>|9#1b8%fvQ3-E!^!}c49rBBHTIgq>nfSFko;XCfalNurCo~ocdBZjG zhQ}mtxID=W8CFmu;Kw9~IP+MEJXr8L!(2f^NVed47f6_AVo11sjNBYo^{(N4kK7za zLP{~!#un2AyX3cl?|ZDofN(R%+(yv$qxX*0^&g%XJA1d-=tBt}2aivwHF!Gz{pW12 z4&v4_)h5qf6DdZXwISg)6ZxsW1Wo>a3T*UrMxM?l3p`&N(5Fw0(3_CcYY_5a@hoQE zUY=z#zgumqk|W*qO$}POpVx+NiAXl)%50Ww*t~4rV?b5+*oEj7k!(!3aZR{_VB#JD z6L&E(Z7|6uW6D|aqOULGnv}Zz+~FEIU!1a=gZGlWPILUX7v|%*CZ$qNYoWi!O8hYu z;~@5pSEaoY&wO-*-g7L|xeIh8Ymlx5UX9-qm{+q2x3jm7h^2v7`xLwy zm%^G&@M>~gI`E!2Kls}RBO-0V#O*{IT#9)$n{eCHxT|yb^MiC9M6~8o%&RfSrqtNZ zQ=P(1x%zg*rs7hCy^<4jKXYvG1Vn7=Cp+foXW(_uXIR2w*VHl)Zbi>!>#qml3K9ob zIubDx4~+{(OC%byd3dh7x^-)P!*ZKfV@6FZW_jh4;#ctI8ZF!p4|LZTq7A0yxDA$; zUCR8~S;kUdXc#TnF&nD`xt*AHQ|TGgeWu!vrb~PjmnyuXK{Wq-$#4WdP9YPf+oW5- z#4-EEeK)@c`_&8QHP>I9Qsj(p(^aP)tQSo%7(}}n3EAzGYSX5jKKvqZrM4WoGtr^Lq*%KVd}3m7csvE zi=qpqE4God%ZYB9SO?E@o1$a7&gZ<`1m~6D?vR};aa@tR0~z+O5+OOq5^_CyKGem1 z*c}lYv=bh`35E~v3B=tMUz?H09|oOF&+!RXe@o>m31*BXn8(9TeP)K<^JIzl7i@P- zyA-dSpNJgEe2ZKSo1**YRV%NM?`}&Pe2K<=7VG#9e73iQ6y1i$pNigEEHe#$kchey zQ*@y;Q*<&{Cr=)g7&+5&Tram5tg72BtWo$-#f6upr5M6gT5;BifutD9bpB_HqN z)`V4m@4ltcMP~#O%-ykcxN>FAn6+aGwznxYdsL^`p&ebc{0n-u5_gV_Pm(fVcMYbc-M4Hn?r2rhIm#Uf&}ahw1x?#t~fE zZ|dKtOP2NaXP2ozo*(mjF!IYl-fuZ`lK=K<&PZM6I4BiTf`||IT4#60?2O2igCJRn zyR%t4mY5IyRky=)y;jg)8SYN%Wg3tvL8V^C`|gj&W_zbW8^xv6MaY&H*G4gqmmnv7 z4+ztS8Hag8GQS67iqDlV!Q(K;eh;l&bQg3Pu6O-9+nXU8FX{<%es-^_zn`mTb)D)R zL#Bu@AkA-^kHJy|QS^jDA@~h{ecEl+E@v9oEdfA?JcM>vr4x zATwAqMwj_{;)Er{w;Z^;>mInM$9J&&FbycAmf`N03{+}((P{3J_&50+n+9Y8kZV%P z+uveMYfy*RgE*{60~$LHOT&LRo@MMe*u1zW%$3~^|nw~eUM~^W!cPH$MuzkYw z1x#GP;3-v+24n)zNEq@ZpaH){8`JWpt+@Vde`ol>LuD>QWq_pVJ!i8n*odmrPuDLvImg1W8 z{P1<=>kl2-O&>W6PyW#RY&q5yWnSZTb@Xv$WJByvS#K7vhA(=tXuelkdURamS_L*xg1Rvt>{yx-ubY?sK4Sb`RIAc=Jrq3D= zNcPr)qH79@j!QAA#{?g2knwA&l`+QC$vTA!5y%l>R zmts6z01Ru1VRS!B_ zFHO=(>$+HMA4@Q`hbTAiH**_L+9~2w%6|V zJ;U9bFDUhHTXl*f`_hK_ouc_S+uWV)(fG?>*SP(YD(m}SZfTKv%-ykXB(yllfu%rI z^%mM<$#pgq9oK{_ zxW1V?dPp_*hi8ISVrV(>pfU`eTz^v&cAp!nx!G?wbDxBdm#k4k%P~b~b9cQFlRO(9 zRk65_m!ajDqT`yBDq3FR9B|>63ePiU-FAdv>Gy zCpovT?d4uJ9kcu9%h8-Ref@c7a*o@E=g@{0PV$ayHv+zv**BKpnv`0HSmB*FA_90% zWXL1VgZU5H`9h-VnQuo#x<5a}eeAb@yGz*^uzg&L+YX*gjo|Ct9G*zb!HGYVd6SW2 z!{Y@KdqL6N0E&*iTi9rx=*k|$+ZE)MzsqY;ZY4zSnw@BXGnY$wn zPIQ(beKzd(Ue4l_(e6CSa73kym$&}~F&qBk5A=;&GcNf%(`)qb2)E@(P;^Yo5$$FZ zZexC*?e%Om+{qZZ`PPN;0g1rmoK=$=kbI@Cf*d)`8?J~M zT28JSOK?qy?*q-mWupSkgkkMO!W_%jB-ezur67*5Bdgey^4nkr&*JWa^)COlDZ16k z4@OfvNahQhsKa~Wug%2E&*z2gf`fVjJRmd=6sdw)U^YxD+#S%*r7WLhTm%B3$#+;D>CTA$G0kt%cFV z>QU2=WqO@)M=iM<6dluYMA7j+22Uq=qYTC!m5n=!X*s6oteUJlifK8f=(r}O?!sz+ z7pr{;R=d?-8VA=DUU6S@N14o+)Uw$nP+QtiTOm0oy3l{4=tNH){)sL;EEN)5|5xtL zmIxWVu!V}_;J$}^oan-Pe{dl7@&#k@B#_7?hL!{8McLBAK7!eiOtaLpZa z&G}4tjCDl>pFO+G@Rid1oKQkDk;mf@OPnF6m0B{mzRpE_xNe9KXIMLV%CK}`R}1!h zpC)%rMo%LlPaJbB(ndmlhwlm~z2xF#SNt-V;WH9aD!&PAdVgIUf0OAwq^djp#ca0& zc4~{rqik|w!3=lmvbR2SM&iB1Lj-=N|6y!wuITBb#O_n;Q_O@)R z6Wy{cJ03w`+QIH_jcSNnI~cv9EOk7l(kiDV@-QRn>F;x$8ptiX`%Oe!pm#Oq{J6K! z+*R%EEJv2gD?ddxTD{eD-;KuEKJ$(%^nf|G-*+ECPVb-d#&}~U zb?}(sV~UP-SxQ}=KFa;@jtSoOo8HV|iSWA?UaXO|(fqYS=ZX%9X~3`)_7W-;{{DZH zfLB;TTJGAh$A`Fg=E7^>!R)pC#mj78D6tI^rz6w&o7Ja#v-ZvNc!WH1TXVN^dUJOL z^vHdnLzXW^Ljx9Wc_q{c>8HP<_CK`)7))xLy>odXrt(=@hnJt;S~&b z-U5psWILx#1Gc*U2CrYc_WJ#tE*1^Q)=(w`@eR>#h*$Oew(bRp5pPI6**Uph;R;hm z2Uc07ZkaXD**SCszCYk6g)DVBU%iqg66RQTQ6>?Uis#PspXc0$saGV-@4-&J6NL2T zZwz*SM}%>%m-EtDl%V>ee6BMKc}6}(E^MnV>3c!Czn?QDsMIUi+k0YfUx~fls*7kq zixO0SBf5A`czU-+Ruaj?e)Oqye@ABG&~9UWmB#Mwf(~xKQ?NS_amX|vQ-ZLD!{6tD z&AECmGQW%dB4v5Hui0WUklRyg>*i)|Kd^UiKOID9GQ=U%fTAfflpyxR?ZNKj>l+heLI7G<94LUqa@}{LF0bj=r(TJKqa-YI8>Br}aeUZHdK@I1CBV zy_qv|%o65UsqF)gckdMY&AX~<-am-LM5zb%W)(d0NoLB+OVO*f<399ijsMWwy51%Z znKH~f6;vSXxIs=m*lYFe69aoK6Ng3h*Z5iSd>bv;z649C97xFcm%-X+afy%+X3$8O z=$nXi8U!P?aHXjyM$n?i7A$aEtYw8tlEjRfV5QoGGGCycbtlrT*$`@_h0} zzXk6nq7RUFbvZV<=>A>8loAPfS2q%JJmu}Ny63p`$oYd-M3`#hQijwEEWCRz9Hj?u zJZbF;#Q5cLm{NYcU&=BE|3r{_88_@-+j}hZ?L_LaMA)8;uUq;k{n^j!*0#EHf|Xg$ zNccVRuTyE3wB_ou)98Q;s>_o0u+Ku7jEWbK)ctUc?$3YZ4tt7J()pKO>v zr7Ax%La(ZmQ#ylQ0!q->RS8r&h?=1qq zMEU2`8-HaP`!Mr(O0Dm*&ucWHgWij`K0cE&?08n@fbqU1pLskyqh2WSE=Qb+42c23 z`R6*G6wSG%AdsM(aj=-2lc8^#RpM1$g`K+X9TnR-6U@z-YoY^8&p@y z#!At=`S<&6us1$N-e;5^*?ek`L!&1+yyDEg?Xz~)+T9i3OD+N1XLbW&_*g=YLsGAH z(8ijR@0(zedR!`KJBWarxT7-69c3swv)jpCWJ~;c@hy7b?s?wjHK+UR>xT0>MT=^D zQpV&QGkA#O<=mx9W_9o;bRFZ!eJ2+8d@#pc%BBH-YjK5spw~ffUBg9=?8rrVouhfh zpZEm1$xFxW;p=WhH~s3-#`?F_#g1eqHGCa&cTDiXm$T&+`sR#--jyk6gV;Wc=cBfj z>iAn@U1wA`{r5{Y+sEzM8QD%m>V?!^#cXLT5i)Ab_8|wT|HKKHKcyZ$sjD7>xC!$| zPwmO9T`0Y+0;zYk+d_}q1zXN{6-YgrH?9d$ho<+@Bd0dhJy&H~+#Nr6nZZMbib_}N zz1P&#Up`rA<%tXXzSPg|X$U@fs+qq6&D>q;?JAm?CSJ@ogQrxpZXY>E$JBH8oZiKv z=vbPGI$XgY);nvTtFPZj?lVKR$rrs%=baR0omYF1-RJ{EkCeVKMJLjR+B3gPO?U@I z=Ap}wfk-5{4S2En^~!g@`9079%znewe}TPQhD>G_MMvbEX(Nzmw=7u~LC0GnmTK5f z4Uxz7ihVc{TS%q9n5{1wTvZ?M1#K)s~yDq46GL>{*Xd-TqF#c@gq znZ`9TjmIR@xJ9+41+-hi;fkbQ8+a}cjRl^|hP(6UCr<6jj2iAP82QDJSidHa;4?;= zC4%eG4*cLZk57zj`rPCt3vP+KZ~hQ|b(3s$6I z{;J{W{05%Rt>RB-L(Va&XH#^k@G9O6%jH&RSgcY^>Jh=G)O(w5)LF=QI~o+7Jhu}( zA74SbvPymR?nr&x4dY@@c7W#qT?1zO{E^4=1S*w{=jiQgtt?>#wMbp3t)TZT>Wt$RK<)-}Jcz65Ve ze5SbDsnMcejo%}4es9gnjhu65U;S$#-hJTvQSkXa@i|F~X2v}wnc&0w)ut9sz0b4t zGWhlJnVj`i6KycThcCzjlbk{C_0mhmqgRx%Zp)ACjGjg4o;8W_kpOt$uB06kp2;z{~K9*nxPpJkm*BuYGuX-hTix9)dbe>q64*fYP zPHGOQ-NNk-{xu^FzRtu9iq$Ib>IwcTVQF~=m_K~8Q9ihWpBhHD9GT@m)tK{Bs?H4^ zb)Ct*J%uREJ15ppkT1;k*%HGKx6&V`Kj~EiS0tpK$(+RmAWNGb$^V01{ne=oz5zi| z5i= zbOYgEftx?yjc)m-p5J;XQC`NhBlPV*k8!K&W`3{2GhncYiDbg@#1dR8JeH?3a_YGz zryf&{OfuRMG!D&FBTHC>V<=GJ{wPX1HNnJjO^Ek2rgsF%gS=#i3;o@h zX&&Z(ltQjXZE`hstM+_6KO@tt0S9T;8@^I!bkft-eHDA758|pNxI0*sh=U9nJeez8 z4{fdop9zo4%fgyuC5C?mn(0Muqxd0Y*NNu|#GC$~J!5xV)Y-iU{tQet+9cyrtQ=!k zJM+(;5eE{BAR%6*<_x#j2st7Q9|<}$tA_XJ`Kj&>P;|zYCVL6n2f1x%oo_zH8#=*=+(L)S}3!RY%BE1$%r8*nSBg;X!06LZw*oTz?G3$qx-t(Dah^^lnTyp8-B0i zZ7Y>ql#(tpEV~c$gWTRdHI^jery}mhDQ6nu&}F7FwQXVIezdRPQA8uX;)Pw#b%$Sx zF)hjL;|64}EB$?b{Hc}(Qg7qoxlV4q6+WpatflPEEG>kaiM#+^2K+2H!O!w8#E#~A z4X-HD8}3>VRVOcwx4{{@nRjGbvQcbdG|bR#R$|4uYINSB#P=X`mv$T$u8Z5SCE{7Y z{cknyl{uDim@x%k6BRQFn0%1`CLz+WpEn<^YaQw3to`llp!30-!Fj9^5UAky;aazG9fv%E+pbQ zON+r&srbhEJ~_*~58pV2r_YZjG?2tZ+7h=x%enKHUfy14IZf-*6RQo%CaKib7{{+o zgK-#%6OAJndH=1sx_YOn-l|Wb<)of4cWJYh9~Jk~8^4<5wOE38YN9iF*S2@+%xTSa zmn(|ASK-gV`?=bLtg-QU$jmO{Y4B}S-LfWH(5G%z>E+8TQWH@&+t+hzzSF43sq1p= z?2bfaGUut(`9C`DYY(?wQ~Fq2Kf9x`#0mRo=uDfntn&M*Zg>1!HLb11b249f&(W39 z;^q0#g8SbtRqC18>Dc_GAiYU!-I0$NkqI; zBgU7TiRMKoE)gdb#56B@60Ub0q@V7`(|8B$NA@lFqklGbozU>)J@+WvqVTxauSeL~9W%#Io=R6a1<~N`-dw|Yl3HJ9AZ!w8GnuEBb zs3lHku7UM+2ClQQ(aNz{b;bH>&(&)+N}S14)_7%DS8Q=-I+NE}$XZ_hFRZ0#%w>Lf zrfqUE^pr#9lYeC`orsQTCC;JdlyGlw+EnEL99v2kcb_2 z@SfNo=hoJR)B0MhW%0#$(>p%Km#~S-8Cx31N>NLcJuc{2Ao%0r$K_xJp9GdL>o+0Ds@?Ybv@^q zz7}hlv+CuF%*6C08)qG~gBzW({=Zg7Ar3Ta)?>)4$vOXKV;+-?~ z)kRU&Vw^v6<}Q`G{`R@f(?#XpstJ89)^f|G%M%hzQ6i5?rGw52JP{P027E@mcMPj< zvz8V8eT*%QCB&;IVU<(w=Z|s+eK69yv&X9urW(W5ZWE4(xAeepw@%wl-a}(LN7(z5 zJwCZ65F_tqyA#TwEm|CS@0hHC%q5Czy zoSxgW-OpFGa0;HS>*r}O^deJ@b{i#6Hg})9rpP&eW8k-Bs78xqM6QE}TU_M(Fe&xW z+6HdRVH=%S%L3mh!y<}5l#v#%ryZ|d9GAE~Bgwty&!3!Bc$hMaDA&M9i)Yp9b1#ic z>`OZ6Jl-eCor0_j%p$V1T+JZ%ORTM&<@`8#gu4+Wl11bZuPAZV!YpI&|CH%WN5;($ z@jPd$k$u{2i2>(y^~zoxOO^w;4yB(Lm6D$5-R3Y%F(?xA=Tn9Q4qYXHKn98AMAh-WF52E#5e zm&i52Bjs>+JqIL2eMm5i$P!!=*jIdQJD@o_?s_FAwmU;@Fqg%5-HL_jJ9LrZ2*CZ>~{1xb-cg>6D^t}l0ht<|G)yTY}QWLjM_Oi#e z(wCHW)*%yxUQsqdmR4%=*=M=;ADQh9ygprvHo{~=LG}aH z*t}J1_X*@<@pTsC1Im;oEOt?K2g&*ksK!s=Pd%gl!UV6vr7TKN@g2o1BJW+rjbEab z$afU8h%DXbyd@{NqnJfx>D4zZraNjRR{Q72Gu=wKqqx5;z50nowAx24tD$pt1-x3Q z^DZw-`g&%ZU9)*bvqI$m1r`%m`;3W+%yL{)C?R^E@QkgeX8pA-5psf@S&rGY3Yu-r z63nQDEMX`CYYG1N40CYd@1jW^9{tLzIp-E%BBcJn8&Gz?@K`iOCz1%I%gaKE3ffwi zOY!V7iTFQ=!vuXOQ485N5zcRcWjEOQe%t7wT**?1Ym<umjpauZVWUzWdZMXTmKv`Li3+dD2EmGID$HaQx&zS#zbCEaYD0eRW-eEca=0ZD?}C3Ce!X#39#Y6Ng5^e|}IdLi4-S1VSPCN_Wq) zWcMUwIcCbxloDy1IBdBf+pT;{RX3*+4dVqF$;>LG*unv^kK|KS}hQjhf-Od=}v_=tPmUz%UzE!jLb#%EX} z{F?k<#9>&9iNkHU2hVwei3pyXJ{!{I7Us?U?g}uR;6TZffqHSTE>Hd+;@I;_oikM@AF5X{lK@S(HpDAE22evp715OCZ!I%RM(wxa+Aoq8PLgq zi7`0xQI^k`6uXGC4LE6uz0!&LXym7OhXACr1)`9p4wJvF?{CTY4aJO|j~#uh%C)SFc} z&v^uqJmgMci=sJ}{mRgF!47w1`aI{7VF3-ubuqPPeh=O(M;@8$JP6y`lAk~p$H_wf z43T=KjX>h+LfGxr40Z?ZSP>JxZp%ln_%T;q8AL9ZVj9rq2V1Ly zPU}dLt^u-`OEC>7b7=AsBCkwVlH0S&L9adHi*hNM1w#YMEQI`Eb?nriW2gQ;zEhi0 zGCzg}wE4lOzzOGr6Fvz26_=8|B>xM?*AChIV8zU&h96`JCIglF=DlpU-^v!=YUEMm zGoleJ?iTg6j!X$Eb>@J+?)#7C#(r6d+_F@PwT`yLj>|gtxE!$njv@;m+a~1sYM3}V z&P-(elU>Jfcg*v#g#2!JJSne~Vf&c+W9ffN7=BQ6NMT(eOUN|^ZJ1sO4Q3=n+Kj`l z>Fa^D*SrpX@PIIiME!}hk>Hx}w$yaF*9-s7uAQfu`ePc9Yl44!hkCm5tVTV4EzPkc zma%h#NQ_r#tRh(oz*#GGEk$1~Q;0LL7ZjWn% z-2ep7!t$v-4ky^Yze;;7+|eEM%5(R5{lTkoDJJ!p-~-{du9?0yx0>ER9}>*-5vgYpe5(4uYI^AAX8Qgq zu(41nCiR%$1N+)?pZ7R=b_J|5T$dPMMn^I8DklC%tc0B?O?}qulb-~CV8Vvd+FCFqgTZ9vDZC&(-2X9$lo9*LL%=szx?N_dDdVUMN z>5HxwcPF`JSFi4Zcoy5sn5e^34K(>#r&QK|ENSV;6X4p{J4bW=!k$6Y9#evd>wNN1 zT{#c5i2UIx6GTZWC323lzR7OK++E11L9Rb5eBgNYUvc;}CIoOw=j$$QwvG`9TxC|Bz%p z328Z|=vW&1k~20q{dIl!$DhPg_M~X*of7pDnP@1#9usxw%y~6+<2XgfZJ1;EUN*E_ z@O@tSz1DiPpW$H4eDw2A^*Y=R*$vP8y%SFO8#PjwAXF^iXrs$?rK%qMLbm((`S?C3a*pd2>!SGzSNM$0)_rfTs;{aR zWJ@!2o)A^t9_jfnq+V^ZI(d4OF8w7eNbk>e-6&>9Upnx*`xJ}8oiw_zc2Q4pBakI zk``h;Au$=eTD@-LBF&Ncoryf|q1~&B&sxLX$y3ziqi4DfL?$8*9ZH?*eRDXV;!Pes zdDhOjJ=3=}GhK)8!In9?DXi6v!ID{LK%CuEg0ECO&zYix_FQtSy*?tg7wi(xXz6VM zGbj==CevGdO(5HCzsEYb--EkjzK+>7n^#LN=%Ami+}PcI8hS;19dmbF6EYV5ov9C& zE7t{g$9x@gcU+TF546eA?iHJzmX)!S5?{yM9oGaO$PaS$=5AA+0pDO8EP>hG(tJ+h z-Nz7o$guje(ib1ibT`$-IEcs-o4HxX_S&txc#ux*+0|*bH@JePF1doH4Vhg-?IAB2 zR{I@T?K6H0R=YV9T$>p_wZ#k`-unJQvIe>OSi&atZbpovuffFa21Up33C!IwkEhgM zFXqPPg1hSm2`+`*EWzE$ameFMUz{5`A8g+_VEecfb9c<+;cW?|-Ur`g>su!x-V)Jy z%-u1M2QuNONzSZSd+B+2zv5Cm)04xv29%KxUq$muoWq?mbQ?%8sb@(LwFjCUq~55b znR+a)In#N0gKI}~QpWqMgy}j&5rl`u%(^Xf`%Xa|$jpohzFw5MJ0{Vvt|~QlH{u?N z;r`&dxDA#z&p`Pf=nABr}PpQ^*k!Nen*RdY&cZOBGezb7mvsv;iVmi-~R)3w_ zR+l9Y@gjX;Q6y52={#oslsbG(s{ZqW8r}~*(`_1%aszVPN_~I!S&|9UI|)7$f%Q#t z047;%AGZxJi{F~*2?Jj9nj_jYOE3Y*WS~7)T&m!ldeJQ(E=7CDIY@a4!Bp40(tW=; zS$hvc7k;L2WRLoc312m_{H31yXq-}avrxw}O6{F}uRHPFK|!w)XJBhi99Lu%Pb9&NVt6dJZRgoe?%KD%igoA(JzOF`Bgmkg z8HxOZw#1~g&hGkIpT+)$rGX`wIArN?reya?`3t#Mt4Ms0YU^0SCJwut-!#nIXWeT_ zkPLV=<`wNWxGpZ0SL+mF`&Q3wr^hxO<_(n0i$pc9esF%m=4lZzk~>qI+?l+p!(*lz z%dtwTCZ*}?9&P3ozYI@Vq8b~udn)?e8Tk6eEHHg|VyZEeW~x!HVEBhTibOARz1$u= zIR?ENS^9SuHy2Dbvxv+na!rV5d0=AXb?{3;TT9Vz!#f7YvisPKVwL?9V{79Q+=e+0 zb~Uc)uMNGOY4z^&+u*v)u}Bz8qwHhI%6?@e(gBR({a_URdgCl26Nlmt8&Zu||1~i( z^S=+cG7h$-n&YrnhJyra=@{0M+hCH>ZqHngifkWCq&8g~-SY9O;8~*jb!_63lej-D z4synl$%$QpnMi+U&}L@{R|dXP5^D^;I7Cq*dma2N`-5s+iKy93NHXDQQ;h@G4Au>2 z?Q$yDSrKDGlF3P~32#fV2M>Z>_}OxBcSQX$4ahYikNa9z*Fn~WnegT0)(w{^*F!uH zi{E@H{^aCb@ytP*W05etVz5ez=T|>3xF!Xf=bc$g=2a``016f*BIYn($N3beyfiGvT)~(%(Ukfr+2%TB6ls#89@ReFnySMZ4H^njG5>0#$ zW-a+TAx|8lc)T%fVx;mH5>LkFE9DJP+T)s(a-l=Mq-vH|6*^>QADN=G$;o!J2kETT zuHJw~cta-E^5M4gqoP9=TRPK@c5D$tPD)QwDhEB3$Tr-zQqz%Xym!IGSSvr%csLH0 z;F^%JaOogj4VvxEpMT#d$sW0Y~7}$h1U$MtxOdr}$oZkz}z3Yd}^T$?#(+nPY-=) zT~z&@lO-`%nS-;*NkpJ^PukypO*t&pe1^F@)o-28;0dXz)OVm7x7>SKkE6&rx#ODB zs9`OcYGm3`sn`EJ)g6a_?>ud@mdsajO^9+cx8s^$!F2xiXxo3Vmd2vU(th+-|M?`* zTe&C9659M=gE%?q#mPw%my-!Vn}dXP$$fRg)HQD-`=a=#iIq&OOLnP{^TT-%RVwS% zSo0ktEe?{UnTb^D8e9+Acg1sn`_9+Ex;84~!{yG=vSr>yfA_b14fvX8?nIW#MEu>O zAP%4XF*DZnMtdC0D6+?%QWw`A?;QaXmj)(|sW#>ZZNjZVrHGdV?(R6aJ1)iipiLZ> zjNcR64z{oG$-bIPF+XS%hp+8y9ILjbuKp7eT*{|;;{2c-hs0s=j>eIGkk||fF2(#H zbA{L${<|mgd;8jYHtusS#r&X695w=RSo^%{dLCFyF2(#HbA?JRY&PDR^J8V5H3@qO zQHD$d+Qi{+gXelH$CrCqAP#x|;A>;=CH=0N>*!(S-jtbrHP>5sa&cn!VXhFGqOE1l zO~=3Rp2dE}XIO$+!hd=Q87`6iEZP4OI|ImKeftD&?!DXBGAGFVAbWyBBCoGI@XOpt z4S4S`!^iw!kf-5-I6rv9h`#Rb=W-+6VY}l}?7d@4T(&mNy#P5BrbyOMqT0AU@t`a& zMdn4HaC_s$cJ8)X!s;Da4bLIYH(J<=U8_G z?<-d~;O zZiSZ~Q-3VMH7V8Os}ZrZSwq|x5fzw89VU4EJtjdMZhk&DGU?{N?h~-M%bC_i0oBN* zxb5KmI|9#|-;i~h2|)4dGS5Ld4n1o`CJPcjL&EmCK~#cDCNe?DE-E#(O;_g>{hgY>^KNk z7jO5>403Pbr(e0TE*;mweIz-EZD@!?mJmydsR`P`U%KnB>a}oJREHHAR>abgmnN); zYJ5Pu*~H-=i(rEUq0pp85G~XYhfEoAO^AUF;;_l{yPUtyToFq_+{)J1UF6#VnHgku z5fR4mCEVcWxz6MoD`IR#v?Qpe;Qk2yYZHelvnNDTp9!qiHa|H3a4Wa4%ah)P(Bd#Z zXz_|4&!g;IT^l^-P5k97J=0CM7)7o(B&|flaxpSU_P-~{AZf@#td@1=*#90Rc2TLH zYFw#TLeH?Sfr*}UiEl+_;*d#1SOLI{Rs;Pq1mp{!Vd9WUM0hSE0_FE zWX^BP=7rI4l{|ZPSMMy`gAac4iN|Ci6NkL>Ar1@f!M5+^x@~a}GFiyPA=d;l?2^si z65NB&{|*Tv3oYW%Via$KeXs=f!M3m!aesLnT$8m*peZtO`O3=*#yydUpU1qSQl~Ai zp?}y}k!mcpsK!vDqMn-DVD7H=lamtJ`K(^)kQ21IM4LV{D_#E2XToD>QYpV8BFa5+ z%v&%dVB*4_B&`eAD@e^m{y|y0Cz1%ISwgh6-#>n~+p*~dcs?oh?*t%tHH!w6lEEL? zzQmdGvQQcl|0m*5J=`XG&q+bt>rh&*$BAjcs}huHNCV2qPsk_|Wf&5NA`$-CjN+EZ zkge{)$V0bLOdh(;mPRILO=}BdX{h6Q) z;TuB)Ad`XND8$mh)E`TTJ<{;pK839fg&kPm0;Za!#olJYMD zXe8u!WV$IC><+>6;}gkPNSO(Zgp|tPS1)RwD@v`}a-+L_<7ZBH)QdK{ z;-WY|SWPu{|GuY#yS7VU6E?XFncB zUqjs5cG%$t;+=utikKM`n|ByNU>i7@vXdIS&1z$ZBYu#{z%2*sCbFj^{x0^z7vaf~ zQVrvv?1wxHIfqV*7JiFpB%&$w|MuWLF$M35{5C1Pfq74~-z@()Hp>0!KxwQ=qyJbm zAnP)0twELkSQ8L#%-u27 z#k-v%b1A3V=GRd+u-mf>v&smH7xOUqY=A^4P<`)y@+ zTI2CvZ|LDHMvX{4`BqC1d}gTq#-Al&oi99hq7>D8O zn7gw{y~EM!?&LZVXD76rGL8gahn^T}&nETid|lbS;aQ3rPW{cb?nT>xuP zvvNw!ihbs^#D3Bzil{2Y-7yo#tejHU9?5iCP9CB25l_F*UVPo17i7p|KF)4q6Ykjj zW0~#+c=zGDn2BRn4*AuZbagVGAEdtocPDXbYF`y($YVavYES(E9`52do4IY~!V8JG zJ7(gTl~d{mcnA&Ko~`H33}WvrxOhqu0HlDoO`LFYKgFs_T4IJy4~D+hk? zwq)m-chdCk^{^uETpC?1nf{HPLtnb#Tn6`xtX zq&WKUk{-T<%!r}upeMMo+$qSK>s5Inm@9Ke?%k$+X$9and}*Satu za?Kf;(V@3QdVcVczPu0*#lU^JKruoCL2)Tur9y@N|AR&Fwop**gkt z6rW)Urt9zycV}OB@DI7MiSUQxGpwy+Egtg9%s5{1Lg{bbprky_I&Y@vn8ypAgLA;E zm4jE4QigDoXP-G{=|G}%`4n%^8F%|S8B>ZSe2VU}SZ3ZWb4vq>HINt#37eM7Pqg9x zZb^I!iT;rAX}S0r|2UrA&#vjAnajFg{`Z?+dcm##;O(JmUr{FuA)GOM!BWp{~;eMt+o1(it?XdUGyhF|xZ#x!u$EETr z{g{<{>WFBN>F=Dm9eex^3syNSSWN246*TWxoSiRNCEwtUtOef4n4)6|x#mVXkTBXo z`%I|olix$_;A@>m>06%JmeE-ve-UfP44z>@0y6B!J;U`QD^Kz2Utn{0OuGr$kuxbR zI<#;hzP#ME*Xx2g-+3daK(|DE9TN&nF2DmDj7d_}>E41L;HgIx9ZTD7d<(*DS=!V! zea9ePM%f2hDsG80x{#f>{dO3nm)U4rvT@M=QQi8Nw={!1I6 zQZNf)QDPiEgLh?IqM*?~dnHyZ2joB$@?lR zNnekA!h9yL(y=UKV`ElMspyc6-j^i}^nWo9K2xyZcvjvk@TbPpnOQk_tsuX8*Xhl5 z46&p6Ov*(kMN4A^z66tRh*Jblz-O8a^G3od!`FFd&0n+3x05x8>X8P+yx#EUxDRgt zTvz@Vm7+WQkM-MNPkHOE^_e(XeLfRs?qICf#7gwnZkzyJ(;v*J*%^clwM+5+@euM_ z7EVu(Z66TiwKTctSUS9G<&Js-Eb=zD)Vun%QykejH_Y!8&A%CEC^sC_Zt(FsHcHn& z^FQ9eUrYU7nLCwB+1%aczpl{-t5(*PpSKA&ro=4TP356iZKI{$pDXNMS$rJz@Wf0U z(_j`QW>IwE8K&qk3kjA?cDUuM`szEj*40-`xU@U>J(Na#A0p}y1>uZgy6ERkUO)Gv znB)LlbX}*Ys`Zke8_>{h%(1~AuGKJ|KW3Bn>W+^s!Y%B{(lV|IF@7(+$mCG42)Dd$ zl@cC?Hm}yS?@}jnYl^$|U_iJTUXA%UX4k+d=FD-Pn0S`XIusCYMQ(AHujL|&E`MI7 zL=JhS?(R`jZ~W{^|BZ=xHRj{Y@4>fo@yOlYsu?%?U)zR*lQX8?;Cne_*DzPeZnQM! zYQ5Fw)tFH;Z3NOIXYKMnKm?jA_8``zl|e47cgq6ITRbuivK*Ip*B$D&(7Wpe@hp$t z-_y7JFt5h#LGM`hnb!wj+dm?PDKl!^p4jnu=W}DjoTBu;h}q8i8FD-@Wh#2IwH~(PaG!BgRoq>yap z4EK<0!u7y7wrw2|yATnSnK)x6PNYNnEM?Iko!qLccLi+1Rw-iQY>IBzTe*5AvMxmH zVXn9oF>w|vr_x*J=;-B}y(7?B3K?d|Ilh8?Ws&g~6y5QyBO=eXz%{39z+ntJ9 z*W%k=^h+`A25PM-6{GndEPm^N#WzNCIvr;$V{AaYJ-I&_| zx6kK9ViQohGXC+I9QV}dPH(g?fqR?yxUDZnBIDrqaq>32Em>z!>Y_k`$v5b)1|~ZX zqgNNB4L(y`S~bxIlW)l8amp~~tLg3ZC`j;`(tE2X#=*3kQk}rWUDY{5$FLIlOzkbu z{Kqp8Gx?^}9c}kH1Mcmh--70j&#(luYv6GXBJNkzq z=~y&XHBi|5*_XrpKQa^C%|qI#}y^0S1$8)%-t1znH5dC4bimF9=@X%=D9P+{NlF} za(7sLl--YOQtGLB*SI@>t%`X&0c6Fg4dZ&edH2LtJNE;`yV?Cf>S z^H~yp+lJbzRZIIhC&dqdsXvxJ)aYc&MaN8B>ba*T^7C=uxhCX^doN9QADHZ&49&z& z0@-0Zv)Dd@8|HIFE0y^E3U-wjP38kne&jLyH%?t3fn`RqCGX7 zOA+t&Ld1KOzKcy*#=-oc-K$pD-{ACX*IvJmOtCD%v>Qu@S6p_uMl~lV_HZT+?RAA6 zPMaN$*Hw62UdGZVW)xW>JeDgv8J?LZ53A~Khc73Sg-ixE8nYl;a9~mV+xffAH|o1L zeCFKX!ct9H94+ZT=0{EDZur)w4%ZL-HQRYJ12M3PQDkY!sb?~G!+YoF?)uI;EnEjN zu(>D9+KCrp;o_I#y?O!heO~_JN`I9Yp3h&qaRSg^iSc}p!Sz46_6#SvS4({@$opCW)PsnZL+koBz{Kb#ib`@Il0gpFj>ggMz}i@2?bUeWUz0M zHZk(&?iCi*$ZQ|8i+DGIhJM|_ zrw$~9OSH&9lD4C&a8I~ByN&8^X6s|%2iLBwtGQlg32lDx>{yz915cb~;0?GGKYi>c zPH~s&x=+=J_Yph?__=g+4sxW%V|iOVo?7)?W&H$5y|cs@hv+;e4sBAeK~A~%X!cz1 zsTccdF2%$llZZ;)So2G7`t~yKJoo`{DJBk?L{#d-&1GJ*%3pfFgW%y(ObpqK;@I=6 zYo|`cn}l&#qopy~BY$Gr)6S>gKIPpC`#BSbtE;{dE$Wz@m2!BYMIR!Qnf}dr^NHWP zPlt`3i9;sbxF)RK*=^lByS7>bmmEJIq*!>>fj9hofMVfIX;@ln^Mi9Tes&7*@4jyw zi&11|aHzWqu|-DsHG%X#)UU_qCwFyEKBcSXQtS^W$M+1m({E2ngjj#Ck_fRCi9=&| zwg^6TRh;en1Ga4$t=P7GrjqzdTjH(xPdT@4f7%<2*=2tQX8U-im3pGo_3j_K&ii8j z5R25ydvj4VWy-j$obRiyV;&Dyvb^eE9=x#E{4m5K^|nl094$Ppz?Wbi54p)x7sp;- zw$1zM`ym#oSGymcyT9M>OV~W##xHiSy`ldO@6o+OEK;wy)l1R5g=2gPd6zI*|HQ_! zJLC6jcX*Z2EBg$d`sslL^LVht^;{g;yL6k^25s<}oE=LOZ7`1qkKe)7ovGJs_I6?% zeCE(nB*Q<~vuXuk;EIK)VJ%B9lR|C<9s8;#ZIvJmir6w9I+E#+TlDFp7Le zJcJC3ES^KgR)}c5)xUHyKrl~&1fO9E_Si&ruB~0%>fb~nbD&vc4vu|s0?*6~;!NBl z+q$^Jc10p*R~#5*;U%JM9L{b&kSgOGaqeH}Quu(&F_rW9G$iiC@*1&VkKk&V*-!xiZ=ISXw-* zQmS4Wm&km#%(?ZCFPw(619H-^Z!*(HT4p-`s_eK#-c56zO5@6%BzT@N`^My)T-m+% zTo#wu2w$~oJ1e`N!XJ)FHkOvvbIL%0`zFlc$2DQ?K9(DE zhV*sMn}e8VR=z9j1_^SGSvj>T5*hn-7xxJ4&ayKYijK)SZaa8yH%y#(W*H9}_IqZ! z4wks{raI4W%ynNshRArtzA^14k&{2SMJ~l2-?l{c$8%$QZ|Up4 zjFHf+;hW7Tf)zX*qGeHoq`8GJa5Bcf6lRG3-0jZlYBWdzHVTg_FD^ z+l@@CtGN`_8+N_fV`EJ2qV&@71|g8tdT?c5uH%z6r72 z+@p~rQ*45qk&kLp>UYG3J6g4|y9E+Vvatl$q|~MJvvtEERoxb?gSea~FA-C1Tob;I zx7?`PZ2rtye>NnDWMc`jd6}BRZ%)z44DC2Cnt5w)d}UW#$AlZRYw&wLeSw$Ot+_sL zV`t555A$j)4ce)FTXz>?(6vMiI*B?nV~(_e=pdBCk6AfHx&`5uJ~Ggr^O=7m+=6el zHHf4$8HC$pWGs~LOv4Yd1aoXk4g0mb?gK{gR1k;q{c0FRF2x)hzBx~AtbfKe&%qVU zOj($qHnQl8?W$|bZe^5O@}3m*f+i>!Zt+10y6C1d&L}>kNPM8;dwFIxNU&gfw^%K9gB zW&UI(=A~Q`E&Z+yO2L9b6dlh9a(ZuT<39eR>yIPk>(B<}24v|Ib9XEe9!sxMPkYXp z2kQ6iOc}Y!wr5jxOxKxF$o~;Bi)cBf=y=|sZ8_XZ8x~n?X@-`wC_1X?pH@qekp0y# zak6W3O-j`SMYnPGE@#;pAYc3_n*mvDN{O^h(M|a9Mz`U6pE+gCVNoRRj!W4T-3u*q z+%x-bcFsm*RVL23RKZ$|uNSgs`}A3}ksaJ!#~QhQ$+>5)u# z^DoLh6B;k>FEe;HclSrD95;r!np7184-t7hZ}wctirNd!#7WRh@Ea>@BUnoT#^kpz zoXw?W-f?hu{6)tEm;H6u?ZO?-spEF9ef_5)y7qq%*<|)J{+B5;YE0BA^>Ohw=Z=iU zF&E#pOwMh{1pjgAQ@*q<(LB1@QT?iW=i|GUxw}Sxf<$Zhq%XlWDRpe1rv9!!W@vF1r660=O=Nww?I=kZIFx(we zdol~9dtZo4Y#6Z8iPpK(+rJB11EM82G{?v@9`;M|jDW5I;dWZ{(fXv~S7L18VZx2c zH+X;kHP=}Sif(KF6*1w`Rv%vxL{P@DA?lcXL!2UH8gB!CwW>Ey@VH*4-E6|`U04}b zVn6BKa)KxM;Eews*URJ^^zDtiI*|o~^jUu*<^WN2OuN~H+sLEJy}WXkI}S1E`ixla z$Fvu%GuO-H8{XUBZ{hs#MYgVlZyc^yz90?lW)p4?URBNQjOq3|hMATflfUZ&k_!tLEYN$&O44?0s}85K=I(Xl1IcN5nucWp?~9lLH% zB%@7josF1IT(4-qR_{yqzsZ@XL;S?S<<62T=Q`Iwmm%vav+g3F0OfjRT_skiIx}xi zq;<#I`Yh;VxL&5{>^7c3_Q?OHSJy@H_*aItV~Wn^?xxon@BDXXW!(n43@*hK9n*D) zI)pKte@D4j2b?gM5*>-5=xpw80(4}1pd%}e>&Q$g*&hu>C$pY9u18#dwF|rGuh>QR z#dlFtO6JW_bTV&-t^?&XzT8=cxPyD3TjEkOqlTihxw~JW!TC?U@!rjl;8L

^STM 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͢g>BY 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 Date: Thu, 25 Jul 2019 13:10:57 +0200 Subject: [PATCH 412/627] Added tooltip to filename fields for custom texture and model into bed shape dialog to show the full path of the file --- src/slic3r/GUI/BedShapeDialog.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 60b4f37f16..5446550a7f 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -208,6 +208,14 @@ wxPanel* BedShapePanel::init_texture_panel() filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) { e.SetText(_(boost::filesystem::path(m_custom_texture).filename().string())); + wxStaticText* lbl = dynamic_cast(e.GetEventObject()); + if (lbl != nullptr) + { + wxString tooltip_text = (m_custom_texture == NONE) ? _(L("")) : _(m_custom_texture); + wxToolTip* tooltip = lbl->GetToolTip(); + if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text)) + lbl->SetToolTip(tooltip_text); + } })); remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) @@ -268,6 +276,14 @@ wxPanel* BedShapePanel::init_model_panel() filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) { e.SetText(_(boost::filesystem::path(m_custom_model).filename().string())); + wxStaticText* lbl = dynamic_cast(e.GetEventObject()); + if (lbl != nullptr) + { + wxString tooltip_text = (m_custom_model == NONE) ? _(L("")) : _(m_custom_model); + wxToolTip* tooltip = lbl->GetToolTip(); + if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text)) + lbl->SetToolTip(tooltip_text); + } })); remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) From e23dbfa3fb9396b9f31482b57d3e4a3501c837b3 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 25 Jul 2019 13:42:42 +0200 Subject: [PATCH 413/627] Added missing include (OsX build) --- src/slic3r/GUI/BedShapeDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 5446550a7f..1177c91804 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "libslic3r/BoundingBox.hpp" #include "libslic3r/Model.hpp" From f8218eb9036a40846682f6beb7947d64b920253e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 25 Jul 2019 14:39:19 +0200 Subject: [PATCH 414/627] Reworked the FFF Print::update() to process the filament retract override values and to store them into the output G-code. --- src/libslic3r/Config.hpp | 71 +++++++++++++++++- src/libslic3r/GCode.cpp | 23 ++---- src/libslic3r/PlaceholderParser.cpp | 18 ++--- src/libslic3r/PlaceholderParser.hpp | 1 + src/libslic3r/Print.cpp | 110 +++++++++++++++++++++------- src/libslic3r/Print.hpp | 9 ++- src/libslic3r/PrintBase.hpp | 9 ++- src/libslic3r/PrintConfig.cpp | 19 +++++ src/libslic3r/PrintConfig.hpp | 8 ++ src/libslic3r/SLAPrint.cpp | 6 +- src/libslic3r/SLAPrint.hpp | 2 +- 11 files changed, 210 insertions(+), 66 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 64fc69dd55..f1fab91fd3 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -129,6 +129,19 @@ public: virtual bool nullable() const { return false; } // A scalar is nil, or all values of a vector are nil. virtual bool is_nil() const { return false; } + // Is this option overridden by another option? + // An option overrides another option if it is not nil and not equal. + virtual bool overriden_by(const ConfigOption *rhs) const { + assert(! this->nullable() && ! rhs->nullable()); + return *this != *rhs; + } + // Apply an override option, possibly a nullable one. + virtual bool apply_override(const ConfigOption *rhs) { + if (*this == *rhs) + return false; + *this = *rhs; + return true; + } }; typedef ConfigOption* ConfigOptionPtr; @@ -309,6 +322,62 @@ public: bool operator==(const std::vector &rhs) const { return this->values == rhs; } bool operator!=(const std::vector &rhs) const { return this->values != rhs; } + // Is this option overridden by another option? + // An option overrides another option if it is not nil and not equal. + bool overriden_by(const ConfigOption *rhs) const override { + if (this->nullable()) + throw std::runtime_error("Cannot override a nullable ConfigOption."); + if (rhs->type() != this->type()) + throw std::runtime_error("ConfigOptionVector.overriden_by() applied to different types."); + auto rhs_vec = static_cast*>(rhs); + if (! rhs->nullable()) + // Overridding a non-nullable object with another non-nullable object. + return this->values != rhs_vec->values; + size_t i = 0; + size_t cnt = std::min(this->size(), rhs_vec->size()); + for (; i < cnt; ++ i) + if (! rhs_vec->is_nil(i) && this->values[i] != rhs_vec->values[i]) + return true; + for (; i < rhs_vec->size(); ++ i) + if (! rhs_vec->is_nil(i)) + return true; + return false; + } + // Apply an override option, possibly a nullable one. + bool apply_override(const ConfigOption *rhs) override { + if (this->nullable()) + throw std::runtime_error("Cannot override a nullable ConfigOption."); + if (rhs->type() != this->type()) + throw std::runtime_error("ConfigOptionVector.apply_override() applied to different types."); + auto rhs_vec = static_cast*>(rhs); + if (! rhs->nullable()) { + // Overridding a non-nullable object with another non-nullable object. + if (this->values != rhs_vec->values) { + this->values = rhs_vec->values; + return true; + } + return false; + } + size_t i = 0; + size_t cnt = std::min(this->size(), rhs_vec->size()); + bool modified = false; + for (; i < cnt; ++ i) + if (! rhs_vec->is_nil(i) && this->values[i] != rhs_vec->values[i]) { + this->values[i] = rhs_vec->values[i]; + modified = true; + } + for (; i < rhs_vec->size(); ++ i) + if (! rhs_vec->is_nil(i)) { + if (this->values.empty()) + this->values.resize(i + 1); + else + this->values.resize(i + 1, this->values.front()); + this->values[i] = rhs_vec->values[i]; + modified = true; + } + return false; + } + private: friend class cereal::access; template void serialize(Archive & ar) { ar(this->values); } @@ -1595,7 +1664,7 @@ public: // Allow DynamicConfig to be instantiated on ints own without a definition. // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. - const ConfigDef* def() const override { return nullptr; }; + const ConfigDef* def() const override { return nullptr; } template T* opt(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast(this->option(opt_key, create)); } template const T* opt(const t_config_option_key &opt_key) const diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 81880831f1..bc37300269 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1829,25 +1829,12 @@ void GCode::apply_print_config(const PrintConfig &print_config) m_config.apply(print_config); } -void GCode::append_full_config(const Print& print, std::string& str) +void GCode::append_full_config(const Print &print, std::string &str) { - const StaticPrintConfig *configs[] = { static_cast(&print.config()), &print.default_object_config(), &print.default_region_config() }; - for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++i) { - const StaticPrintConfig *cfg = configs[i]; - for (const std::string &key : cfg->keys()) - if (key != "compatible_printers") - str += "; " + key + " = " + cfg->opt_serialize(key) + "\n"; - } - const DynamicConfig &full_config = print.placeholder_parser().config(); - for (const char *key : { - "print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id", - "printer_model", "printer_variant", - "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile", - "compatible_prints_condition_cummulative", "compatible_printers_condition_cummulative", "inherits_cummulative" }) { - const ConfigOption *opt = full_config.option(key); - if (opt != nullptr) - str += std::string("; ") + key + " = " + opt->serialize() + "\n"; - } + const DynamicPrintConfig &cfg = print.full_print_config(); + for (const std::string &key : cfg.keys()) + if (key != "compatible_prints" && key != "compatible_printers" && ! cfg.option(key)->is_nil()) + str += "; " + key + " = " + cfg.opt_serialize(key) + "\n"; } void GCode::set_extruders(const std::vector &extruder_ids) diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 604afad204..c3ac22e968 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -94,14 +94,6 @@ void PlaceholderParser::update_timestamp(DynamicConfig &config) config.set_key_value("second", new ConfigOptionInt(timeinfo->tm_sec)); } -// Ignore this key by the placeholder parser. -static inline bool placeholder_parser_ignore(const ConfigDef *def, const std::string &opt_key) -{ - const ConfigOptionDef *opt_def = def->get(opt_key); - assert(opt_def != nullptr); - return (opt_def->multiline && boost::ends_with(opt_key, "_gcode")) || opt_key == "post_process"; -} - static inline bool opts_equal(const DynamicConfig &config_old, const DynamicConfig &config_new, const std::string &opt_key) { const ConfigOption *opt_old = config_old.option(opt_key); @@ -119,7 +111,7 @@ std::vector PlaceholderParser::config_diff(const DynamicPrintConfig const ConfigDef *def = rhs.def(); std::vector diff_keys; for (const t_config_option_key &opt_key : rhs.keys()) - if (! placeholder_parser_ignore(def, opt_key) && ! opts_equal(m_config, rhs, opt_key)) + if (! opts_equal(m_config, rhs, opt_key)) diff_keys.emplace_back(opt_key); return diff_keys; } @@ -135,8 +127,6 @@ bool PlaceholderParser::apply_config(const DynamicPrintConfig &rhs) const ConfigDef *def = rhs.def(); bool modified = false; for (const t_config_option_key &opt_key : rhs.keys()) { - if (placeholder_parser_ignore(def, opt_key)) - continue; if (! opts_equal(m_config, rhs, opt_key)) { // Store a copy of the config option. // Convert FloatOrPercent values to floats first. @@ -155,7 +145,6 @@ bool PlaceholderParser::apply_config(const DynamicPrintConfig &rhs) void PlaceholderParser::apply_only(const DynamicPrintConfig &rhs, const std::vector &keys) { for (const t_config_option_key &opt_key : keys) { - assert(! placeholder_parser_ignore(rhs.def(), opt_key)); // Store a copy of the config option. // Convert FloatOrPercent values to floats first. //FIXME there are some ratio_over chains, which end with empty ratio_with. @@ -167,6 +156,11 @@ void PlaceholderParser::apply_only(const DynamicPrintConfig &rhs, const std::vec } } +void PlaceholderParser::apply_config(DynamicPrintConfig &&rhs) +{ + m_config += std::move(rhs); +} + void PlaceholderParser::apply_env_variables() { for (char** env = environ; *env; ++ env) { diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index f682c3252f..14fdc5c285 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -19,6 +19,7 @@ public: std::vector config_diff(const DynamicPrintConfig &rhs); // Return true if modified. bool apply_config(const DynamicPrintConfig &config); + void apply_config(DynamicPrintConfig &&config); // To be called on the values returned by PlaceholderParser::config_diff(). // The keys should already be valid. void apply_only(const DynamicPrintConfig &config, const std::vector &keys); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index ac2e4f151e..2cb48353df 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -485,25 +485,79 @@ bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_c return true; } -Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &config_in) +// Collect diffs of configuration values at various containers, +// resolve the filament rectract overrides of extruder retract values. +void Print::config_diffs( + const DynamicPrintConfig &new_full_config, + t_config_option_keys &print_diff, t_config_option_keys &object_diff, t_config_option_keys ®ion_diff, + t_config_option_keys &full_config_diff, + DynamicPrintConfig &placeholder_parser_overrides, + DynamicPrintConfig &filament_overrides) const +{ + // Collect changes to print config, account for overrides of extruder retract values by filament presets. + { + const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); + const std::string filament_prefix = "filament_"; + for (const t_config_option_key &opt_key : m_config.keys()) { + const ConfigOption *opt_old = m_config.option(opt_key); + assert(opt_old != nullptr); + const ConfigOption *opt_new = new_full_config.option(opt_key); + assert(opt_new != nullptr); + const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; + if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { + // An extruder retract override is available at some of the filament presets. + if (*opt_old != *opt_new || opt_new->overriden_by(opt_new_filament)) { + auto opt_copy = opt_new->clone(); + opt_copy->apply_override(opt_new_filament); + if (*opt_old == *opt_copy) + delete opt_copy; + else { + filament_overrides.set_key_value(opt_key, opt_copy); + print_diff.emplace_back(opt_key); + } + } + } else if (*opt_new != *opt_old) + print_diff.emplace_back(opt_key); + } + } + // Collect changes to object and region configs. + object_diff = m_default_object_config.diff(new_full_config); + region_diff = m_default_region_config.diff(new_full_config); + // Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser. + // As the PlaceholderParser does not interpret the FloatOrPercent values itself, these values are stored into the PlaceholderParser converted to floats. + for (const t_config_option_key &opt_key : new_full_config.keys()) { + const ConfigOption *opt_old = m_full_print_config.option(opt_key); + const ConfigOption *opt_new = new_full_config.option(opt_key); + if (opt_old == nullptr || *opt_new != *opt_old) + full_config_diff.emplace_back(opt_key); + if (opt_new->type() == coFloatOrPercent) { + // The m_placeholder_parser is never modified by the background processing, GCode.cpp/hpp makes a copy. + const ConfigOption *opt_old_pp = this->placeholder_parser().config().option(opt_key); + double new_value = new_full_config.get_abs_value(opt_key); + if (opt_old_pp == nullptr || static_cast(opt_old_pp)->value != new_value) + placeholder_parser_overrides.set_key_value(opt_key, new ConfigOptionFloat(new_value)); + } + } +} + +Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config) { #ifdef _DEBUG check_model_ids_validity(model); #endif /* _DEBUG */ - // Make a copy of the config, normalize it. - DynamicPrintConfig config(config_in); - config.option("print_settings_id", true); - config.option("filament_settings_id", true); - config.option("printer_settings_id", true); - config.normalize(); - // Collect changes to print config. - t_config_option_keys print_diff = m_config.diff(config); - t_config_option_keys object_diff = m_default_object_config.diff(config); - t_config_option_keys region_diff = m_default_region_config.diff(config); - t_config_option_keys placeholder_parser_diff = this->placeholder_parser().config_diff(config); + // Normalize the config. + new_full_config.option("print_settings_id", true); + new_full_config.option("filament_settings_id", true); + new_full_config.option("printer_settings_id", true); + new_full_config.normalize(); - // Do not use the ApplyStatus as we will use the max function when updating apply_status. + // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. + t_config_option_keys print_diff, object_diff, region_diff, full_config_diff; + DynamicPrintConfig placeholder_parser_overrides, filament_overrides; + this->config_diffs(new_full_config, print_diff, object_diff, region_diff, full_config_diff, placeholder_parser_overrides, filament_overrides); + + // Do not use the ApplyStatus as we will use the max function when updating apply_status. unsigned int apply_status = APPLY_STATUS_UNCHANGED; auto update_apply_status = [&apply_status](bool invalidated) { apply_status = std::max(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); }; @@ -516,24 +570,26 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co // The following call may stop the background processing. if (! print_diff.empty()) update_apply_status(this->invalidate_state_by_config_options(print_diff)); + // Apply variables to placeholder parser. The placeholder parser is used by G-code export, // which should be stopped if print_diff is not empty. - if (! placeholder_parser_diff.empty()) { + if (! full_config_diff.empty() || ! placeholder_parser_overrides.empty()) { update_apply_status(this->invalidate_step(psGCodeExport)); PlaceholderParser &pp = this->placeholder_parser(); - pp.apply_only(config, placeholder_parser_diff); + pp.apply_config(std::move(placeholder_parser_overrides)); // Set the profile aliases for the PrintBase::output_filename() - pp.set("print_preset", config.option("print_settings_id")->clone()); - pp.set("filament_preset", config.option("filament_settings_id")->clone()); - pp.set("printer_preset", config.option("printer_settings_id")->clone()); + pp.set("print_preset", new_full_config.option("print_settings_id")->clone()); + pp.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); + pp.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); + // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. + m_config.apply_only(new_full_config, print_diff, true); + m_config.apply(filament_overrides); + // Handle changes to object config defaults + m_default_object_config.apply_only(new_full_config, object_diff, true); + // Handle changes to regions config defaults + m_default_region_config.apply_only(new_full_config, region_diff, true); + m_full_print_config = std::move(new_full_config); } - - // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. - m_config.apply_only(config, print_diff, true); - // Handle changes to object config defaults - m_default_object_config.apply_only(config, object_diff, true); - // Handle changes to regions config defaults - m_default_region_config.apply_only(config, region_diff, true); class LayerRanges { @@ -1698,8 +1754,8 @@ void Print::_make_wipe_tower() (float)m_config.filament_cooling_initial_speed.get_at(i), (float)m_config.filament_cooling_final_speed.get_at(i), m_config.filament_ramming_parameters.get_at(i), - m_config.filament_max_volumetric_speed.get_at(i), - m_config.nozzle_diameter.get_at(i)); + (float)m_config.filament_max_volumetric_speed.get_at(i), + (float)m_config.nozzle_diameter.get_at(i)); m_wipe_tower_data.priming = Slic3r::make_unique>( wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 368bf4ee8b..b6d7b678d2 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -296,7 +296,7 @@ public: void clear() override; bool empty() const override { return m_objects.empty(); } - ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; + ApplyStatus apply(const Model &model, DynamicPrintConfig config) override; void process() override; // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. @@ -368,6 +368,13 @@ protected: bool invalidate_step(PrintStep step); private: + void config_diffs( + const DynamicPrintConfig &new_full_config, + t_config_option_keys &print_diff, t_config_option_keys &object_diff, t_config_option_keys ®ion_diff, + t_config_option_keys &full_config_diff, + DynamicPrintConfig &placeholder_parser_overrides, + DynamicPrintConfig &filament_overrides) const; + bool invalidate_state_by_config_options(const std::vector &opt_keys); void _make_skirt(); diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index d84d492a00..b0258fc647 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -217,7 +217,7 @@ protected: class PrintBase { public: - PrintBase() { this->restart(); } + PrintBase() : m_placeholder_parser(&m_full_print_config) { this->restart(); } inline virtual ~PrintBase() {} virtual PrinterTechnology technology() const noexcept = 0; @@ -240,7 +240,7 @@ public: // Some data was changed, which in turn invalidated already calculated steps. APPLY_STATUS_INVALIDATED, }; - virtual ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) = 0; + virtual ApplyStatus apply(const Model &model, DynamicPrintConfig config) = 0; const Model& model() const { return m_model; } struct TaskParams { @@ -316,7 +316,7 @@ public: virtual bool finished() const = 0; const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; } - PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } + const DynamicPrintConfig& full_print_config() const { return m_full_print_config; } virtual std::string output_filename(const std::string &filename_base = std::string()) const = 0; // If the filename_base is set, it is used as the input for the template processing. In that case the path is expected to be the directory (may be empty). @@ -327,6 +327,8 @@ protected: friend class PrintObjectBase; friend class BackgroundSlicingProcess; + PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } + tbb::mutex& state_mutex() const { return m_state_mutex; } std::function cancel_callback() { return m_cancel_callback; } void call_cancel_callback() { m_cancel_callback(); } @@ -341,6 +343,7 @@ protected: void update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const; Model m_model; + DynamicPrintConfig m_full_print_config; private: tbb::atomic m_cancel_status; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index e147e10871..8ec74b83a2 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -29,6 +29,7 @@ PrintConfigDef::PrintConfigDef() this->init_common_params(); assign_printer_technology_to_unknown(this->options, ptAny); this->init_fff_params(); + this->init_extruder_retract_keys(); assign_printer_technology_to_unknown(this->options, ptFFF); this->init_sla_params(); assign_printer_technology_to_unknown(this->options, ptSLA); @@ -2254,6 +2255,24 @@ void PrintConfigDef::init_fff_params() } } +void PrintConfigDef::init_extruder_retract_keys() +{ + m_extruder_retract_keys = { + "deretract_speed", + "retract_before_travel", + "retract_before_wipe", + "retract_layer_change", + "retract_length", + "retract_lift", + "retract_lift_above", + "retract_lift_below", + "retract_restart_extra", + "retract_speed", + "wipe" + }; + assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end())); +} + void PrintConfigDef::init_sla_params() { ConfigOptionDef* def; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5731bef00f..f2d0775fa0 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -185,10 +185,18 @@ public: static void handle_legacy(t_config_option_key &opt_key, std::string &value); + // Options defining the extruder retract properties. These keys are sorted lexicographically. + // The extruder retract keys could be overidden by the same values defined at the Filament level + // (then the key is further prefixed with the "filament_" prefix). + const std::vector& extruder_retract_keys() const { return m_extruder_retract_keys; } + private: void init_common_params(); void init_fff_params(); + void init_extruder_retract_keys(); void init_sla_params(); + + std::vector m_extruder_retract_keys; }; // The one and only global definition of SLic3r configuration options. diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index fb2a5d7df3..7060536450 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -145,14 +145,13 @@ static std::vector sla_instances(const ModelObject &mo return instances; } -SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConfig &config_in) +SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig config) { #ifdef _DEBUG check_model_ids_validity(model); #endif /* _DEBUG */ - // Make a copy of the config, normalize it. - DynamicPrintConfig config(config_in); + // Normalize the config. config.option("sla_print_settings_id", true); config.option("sla_material_settings_id", true); config.option("printer_settings_id", true); @@ -459,6 +458,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf check_model_ids_equal(m_model, model); #endif /* _DEBUG */ + m_full_print_config = std::move(config); return static_cast(apply_status); } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 9620e9b68d..f6d0781a83 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -349,7 +349,7 @@ public: void clear() override; bool empty() const override { return m_objects.empty(); } - ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; + ApplyStatus apply(const Model &model, DynamicPrintConfig config) override; void set_task(const TaskParams ¶ms) override; void process() override; void finalize() override; From 34013ad14206dc92008d3504273ebae97a896cf4 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 25 Jul 2019 14:52:51 +0200 Subject: [PATCH 415/627] Fixed broken Perl bindings. --- xs/xsp/Print.xsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index df5f48587a..74c0f4b0ce 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -75,7 +75,7 @@ _constant() Ref config() %code%{ RETVAL = const_cast(static_cast(&THIS->config())); %}; Ref placeholder_parser() - %code%{ RETVAL = &THIS->placeholder_parser(); %}; + %code%{ RETVAL = const_cast(&THIS->placeholder_parser()); %}; Ref skirt() %code%{ RETVAL = const_cast(&THIS->skirt()); %}; Ref brim() From 4e4bdb5edd35d2e030585c5f9929cc7f0f5bd3db Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 25 Jul 2019 15:28:34 +0200 Subject: [PATCH 416/627] Removed obsolete code --- src/slic3r/GUI/GLCanvas3D.cpp | 428 +++++++++++----------- src/slic3r/GUI/GLCanvas3D.hpp | 2 - src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 1 - 3 files changed, 206 insertions(+), 225 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 38c0315abf..63c7eba2b3 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1219,7 +1219,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar , m_moving_enabled(false) , m_dynamic_background_enabled(false) , m_multisample_allowed(false) - , m_regenerate_volumes(true) , m_moving(false) , m_tab_down(false) , m_cursor_type(Standard) @@ -1858,245 +1857,235 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re PrinterTechnology printer_technology = m_process->current_printer_technology(); int volume_idx_wipe_tower_old = -1; - if (printer_technology == ptSLA) - // Always do the full refresh in SLA mode to show / hide SLA support structures when an object is moved outside / inside the build volume. - m_regenerate_volumes = true; - - if (m_regenerate_volumes) - { - // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). - // First initialize model_volumes_new_sorted & model_instances_new_sorted. - for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++ object_idx) { - const ModelObject *model_object = m_model->objects[object_idx]; - for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++ instance_idx) { - const ModelInstance *model_instance = model_object->instances[instance_idx]; - for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++ volume_idx) { - const ModelVolume *model_volume = model_object->volumes[volume_idx]; - model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); - } + // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). + // First initialize model_volumes_new_sorted & model_instances_new_sorted. + for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++ object_idx) { + const ModelObject *model_object = m_model->objects[object_idx]; + for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++ instance_idx) { + const ModelInstance *model_instance = model_object->instances[instance_idx]; + for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++ volume_idx) { + const ModelVolume *model_volume = model_object->volumes[volume_idx]; + model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); } } - if (printer_technology == ptSLA) { - const SLAPrint *sla_print = this->sla_print(); - #ifndef NDEBUG - // Verify that the SLAPrint object is synchronized with m_model. - check_model_ids_equal(*m_model, sla_print->model()); - #endif /* NDEBUG */ - sla_support_state.reserve(sla_print->objects().size()); - for (const SLAPrintObject *print_object : sla_print->objects()) { - SLASupportState state; - for (size_t istep = 0; istep < sla_steps.size(); ++ istep) { - state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); - if (state.step[istep].state == PrintStateBase::DONE) { - if (! print_object->has_mesh(sla_steps[istep])) - // Consider the DONE step without a valid mesh as invalid for the purpose - // of mesh visualization. - state.step[istep].state = PrintStateBase::INVALID; - else - for (const ModelInstance *model_instance : print_object->model_object()->instances) - // Only the instances, which are currently printable, will have the SLA support structures kept. - // The instances outside the print bed will have the GLVolumes of their support structures released. - if (model_instance->is_printable()) - aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); - } - } - sla_support_state.emplace_back(state); - } - } - std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); - std::sort(aux_volume_state .begin(), aux_volume_state .end(), model_volume_state_lower); - // Release all ModelVolume based GLVolumes not found in the current Model. - for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++ volume_id) { - GLVolume *volume = m_volumes.volumes[volume_id]; - ModelVolumeState key(volume); - ModelVolumeState *mvs = nullptr; - if (volume->volume_idx() < 0) { - auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); - if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id) - mvs = &(*it); - } else { - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); - if (it != model_volume_state.end() && it->geometry_id == key.geometry_id) - mvs = &(*it); - } - // Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID. - // The wipe tower has its own wipe_tower_instance_id(). - if (m_selection.contains_volume(volume_id)) - instance_ids_selected.emplace_back(volume->geometry_id.second); - if (mvs == nullptr || force_full_scene_refresh) { - // This GLVolume will be released. - if (volume->is_wipe_tower) { - // There is only one wipe tower. - assert(volume_idx_wipe_tower_old == -1); - volume_idx_wipe_tower_old = (int)volume_id; - } - if (! m_reload_delayed) - delete volume; - } else { - // This GLVolume will be reused. - volume->set_sla_shift_z(0.0); - map_glvolume_old_to_new[volume_id] = glvolumes_new.size(); - mvs->volume_idx = glvolumes_new.size(); - glvolumes_new.emplace_back(volume); - // Update color of the volume based on the current extruder. - if (mvs->model_volume != nullptr) { - int extruder_id = mvs->model_volume->extruder_id(); - if (extruder_id != -1) - volume->extruder_id = extruder_id; - - volume->is_modifier = !mvs->model_volume->is_model_part(); - volume->set_color_from_model_volume(mvs->model_volume); - - // updates volumes transformations - volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); - volume->set_volume_transformation(mvs->model_volume->get_transformation()); - } - } - } - sort_remove_duplicates(instance_ids_selected); } + if (printer_technology == ptSLA) { + const SLAPrint *sla_print = this->sla_print(); + #ifndef NDEBUG + // Verify that the SLAPrint object is synchronized with m_model. + check_model_ids_equal(*m_model, sla_print->model()); + #endif /* NDEBUG */ + sla_support_state.reserve(sla_print->objects().size()); + for (const SLAPrintObject *print_object : sla_print->objects()) { + SLASupportState state; + for (size_t istep = 0; istep < sla_steps.size(); ++ istep) { + state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]); + if (state.step[istep].state == PrintStateBase::DONE) { + if (! print_object->has_mesh(sla_steps[istep])) + // Consider the DONE step without a valid mesh as invalid for the purpose + // of mesh visualization. + state.step[istep].state = PrintStateBase::INVALID; + else + for (const ModelInstance *model_instance : print_object->model_object()->instances) + // Only the instances, which are currently printable, will have the SLA support structures kept. + // The instances outside the print bed will have the GLVolumes of their support structures released. + if (model_instance->is_printable()) + aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id()); + } + } + sla_support_state.emplace_back(state); + } + } + std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower); + std::sort(aux_volume_state .begin(), aux_volume_state .end(), model_volume_state_lower); + // Release all ModelVolume based GLVolumes not found in the current Model. + for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++ volume_id) { + GLVolume *volume = m_volumes.volumes[volume_id]; + ModelVolumeState key(volume); + ModelVolumeState *mvs = nullptr; + if (volume->volume_idx() < 0) { + auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); + if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id) + mvs = &(*it); + } else { + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + if (it != model_volume_state.end() && it->geometry_id == key.geometry_id) + mvs = &(*it); + } + // Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID. + // The wipe tower has its own wipe_tower_instance_id(). + if (m_selection.contains_volume(volume_id)) + instance_ids_selected.emplace_back(volume->geometry_id.second); + if (mvs == nullptr || force_full_scene_refresh) { + // This GLVolume will be released. + if (volume->is_wipe_tower) { + // There is only one wipe tower. + assert(volume_idx_wipe_tower_old == -1); + volume_idx_wipe_tower_old = (int)volume_id; + } + if (! m_reload_delayed) + delete volume; + } else { + // This GLVolume will be reused. + volume->set_sla_shift_z(0.0); + map_glvolume_old_to_new[volume_id] = glvolumes_new.size(); + mvs->volume_idx = glvolumes_new.size(); + glvolumes_new.emplace_back(volume); + // Update color of the volume based on the current extruder. + if (mvs->model_volume != nullptr) { + int extruder_id = mvs->model_volume->extruder_id(); + if (extruder_id != -1) + volume->extruder_id = extruder_id; + + volume->is_modifier = !mvs->model_volume->is_model_part(); + volume->set_color_from_model_volume(mvs->model_volume); + + // updates volumes transformations + volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); + volume->set_volume_transformation(mvs->model_volume->get_transformation()); + } + } + } + sort_remove_duplicates(instance_ids_selected); if (m_reload_delayed) return; bool update_object_list = false; - if (m_regenerate_volumes) - { - if (m_volumes.volumes != glvolumes_new) - update_object_list = true; - m_volumes.volumes = std::move(glvolumes_new); - for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) { - const ModelObject &model_object = *m_model->objects[obj_idx]; - for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) { - const ModelVolume &model_volume = *model_object.volumes[volume_idx]; - for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { - const ModelInstance &model_instance = *model_object.instances[instance_idx]; - ModelVolumeState key(model_volume.id(), model_instance.id()); - auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); - assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); - if (it->new_geometry()) { - // New volume. - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by); - m_volumes.volumes.back()->geometry_id = key.geometry_id; + if (m_volumes.volumes != glvolumes_new) + update_object_list = true; + m_volumes.volumes = std::move(glvolumes_new); + for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) { + const ModelObject &model_object = *m_model->objects[obj_idx]; + for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) { + const ModelVolume &model_volume = *model_object.volumes[volume_idx]; + for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) { + const ModelInstance &model_instance = *model_object.instances[instance_idx]; + ModelVolumeState key(model_volume.id(), model_instance.id()); + auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); + assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); + if (it->new_geometry()) { + // New volume. + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by); + m_volumes.volumes.back()->geometry_id = key.geometry_id; + update_object_list = true; + } else { + // Recycling an old GLVolume. + GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx]; + assert(existing_volume.geometry_id == key.geometry_id); + // Update the Object/Volume/Instance indices into the current Model. + if (existing_volume.composite_id != it->composite_id) { + existing_volume.composite_id = it->composite_id; update_object_list = true; - } else { - // Recycling an old GLVolume. - GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx]; - assert(existing_volume.geometry_id == key.geometry_id); - // Update the Object/Volume/Instance indices into the current Model. - if (existing_volume.composite_id != it->composite_id) { - existing_volume.composite_id = it->composite_id; - update_object_list = true; + } + } + } + } + } + if (printer_technology == ptSLA) { + size_t idx = 0; + const SLAPrint *sla_print = this->sla_print(); + std::vector shift_zs(m_model->objects.size(), 0); + double relative_correction_z = sla_print->relative_correction().z(); + if (relative_correction_z <= EPSILON) + relative_correction_z = 1.; + for (const SLAPrintObject *print_object : sla_print->objects()) { + SLASupportState &state = sla_support_state[idx ++]; + const ModelObject *model_object = print_object->model_object(); + // Find an index of the ModelObject + int object_idx; + if (std::all_of(state.step.begin(), state.step.end(), [](const PrintStateBase::StateWithTimeStamp &state){ return state.state != PrintStateBase::DONE; })) + continue; + // There may be new SLA volumes added to the scene for this print_object. + // Find the object index of this print_object in the Model::objects list. + auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object); + assert(it != sla_print->model().objects.end()); + object_idx = it - sla_print->model().objects.begin(); + // Cache the Z offset to be applied to all volumes with this object_idx. + shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z; + // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene. + // pairs of + std::vector> instances[std::tuple_size::value]; + for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) { + const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx]; + // Find index of ModelInstance corresponding to this SLAPrintObject::Instance. + auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), + [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; }); + assert(it != model_object->instances.end()); + int instance_idx = it - model_object->instances.begin(); + for (size_t istep = 0; istep < sla_steps.size(); ++ istep) + if (state.step[istep].state == PrintStateBase::DONE) { + ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); + auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); + assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); + if (it->new_geometry()) + instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); + else { + // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. + m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); + m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); } } - } - } - } - if (printer_technology == ptSLA) { - size_t idx = 0; - const SLAPrint *sla_print = this->sla_print(); - std::vector shift_zs(m_model->objects.size(), 0); - double relative_correction_z = sla_print->relative_correction().z(); - if (relative_correction_z <= EPSILON) - relative_correction_z = 1.; - for (const SLAPrintObject *print_object : sla_print->objects()) { - SLASupportState &state = sla_support_state[idx ++]; - const ModelObject *model_object = print_object->model_object(); - // Find an index of the ModelObject - int object_idx; - if (std::all_of(state.step.begin(), state.step.end(), [](const PrintStateBase::StateWithTimeStamp &state){ return state.state != PrintStateBase::DONE; })) - continue; - // There may be new SLA volumes added to the scene for this print_object. - // Find the object index of this print_object in the Model::objects list. - auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object); - assert(it != sla_print->model().objects.end()); - object_idx = it - sla_print->model().objects.begin(); - // Cache the Z offset to be applied to all volumes with this object_idx. - shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z; - // Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene. - // pairs of - std::vector> instances[std::tuple_size::value]; - for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) { - const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx]; - // Find index of ModelInstance corresponding to this SLAPrintObject::Instance. - auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(), - [&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; }); - assert(it != model_object->instances.end()); - int instance_idx = it - model_object->instances.begin(); - for (size_t istep = 0; istep < sla_steps.size(); ++ istep) - if (state.step[istep].state == PrintStateBase::DONE) { - ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); - auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); - assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); - if (it->new_geometry()) - instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); - else { - // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. - m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); - m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation()); - } - } - } - - // stores the current volumes count - size_t volumes_count = m_volumes.volumes.size(); - - for (size_t istep = 0; istep < sla_steps.size(); ++istep) - if (!instances[istep].empty()) - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); } - // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed - for (GLVolume* volume : m_volumes.volumes) - if (volume->object_idx() < m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) - volume->set_sla_shift_z(shift_zs[volume->object_idx()]); + // stores the current volumes count + size_t volumes_count = m_volumes.volumes.size(); + + for (size_t istep = 0; istep < sla_steps.size(); ++istep) + if (!instances[istep].empty()) + m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); } - if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) + // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed + for (GLVolume* volume : m_volumes.volumes) + if (volume->object_idx() < m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable()) + volume->set_sla_shift_z(shift_zs[volume->object_idx()]); + } + + if (printer_technology == ptFFF && m_config->has("nozzle_diameter")) + { + // Should the wipe tower be visualized ? + unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); + + bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; + bool co = dynamic_cast(m_config->option("complete_objects"))->value; + + if ((extruders_count > 1) && wt && !co) { - // Should the wipe tower be visualized ? - unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); + // Height of a print (Show at least a slab) + double height = std::max(m_model->bounding_box().max(2), 10.0); - bool wt = dynamic_cast(m_config->option("wipe_tower"))->value; - bool co = dynamic_cast(m_config->option("complete_objects"))->value; + float x = dynamic_cast(m_config->option("wipe_tower_x"))->value; + float y = dynamic_cast(m_config->option("wipe_tower_y"))->value; + float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; + float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; - if ((extruders_count > 1) && wt && !co) - { - // Height of a print (Show at least a slab) - double height = std::max(m_model->bounding_box().max(2), 10.0); + const Print *print = m_process->fff_print(); + float depth = print->get_wipe_tower_depth(); - float x = dynamic_cast(m_config->option("wipe_tower_x"))->value; - float y = dynamic_cast(m_config->option("wipe_tower_y"))->value; - float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; - float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; + // Calculate wipe tower brim spacing. + const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + double layer_height = print_config.opt_float("layer_height"); + double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); + float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); - const Print *print = m_process->fff_print(); - float depth = print->get_wipe_tower_depth(); - - // Calculate wipe tower brim spacing. - const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; - double layer_height = print_config.opt_float("layer_height"); - double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height); - float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); - - if (!print->is_step_done(psWipeTower)) - depth = (900.f/w) * (float)(extruders_count - 1); - int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), - brim_spacing * 4.5f); - if (volume_idx_wipe_tower_old != -1) - map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; - } + if (!print->is_step_done(psWipeTower)) + depth = (900.f/w) * (float)(extruders_count - 1); + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + brim_spacing * 4.5f); + if (volume_idx_wipe_tower_old != -1) + map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; } + } - update_volumes_colors_by_extruder(); - // Update selection indices based on the old/new GLVolumeCollection. - if (m_selection.get_mode() == Selection::Instance) - m_selection.instances_changed(instance_ids_selected); - else - m_selection.volumes_changed(map_glvolume_old_to_new); - } + update_volumes_colors_by_extruder(); + // Update selection indices based on the old/new GLVolumeCollection. + if (m_selection.get_mode() == Selection::Instance) + m_selection.instances_changed(instance_ids_selected); + else + m_selection.volumes_changed(map_glvolume_old_to_new); m_gizmos.update_data(); m_gizmos.refresh_on_off_state(); @@ -2139,9 +2128,6 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); } - // restore to default value - m_regenerate_volumes = true; - m_camera.set_scene_box(scene_bounding_box()); if (m_selection.is_empty()) @@ -2913,7 +2899,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } - m_regenerate_volumes = false; m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); wxGetApp().obj_manipul()->set_dirty(); m_dirty = true; @@ -2973,7 +2958,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } else if ((m_mouse.drag.move_volume_idx != -1) && m_mouse.dragging) { - m_regenerate_volumes = false; do_move(L("Move Object")); wxGetApp().obj_manipul()->set_dirty(); // Let the plater know that the dragging finished, so a delayed refresh diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 2ce35660aa..941eeaaf60 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -463,7 +463,6 @@ private: bool m_moving_enabled; bool m_dynamic_background_enabled; bool m_multisample_allowed; - bool m_regenerate_volumes; bool m_moving; bool m_tab_down; ECursorType m_cursor_type; @@ -652,7 +651,6 @@ public: Linef3 mouse_ray(const Point& mouse_pos); void set_mouse_as_dragging() { m_mouse.dragging = true; } - void disable_regenerate_volumes() { m_regenerate_volumes = false; } void refresh_camera_scene_box() { m_camera.set_scene_box(scene_bounding_box()); } bool is_mouse_dragging() const { return m_mouse.dragging; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index d96b806e5b..d5a3a93c45 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -623,7 +623,6 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) { case Move: { - m_parent.disable_regenerate_volumes(); m_parent.do_move(L("Gizmo-Move")); break; } From 77401ed79d230c3d9d58e01276eb32513a954192 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 25 Jul 2019 16:23:32 +0200 Subject: [PATCH 417/627] Upgrade for ModeButtons --- src/slic3r/GUI/Plater.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 2 +- src/slic3r/GUI/wxExtensions.cpp | 18 +++++++----------- src/slic3r/GUI/wxExtensions.hpp | 2 +- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 067d13b8ab..1e9260d070 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -718,7 +718,7 @@ Sidebar::Sidebar(Plater *parent) p->scrolled->SetSizer(scrolled_sizer); // Sizer with buttons for mode changing - p->mode_sizer = new ModeSizer(p->scrolled, 2 * wxGetApp().em_unit()); + p->mode_sizer = new ModeSizer(p->scrolled); // The preset chooser p->sizer_presets = new wxFlexGridSizer(10, 1, 1, 2); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index cb8bb0b6d3..3d934770ff 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -198,7 +198,7 @@ void Tab::create_preset_tab() // There is used just additional sizer for m_mode_sizer with right alignment auto mode_sizer = new wxBoxSizer(wxVERTICAL); mode_sizer->Add(m_mode_sizer, 1, wxALIGN_RIGHT); - m_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 5); + m_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 10); //Horizontal sizer to hold the tree and the selected page. m_hsizer = new wxBoxSizer(wxHORIZONTAL); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 3fc404864a..e5e429a02d 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -2808,11 +2808,13 @@ ModeButton::ModeButton( wxWindow * parent, const wxString& mode /* = wxEmptyString*/, const wxSize& size /* = wxDefaultSize*/, const wxPoint& pos /* = wxDefaultPosition*/) : - ScalableButton(parent, id, icon_name, mode, size, pos) + ScalableButton(parent, id, icon_name, mode, size, pos, wxBU_EXACTFIT) { m_tt_focused = wxString::Format(_(L("Switch to the %s mode")), mode); m_tt_selected = wxString::Format(_(L("Current mode is %s")), mode); + SetBitmapMargins(3, 0); + //button events Bind(wxEVT_BUTTON, &ModeButton::OnButton, this); Bind(wxEVT_ENTER_WINDOW, &ModeButton::OnEnterBtn, this); @@ -2841,6 +2843,7 @@ void ModeButton::focus_button(const bool focus) Slic3r::GUI::wxGetApp().normal_font(); SetFont(new_font); + SetForegroundColour(wxSystemSettings::GetColour(focus ? wxSYS_COLOUR_BTNTEXT : wxSYS_COLOUR_BTNSHADOW)); Refresh(); Update(); @@ -2851,7 +2854,7 @@ void ModeButton::focus_button(const bool focus) // ModeSizer // ---------------------------------------------------------------------------- -ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 10*/) : +ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 0*/) : wxFlexGridSizer(3, 0, hgap) { SetFlexibleDirection(wxHORIZONTAL); @@ -2869,15 +2872,8 @@ ModeSizer::ModeSizer(wxWindow *parent, int hgap/* = 10*/) : m_mode_btns.reserve(3); for (const auto& button : buttons) { -#ifdef __WXOSX__ - wxSize sz = parent->GetTextExtent(button.first); - // set default width for ModeButtons to correct rendering on OnFocus under OSX - sz.x += 2 * em_unit(parent); - m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first, sz)); -#else - m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first));; -#endif // __WXOSX__ - + m_mode_btns.push_back(new ModeButton(parent, wxID_ANY, button.second, button.first)); + m_mode_btns.back()->Bind(wxEVT_BUTTON, std::bind(modebtnfn, std::placeholders::_1, int(m_mode_btns.size() - 1))); Add(m_mode_btns.back()); } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index a704f79d7a..d7d5fcac21 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -946,7 +946,7 @@ private: class ModeSizer : public wxFlexGridSizer { public: - ModeSizer( wxWindow *parent, int hgap = 10); + ModeSizer( wxWindow *parent, int hgap = 0); ~ModeSizer() {} void SetMode(const /*ConfigOptionMode*/int mode); From 86287a28ec806740a96675d1923ff328654991a4 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 25 Jul 2019 17:08:31 +0200 Subject: [PATCH 418/627] Fixed the Perl bindings, made Print::m_placeholder_parser protected, removed the non-const accessor method to m_placeholder_parser. --- src/libslic3r/Print.cpp | 14 ++++++++------ src/libslic3r/PrintBase.hpp | 5 +---- src/libslic3r/SLAPrint.cpp | 11 +++++------ 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 6126af162a..c423afeb94 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -502,7 +502,10 @@ void Print::config_diffs( const ConfigOption *opt_old = m_config.option(opt_key); assert(opt_old != nullptr); const ConfigOption *opt_new = new_full_config.option(opt_key); - assert(opt_new != nullptr); + // assert(opt_new != nullptr); + if (opt_new == nullptr) + //FIXME This may happen when executing some test cases. + continue; const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { // An extruder retract override is available at some of the filament presets. @@ -575,12 +578,11 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // which should be stopped if print_diff is not empty. if (! full_config_diff.empty() || ! placeholder_parser_overrides.empty()) { update_apply_status(this->invalidate_step(psGCodeExport)); - PlaceholderParser &pp = this->placeholder_parser(); - pp.apply_config(std::move(placeholder_parser_overrides)); + m_placeholder_parser.apply_config(std::move(placeholder_parser_overrides)); // Set the profile aliases for the PrintBase::output_filename() - pp.set("print_preset", new_full_config.option("print_settings_id")->clone()); - pp.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); - pp.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone()); + m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); + m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. m_config.apply_only(new_full_config, print_diff, true); m_config.apply(filament_overrides); diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index b0258fc647..aebc879044 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -327,8 +327,6 @@ protected: friend class PrintObjectBase; friend class BackgroundSlicingProcess; - PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } - tbb::mutex& state_mutex() const { return m_state_mutex; } std::function cancel_callback() { return m_cancel_callback; } void call_cancel_callback() { m_cancel_callback(); } @@ -344,6 +342,7 @@ protected: Model m_model; DynamicPrintConfig m_full_print_config; + PlaceholderParser m_placeholder_parser; private: tbb::atomic m_cancel_status; @@ -357,8 +356,6 @@ private: // The mutex will be used to guard the worker thread against entering a stage // while the data influencing the stage is modified. mutable tbb::mutex m_state_mutex; - - PlaceholderParser m_placeholder_parser; }; template diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 7060536450..2acf688a7a 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -161,7 +161,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con t_config_option_keys printer_diff = m_printer_config.diff(config); t_config_option_keys material_diff = m_material_config.diff(config); t_config_option_keys object_diff = m_default_object_config.diff(config); - t_config_option_keys placeholder_parser_diff = this->placeholder_parser().config_diff(config); + t_config_option_keys placeholder_parser_diff = m_placeholder_parser.config_diff(config); // Do not use the ApplyStatus as we will use the max function when updating apply_status. unsigned int apply_status = APPLY_STATUS_UNCHANGED; @@ -186,12 +186,11 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con // only to generate the output file name. if (! placeholder_parser_diff.empty()) { // update_apply_status(this->invalidate_step(slapsRasterize)); - PlaceholderParser &pp = this->placeholder_parser(); - pp.apply_config(config); + m_placeholder_parser.apply_config(config); // Set the profile aliases for the PrintBase::output_filename() - pp.set("print_preset", config.option("sla_print_settings_id")->clone()); - pp.set("material_preset", config.option("sla_material_settings_id")->clone()); - pp.set("printer_preset", config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("print_preset", config.option("sla_print_settings_id")->clone()); + m_placeholder_parser.set("material_preset", config.option("sla_material_settings_id")->clone()); + m_placeholder_parser.set("printer_preset", config.option("printer_settings_id")->clone()); } // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. From 17edc289e92015ff39495b6f5c11159ac4de97e1 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 25 Jul 2019 17:18:51 +0200 Subject: [PATCH 419/627] Fixed missing return value. --- src/libslic3r/Config.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index f1fab91fd3..9112261a88 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -434,7 +434,7 @@ public: static ConfigOptionType static_type() { return coFloats; } ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionFloatsTempl(*this); } - bool operator==(const ConfigOptionFloatsTempl &rhs) const { vectors_equal(this->values, rhs.values); } + bool operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); } bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) throw std::runtime_error("ConfigOptionFloatsTempl: Comparing incompatible types"); From 99bd054e990feb1b2a01e1641935fcad87117911 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 25 Jul 2019 17:24:00 +0200 Subject: [PATCH 420/627] Fixed updating of an option list for additional settings --- src/slic3r/GUI/GUI_ObjectList.cpp | 24 +++++++----------------- src/slic3r/GUI/GUI_ObjectList.hpp | 4 ++-- src/slic3r/GUI/GUI_ObjectSettings.cpp | 10 ++-------- src/slic3r/GUI/Plater.cpp | 3 ++- 4 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 8d5c3fda98..a247a92f88 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1566,9 +1566,9 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) #endif } -void ObjectList::update_opt_keys(t_config_option_keys& opt_keys) +void ObjectList::update_opt_keys(t_config_option_keys& opt_keys, const bool is_object) { - auto full_current_opts = get_options(false); + auto full_current_opts = get_options(!is_object); for (int i = opt_keys.size()-1; i >= 0; --i) if (find(full_current_opts.begin(), full_current_opts.end(), opt_keys[i]) == full_current_opts.end()) opt_keys.erase(opt_keys.begin() + i); @@ -2161,16 +2161,15 @@ void ObjectList::part_selection_changed() panel.Thaw(); } -SettingsBundle ObjectList::get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_layers_range_settings) +SettingsBundle ObjectList::get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_object_settings) { auto opt_keys = config->keys(); if (opt_keys.empty()) return SettingsBundle(); - update_opt_keys(opt_keys); // update options list according to print technology + update_opt_keys(opt_keys, is_object_settings); // update options list according to print technology - if (opt_keys.size() == 1 && opt_keys[0] == "extruder" || - is_layers_range_settings && opt_keys.size() == 2) + if (opt_keys.empty()) return SettingsBundle(); const int extruders_cnt = wxGetApp().extruders_edited_cnt(); @@ -2201,24 +2200,15 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D if (!parent_item) return ret; - const bool is_layers_range_settings = m_objects_model->GetItemType(parent_item) == itLayer; - SettingsBundle cat_options = get_item_settings_bundle(config, is_layers_range_settings); + const bool is_object_settings = m_objects_model->GetItemType(parent_item) == itObject; + SettingsBundle cat_options = get_item_settings_bundle(config, is_object_settings); if (cat_options.empty()) return ret; std::vector categories; categories.reserve(cat_options.size()); for (auto& cat : cat_options) - { - if (cat.second.size() == 1 && - (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height")) - continue; - categories.push_back(cat.first); - } - - if (categories.empty()) - return ret; if (m_objects_model->GetItemType(parent_item) & itInstance) parent_item = m_objects_model->GetTopParent(parent_item); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 5c971c40f8..39558d1c54 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -238,7 +238,7 @@ public: wxMenu* create_settings_popupmenu(wxMenu *parent_menu); void create_freq_settings_popupmenu(wxMenu *parent_menu); - void update_opt_keys(t_config_option_keys& t_optopt_keys); + void update_opt_keys(t_config_option_keys& t_optopt_keys, const bool is_object); void load_subobject(ModelVolumeType type); void load_part(ModelObject* model_object, std::vector> &volumes_info, ModelVolumeType type); @@ -266,7 +266,7 @@ public: wxBoxSizer* get_sizer() {return m_sizer;} int get_selected_obj_idx() const; DynamicPrintConfig& get_item_config(const wxDataViewItem& item) const; - SettingsBundle get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_layers_range_settings); + SettingsBundle get_item_settings_bundle(const DynamicPrintConfig* config, const bool is_object_settings); void changed_object(const int obj_idx = -1) const; void part_selection_changed(); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 8728156ac1..78c64e03e6 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -77,8 +77,8 @@ bool ObjectSettings::update_settings_list() if (!item || !objects_model->IsSettingsItem(item) || !config || objects_ctrl->multiple_selection()) return false; - const bool is_layers_range_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itLayer; - SettingsBundle cat_options = objects_ctrl->get_item_settings_bundle(config, is_layers_range_settings); + const bool is_object_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itObject; + SettingsBundle cat_options = objects_ctrl->get_item_settings_bundle(config, is_object_settings); if (!cat_options.empty()) { @@ -107,10 +107,6 @@ bool ObjectSettings::update_settings_list() for (auto& cat : cat_options) { - if (cat.second.size() == 1 && - (cat.second[0] == "extruder" || is_layers_range_settings && cat.second[0] == "layer_height")) - continue; - categories.push_back(cat.first); auto optgroup = std::make_shared(m_og->ctrl_parent(), _(cat.first), config, false, extra_column); @@ -131,8 +127,6 @@ bool ObjectSettings::update_settings_list() const bool is_extruders_cat = cat.first == "Extruders"; for (auto& opt : cat.second) { - if (opt == "extruder" || is_layers_range_settings && opt == "layer_height") - continue; Option option = optgroup->get_option(opt); option.opt.width = 12; if (is_extruders_cat) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1e9260d070..eb807aa57e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3799,6 +3799,7 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y")); model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); } + const int layer_range_idx = it_snapshot->snapshot_data.layer_range_idx; // Flags made of Snapshot::Flags enum values. unsigned int new_flags = it_snapshot->snapshot_data.flags; UndoRedo::SnapshotData top_snapshot_data; @@ -3858,7 +3859,7 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator new_selected_layerroot_on_sidebar ? ObjectList::SELECTION_MODE::smLayerRoot : ObjectList::SELECTION_MODE::smUndef); if (new_selected_settings_on_sidebar || new_selected_layer_on_sidebar) - this->sidebar->obj_list()->set_selected_layers_range_idx(it_snapshot->snapshot_data.layer_range_idx); + this->sidebar->obj_list()->set_selected_layers_range_idx(layer_range_idx); this->update_after_undo_redo(temp_snapshot_was_taken); // Enable layer editing after the Undo / Redo jump. From 77857f7292d9abf648010045efcd6e27fb09ea34 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 26 Jul 2019 09:45:22 +0200 Subject: [PATCH 421/627] Refactoring into rendering pipeline --- src/slic3r/GUI/3DBed.cpp | 22 +++++++++------------- src/slic3r/GUI/3DBed.hpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 16 +--------------- src/slic3r/GUI/GLCanvas3D.hpp | 1 - 4 files changed, 11 insertions(+), 30 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index a402fa82d4..97e200b38e 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -266,6 +266,8 @@ void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor) const { m_scale_factor = scale_factor; + render_axes(); + switch (m_type) { case MK2: @@ -292,12 +294,6 @@ void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor) const } } -void Bed3D::render_axes() const -{ - if (!m_shape.empty()) - m_axes.render(); -} - void Bed3D::calc_bounding_boxes() const { m_bounding_box = BoundingBoxf3(); @@ -393,6 +389,12 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const return type; } +void Bed3D::render_axes() const +{ + if (!m_shape.empty()) + m_axes.render(); +} + void Bed3D::render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const { if (!bottom) @@ -598,9 +600,7 @@ void Bed3D::render_default(bool bottom) const { bool has_model = !m_model.get_filename().empty(); - glsafe(::glEnable(GL_LIGHTING)); - glsafe(::glDisable(GL_DEPTH_TEST)); - + glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); @@ -615,11 +615,7 @@ void Bed3D::render_default(bool bottom) const glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); } - glsafe(::glDisable(GL_LIGHTING)); - // draw grid - // we need depth test for grid, otherwise it would disappear when looking the object from below - glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glLineWidth(3.0f * m_scale_factor)); if (has_model && !bottom) glsafe(::glColor4f(0.75f, 0.75f, 0.75f, 1.0f)); diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index a3e4bcf801..c9a30e6ec2 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -111,13 +111,13 @@ public: Point point_projection(const Point& point) const; void render(GLCanvas3D& canvas, float theta, float scale_factor) const; - void render_axes() const; private: void calc_bounding_boxes() const; void calc_triangles(const ExPolygon& poly); void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); EType detect_type(const Pointfs& shape) const; + void render_axes() const; void render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const; void render_texture(const std::string& filename, bool bottom, GLCanvas3D& canvas) const; void render_model(const std::string& filename) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b072efd988..118c4802ae 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1561,19 +1561,10 @@ void GLCanvas3D::render() glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); _render_background(); - // textured bed needs to be rendered after objects if the texture is transparent - bool early_bed_render = m_bed.is_custom() || (theta <= 90.0f); - if (early_bed_render) - _render_bed(theta); - _render_objects(); _render_sla_slices(); _render_selection(); - - _render_axes(); - - if (!early_bed_render) - _render_bed(theta); + _render_bed(theta); #if ENABLE_RENDER_SELECTION_CENTER _render_selection_center(); @@ -3906,11 +3897,6 @@ void GLCanvas3D::_render_bed(float theta) const m_bed.render(const_cast(*this), theta, scale_factor); } -void GLCanvas3D::_render_axes() const -{ - m_bed.render_axes(); -} - void GLCanvas3D::_render_objects() const { if (m_volumes.empty()) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index fd60fcabc8..c9803f9db8 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -662,7 +662,6 @@ private: void _rectangular_selection_picking_pass() const; void _render_background() const; void _render_bed(float theta) const; - void _render_axes() const; void _render_objects() const; void _render_selection() const; #if ENABLE_RENDER_SELECTION_CENTER From 10b27968d184bda9bee73b12aed95bb148013965 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 26 Jul 2019 11:32:44 +0200 Subject: [PATCH 422/627] Code cleanup --- src/libslic3r/Technologies.hpp | 4 - src/slic3r/GUI/GLCanvas3D.cpp | 25 +---- src/slic3r/GUI/GUI_ObjectList.cpp | 74 +-------------- src/slic3r/GUI/Selection.cpp | 153 +++--------------------------- src/slic3r/GUI/Selection.hpp | 25 ----- 5 files changed, 16 insertions(+), 265 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 2d9078c6d3..ae43369d22 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -41,8 +41,4 @@ #define ENABLE_TEXTURES_FROM_SVG (1 && ENABLE_1_42_0_ALPHA7) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#define ENABLE_SELECTION_UNDO_REDO 1 -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - #endif // _technologies_h_ diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index fd37e07c5f..6c9e79dbd9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1722,16 +1722,7 @@ void GLCanvas3D::select_all() void GLCanvas3D::deselect_all() { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO m_selection.remove_all(); -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - m_selection.clear(); - m_selection.set_mode(Selection::Instance); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxGetApp().obj_manipul()->set_dirty(); m_gizmos.reset_all_states(); m_gizmos.update_data(); @@ -5653,15 +5644,7 @@ void GLCanvas3D::_update_selection_from_hover() if (m_hover_volume_idxs.empty()) { if (!ctrl_pressed && (m_rectangle_selection.get_state() == GLSelectionRectangle::Select)) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO m_selection.remove_all(); -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - m_selection.clear(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ return; } @@ -5678,8 +5661,6 @@ void GLCanvas3D::_update_selection_from_hover() } } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO bool selection_changed = false; if (state == GLSelectionRectangle::Select) { @@ -5696,7 +5677,7 @@ void GLCanvas3D::_update_selection_from_hover() // the selection is going to be modified (Add) if (!contains_all) { - wxGetApp().plater()->take_snapshot(_(L("Selection - Add - _update_selection_from_hover()"))); + wxGetApp().plater()->take_snapshot(_(L("Selection-Add from rectangle"))); selection_changed = true; } } @@ -5715,7 +5696,7 @@ void GLCanvas3D::_update_selection_from_hover() // the selection is going to be modified (Remove) if (contains_any) { - wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - _update_selection_from_hover()"))); + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove from rectangle"))); selection_changed = true; } } @@ -5724,8 +5705,6 @@ void GLCanvas3D::_update_selection_from_hover() return; Plater::SuppressSnapshots suppress(wxGetApp().plater()); -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if ((state == GLSelectionRectangle::Select) && !ctrl_pressed) m_selection.clear(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 5bd2f2654b..f9958af663 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2803,75 +2803,35 @@ void ObjectList::update_selections_on_canvas() const int sel_cnt = GetSelectedItemsCount(); if (sel_cnt == 0) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO selection.remove_all(); -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - selection.clear(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); return; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO std::vector volume_idxs; Selection::EMode mode = Selection::Volume; auto add_to_selection = [this, &volume_idxs](const wxDataViewItem& item, const Selection& selection, int instance_idx, Selection::EMode& mode) -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - auto add_to_selection = [this](const wxDataViewItem& item, Selection& selection, int instance_idx, bool as_single_selection) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { const ItemType& type = m_objects_model->GetItemType(item); const int obj_idx = m_objects_model->GetObjectIdByItem(item); if (type == itVolume) { const int vol_idx = m_objects_model->GetVolumeIdByItem(item); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO std::vector idxs = selection.get_volume_idxs_from_volume(obj_idx, std::max(instance_idx, 0), vol_idx); volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - selection.add_volume(obj_idx, vol_idx, std::max(instance_idx, 0), as_single_selection); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } else if (type == itInstance) { const int inst_idx = m_objects_model->GetInstanceIdByItem(item); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO mode = Selection::Instance; std::vector idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx); volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - selection.add_instance(obj_idx, inst_idx, as_single_selection); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO { mode = Selection::Instance; std::vector idxs = selection.get_volume_idxs_from_object(obj_idx); volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); } -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - selection.add_object(obj_idx, as_single_selection); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ }; // stores current instance idx before to clear the selection @@ -2879,28 +2839,11 @@ void ObjectList::update_selections_on_canvas() if (sel_cnt == 1) { wxDataViewItem item = GetSelection(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode); else add_to_selection(item, selection, instance_idx, mode); -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer)) - add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, true); - else - add_to_selection(item, selection, instance_idx, true); - - wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); - wxGetApp().plater()->canvas3D()->render(); - return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO else { wxDataViewItemArray sels; @@ -2918,8 +2861,7 @@ void ObjectList::update_selections_on_canvas() volume_idxs = selection.get_missing_volume_idxs_from(volume_idxs); if (volume_idxs.size() > 0) { -// std::cout << "ObjectList- > Selection-Remove " << volume_idxs.size() << " volumes" << std::endl; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Selection - Remove - update_selections_on_canvas()"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Selection-Remove from list"))); selection.remove_volumes(mode, volume_idxs); } } @@ -2927,21 +2869,9 @@ void ObjectList::update_selections_on_canvas() { // add volume_idxs = selection.get_unselected_volume_idxs_from(volume_idxs); -// std::cout << "ObjectList- > Selection-Add " << volume_idxs.size() << " volumes - mode: " << ((mode == Selection::Volume) ? "Volume" : "Instance") << std::endl; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Selection - Add - update_selections_on_canvas()"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Selection-Add from list"))); selection.add_volumes(mode, volume_idxs, sel_cnt == 1); } -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - wxDataViewItemArray sels; - GetSelections(sels); - - selection.clear(); - for (auto item: sels) - add_to_selection(item, selection, instance_idx, false); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); wxGetApp().plater()->canvas3D()->render(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 33099f9da8..211863627f 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -140,25 +140,12 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier; needs_reset |= is_any_modifier() && !volume->is_modifier; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if !ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - if (needs_reset) - clear(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // !ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - if (!already_contained || needs_reset) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO - wxGetApp().plater()->take_snapshot(_(L("Selection - Add - add()"))); + wxGetApp().plater()->take_snapshot(_(L("Selection-Add"))); if (needs_reset) clear(); -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!keep_instance_mode) m_mode = volume->is_modifier ? Volume : Instance; @@ -178,16 +165,8 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec } case Instance: { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO Plater::SuppressSnapshots suppress(wxGetApp().plater()); add_instance(volume->object_idx(), volume->instance_idx(), as_single_selection); -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - do_add_instance(volume->object_idx(), volume->instance_idx()); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ break; } } @@ -201,14 +180,10 @@ void Selection::remove(unsigned int volume_idx) if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO if (!contains_volume(volume_idx)) return; - wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove()"))); -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove"))); GLVolume* volume = (*m_volumes)[volume_idx]; @@ -235,16 +210,12 @@ void Selection::add_object(unsigned int object_idx, bool as_single_selection) if (!m_valid) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO std::vector volume_idxs = get_volume_idxs_from_object(object_idx); if ((!as_single_selection && contains_all_volumes(volume_idxs)) || (as_single_selection && matches(volume_idxs))) return; - wxGetApp().plater()->take_snapshot(_(L("Selection - Add - add_object()"))); -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxGetApp().plater()->take_snapshot(_(L("Selection-Add Object"))); // resets the current list if needed if (as_single_selection) @@ -252,15 +223,7 @@ void Selection::add_object(unsigned int object_idx, bool as_single_selection) m_mode = Instance; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO do_add_volumes(volume_idxs); -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - do_add_object(object_idx); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ update_type(); this->set_bounding_boxes_dirty(); @@ -271,11 +234,7 @@ void Selection::remove_object(unsigned int object_idx) if (!m_valid) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO - wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_object()"))); -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Object"))); do_remove_object(object_idx); @@ -288,16 +247,12 @@ void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, if (!m_valid) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO std::vector volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx); if ((!as_single_selection && contains_all_volumes(volume_idxs)) || (as_single_selection && matches(volume_idxs))) return; - wxGetApp().plater()->take_snapshot(_(L("Selection - Add - add_instance()"))); -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxGetApp().plater()->take_snapshot(_(L("Selection-Add Instance"))); // resets the current list if needed if (as_single_selection) @@ -305,15 +260,7 @@ void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, m_mode = Instance; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO do_add_volumes(volume_idxs); -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - do_add_instance(object_idx, instance_idx); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ update_type(); this->set_bounding_boxes_dirty(); @@ -324,11 +271,7 @@ void Selection::remove_instance(unsigned int object_idx, unsigned int instance_i if (!m_valid) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO - wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_instance()"))); -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Instance"))); do_remove_instance(object_idx, instance_idx); @@ -341,16 +284,12 @@ void Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int if (!m_valid) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO std::vector volume_idxs = get_volume_idxs_from_volume(object_idx, instance_idx, volume_idx); if ((!as_single_selection && contains_all_volumes(volume_idxs)) || (as_single_selection && matches(volume_idxs))) return; - wxGetApp().plater()->take_snapshot(_(L("Selection - Add - add_volume()"))); -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxGetApp().plater()->take_snapshot(_(L("Selection-Add Volume"))); // resets the current list if needed if (as_single_selection) @@ -358,23 +297,7 @@ void Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int m_mode = Volume; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO do_add_volumes(volume_idxs); -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == object_idx) && (v->volume_idx() == volume_idx)) - { - if ((instance_idx != -1) && (v->instance_idx() == instance_idx)) - do_add_volume(i); - } - } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ update_type(); this->set_bounding_boxes_dirty(); @@ -385,11 +308,7 @@ void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) if (!m_valid) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO - wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_volume()"))); -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Volume"))); for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { @@ -402,8 +321,6 @@ void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) this->set_bounding_boxes_dirty(); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO void Selection::add_volumes(EMode mode, const std::vector& volume_idxs, bool as_single_selection) { if (!m_valid) @@ -413,7 +330,7 @@ void Selection::add_volumes(EMode mode, const std::vector& volume_ (as_single_selection && matches(volume_idxs))) return; - wxGetApp().plater()->take_snapshot(_(L("Selection - Add - add_volumes()"))); + wxGetApp().plater()->take_snapshot(_(L("Selection-Add Volumes"))); // resets the current list if needed if (as_single_selection) @@ -435,7 +352,7 @@ void Selection::remove_volumes(EMode mode, const std::vector& volu if (!m_valid) return; - wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_volumes()"))); + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Volumes"))); m_mode = mode; for (unsigned int i : volume_idxs) @@ -447,16 +364,12 @@ void Selection::remove_volumes(EMode mode, const std::vector& volu update_type(); this->set_bounding_boxes_dirty(); } -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::add_all() { if (!m_valid) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO unsigned int count = 0; for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { @@ -467,9 +380,7 @@ void Selection::add_all() if ((unsigned int)m_list.size() == count) return; - wxGetApp().plater()->take_snapshot(_(L("Selection - Add - add_all()"))); -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxGetApp().plater()->take_snapshot(_(L("Selection-Add All"))); m_mode = Instance; clear(); @@ -484,8 +395,6 @@ void Selection::add_all() this->set_bounding_boxes_dirty(); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO void Selection::remove_all() { if (!m_valid) @@ -494,16 +403,12 @@ void Selection::remove_all() if (is_empty()) return; -//##################################################################################################################################################################################### if (!wxGetApp().plater()->can_redo()) -//##################################################################################################################################################################################### - wxGetApp().plater()->take_snapshot(_(L("Selection - Remove - remove_all()"))); + wxGetApp().plater()->take_snapshot(_(L("Selection-Remove All"))); m_mode = Instance; clear(); } -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::set_deserialized(EMode mode, const std::vector> &volumes_and_instances) { @@ -632,8 +537,6 @@ bool Selection::is_sla_compliant() const return true; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO bool Selection::contains_all_volumes(const std::vector& volume_idxs) const { for (unsigned int i : volume_idxs) @@ -670,8 +573,6 @@ bool Selection::matches(const std::vector& volume_idxs) const return count == (unsigned int)m_list.size(); } -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool Selection::requires_uniform_scale() const { @@ -1487,8 +1388,6 @@ void Selection::paste_from_clipboard() } } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO std::vector Selection::get_volume_idxs_from_object(unsigned int object_idx) const { std::vector idxs; @@ -1559,8 +1458,6 @@ std::vector Selection::get_unselected_volume_idxs_from(const std:: return idxs; } -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::update_valid() { @@ -1808,8 +1705,6 @@ void Selection::do_add_volume(unsigned int volume_idx) (*m_volumes)[volume_idx]->selected = true; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO void Selection::do_add_volumes(const std::vector& volume_idxs) { for (unsigned int i : volume_idxs) @@ -1818,30 +1713,6 @@ void Selection::do_add_volumes(const std::vector& volume_idxs) do_add_volume(i); } } -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -void Selection::do_add_instance(unsigned int object_idx, unsigned int instance_idx) -{ - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx)) - do_add_volume(i); - } -} - -void Selection::do_add_object(unsigned int object_idx) -{ - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - GLVolume* v = (*m_volumes)[i]; - if (v->object_idx() == object_idx) - do_add_volume(i); - } -} -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::do_remove_volume(unsigned int volume_idx) { diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 2a96985c35..0f71cefc44 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -235,19 +235,11 @@ public: void add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection = true); void remove_volume(unsigned int object_idx, unsigned int volume_idx); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO void add_volumes(EMode mode, const std::vector& volume_idxs, bool as_single_selection = true); void remove_volumes(EMode mode, const std::vector& volume_idxs); -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void add_all(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO void remove_all(); -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // To be called after Undo or Redo once the volumes are updated. void set_deserialized(EMode mode, const std::vector> &volumes_and_instances); @@ -277,16 +269,12 @@ public: bool is_sla_compliant() const; bool contains_volume(unsigned int volume_idx) const { return m_list.find(volume_idx) != m_list.end(); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO // returns true if the selection contains all the given indices bool contains_all_volumes(const std::vector& volume_idxs) const; // returns true if the selection contains at least one of the given indices bool contains_any_volume(const std::vector& volume_idxs) const; // returns true if the selection contains all and only the given indices bool matches(const std::vector& volume_idxs) const; -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool requires_uniform_scale() const; @@ -337,8 +325,6 @@ public: const Clipboard& get_clipboard() const { return m_clipboard; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO // returns the list of idxs of the volumes contained into the object with the given idx std::vector get_volume_idxs_from_object(unsigned int object_idx) const; // returns the list of idxs of the volumes contained into the instance with the given idxs @@ -349,24 +335,13 @@ public: std::vector get_missing_volume_idxs_from(const std::vector& volume_idxs) const; // returns the list of idxs of the volumes contained in the given list but not in the selection std::vector get_unselected_volume_idxs_from(const std::vector& volume_idxs) const; -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ private: void update_valid(); void update_type(); void set_caches(); void do_add_volume(unsigned int volume_idx); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#if ENABLE_SELECTION_UNDO_REDO void do_add_volumes(const std::vector& volume_idxs); -#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - void do_add_instance(unsigned int object_idx, unsigned int instance_idx); - void do_add_object(unsigned int object_idx); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -#endif // ENABLE_SELECTION_UNDO_REDO -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void do_remove_volume(unsigned int volume_idx); void do_remove_instance(unsigned int object_idx, unsigned int instance_idx); void do_remove_object(unsigned int object_idx); From 9593da261326f06302e7a3e4fba020be1a291959 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 26 Jul 2019 13:06:49 +0200 Subject: [PATCH 423/627] Take a single undo/redo snapshot when pasting from clipboard --- src/slic3r/GUI/Plater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index eb807aa57e..02d0e3bf93 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4719,7 +4719,7 @@ void Plater::paste_from_clipboard() if (!can_paste_from_clipboard()) return; - this->take_snapshot(_(L("Paste From Clipboard"))); + Plater::TakeSnapshot snapshot(this, _(L("Paste From Clipboard"))); p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); } From 30d4bfd41026ea8cc8be181bf2af3210ed76a763 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 26 Jul 2019 13:44:33 +0200 Subject: [PATCH 424/627] New Undo / Redo stack for the gizmos. The Gizmo specific stack is entered with Plater::enter_gizmos_stack(), and left with Plater::enter_gizmos_stack(). Other than that, the 2nd Undo / Redo stack is transparent to the user of the Plater. WIP: Currently the Gizmo stack takes a snapshot of the whole scene on Plater::enter_gizmos_stack(). While it should work, it may be cheaper to modify the Undo/Redo stack to only take a snapshot of the gizmos in the Gizmo mode. --- src/slic3r/GUI/Plater.cpp | 96 +++++++++++++++++++++----------- src/slic3r/GUI/Plater.hpp | 6 +- src/slic3r/GUI/SysInfoDialog.cpp | 2 +- src/slic3r/Utils/UndoRedo.cpp | 18 ++++++ src/slic3r/Utils/UndoRedo.hpp | 6 +- 5 files changed, 94 insertions(+), 34 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index eb807aa57e..e6064e6398 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1335,12 +1335,7 @@ struct Plater::priv Slic3r::Model model; PrinterTechnology printer_technology = ptFFF; Slic3r::GCodePreviewData gcode_preview_data; - Slic3r::UndoRedo::Stack undo_redo_stack; - int m_prevent_snapshots = 0; /* Used for avoid of excess "snapshoting". - * Like for "delete selected" or "set numbers of copies" - * we should call tack_snapshot just ones - * instead of calls for each action separately - * */ + // GUI elements wxSizer* panel_sizer{ nullptr }; wxPanel* current_panel{ nullptr }; @@ -1785,9 +1780,16 @@ struct Plater::priv void split_volume(); void scale_selection_to_fit_print_volume(); + // Return the active Undo/Redo stack. It may be either the main stack or the Gimzo stack. + Slic3r::UndoRedo::Stack& undo_redo_stack() { assert(m_undo_redo_stack_active != nullptr); return *m_undo_redo_stack_active; } + Slic3r::UndoRedo::Stack& undo_redo_stack_main() { return m_undo_redo_stack_main; } + void enter_gizmos_stack(); + void leave_gizmos_stack(); + void take_snapshot(const std::string& snapshot_name); void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } int get_active_snapshot_index(); + void undo(); void redo(); void undo_redo_to(size_t time_to_load); @@ -1889,7 +1891,15 @@ private: void update_after_undo_redo(bool temp_snapshot_was_taken = false); // path to project file stored with no extension - wxString m_project_filename; + wxString m_project_filename; + Slic3r::UndoRedo::Stack m_undo_redo_stack_main; + Slic3r::UndoRedo::Stack m_undo_redo_stack_gizmos; + Slic3r::UndoRedo::Stack *m_undo_redo_stack_active = &m_undo_redo_stack_main; + int m_prevent_snapshots = 0; /* Used for avoid of excess "snapshoting". + * Like for "delete selected" or "set numbers of copies" + * we should call tack_snapshot just ones + * instead of calls for each action separately + * */ std::string m_last_fff_printer_profile_name; std::string m_last_sla_printer_profile_name; }; @@ -3712,10 +3722,32 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const } } +void Plater::priv::enter_gizmos_stack() +{ + assert(m_undo_redo_stack_active == &m_undo_redo_stack_main); + if (m_undo_redo_stack_active == &m_undo_redo_stack_main) { + m_undo_redo_stack_active = &m_undo_redo_stack_gizmos; + assert(m_undo_redo_stack_active->empty()); + // Take the initial snapshot of the gizmos. + // Not localized on purpose, the text will never be shown to the user. + this->take_snapshot(std::string("Gizmos-Initial")); + } +} + +void Plater::priv::leave_gizmos_stack() +{ + assert(m_undo_redo_stack_active == &m_undo_redo_stack_gizmos); + if (m_undo_redo_stack_active == &m_undo_redo_stack_gizmos) { + assert(! m_undo_redo_stack_active->empty()); + m_undo_redo_stack_active->clear(); + m_undo_redo_stack_active = &m_undo_redo_stack_main; + } +} + int Plater::priv::get_active_snapshot_index() { - const size_t active_snapshot_time = this->undo_redo_stack.active_snapshot_time(); - const std::vector& ss_stack = this->undo_redo_stack.snapshots(); + const size_t active_snapshot_time = this->undo_redo_stack().active_snapshot_time(); + const std::vector& ss_stack = this->undo_redo_stack().snapshots(); const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time)); return it - ss_stack.begin(); } @@ -3746,32 +3778,32 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y")); model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle"); } - this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data); - this->undo_redo_stack.release_least_recently_used(); + this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data); + this->undo_redo_stack().release_least_recently_used(); // Save the last active preset name of a particular printer technology. ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); - BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); } void Plater::priv::undo() { - const std::vector &snapshots = this->undo_redo_stack.snapshots(); - auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack.active_snapshot_time())); + const std::vector &snapshots = this->undo_redo_stack().snapshots(); + auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); if (-- it_current != snapshots.begin()) this->undo_redo_to(it_current); } void Plater::priv::redo() { - const std::vector &snapshots = this->undo_redo_stack.snapshots(); - auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack.active_snapshot_time())); + const std::vector &snapshots = this->undo_redo_stack().snapshots(); + auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); if (++ it_current != snapshots.end()) this->undo_redo_to(it_current); } void Plater::priv::undo_redo_to(size_t time_to_load) { - const std::vector &snapshots = this->undo_redo_stack.snapshots(); + const std::vector &snapshots = this->undo_redo_stack().snapshots(); auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(time_to_load)); assert(it_current != snapshots.end()); this->undo_redo_to(it_current); @@ -3779,7 +3811,7 @@ void Plater::priv::undo_redo_to(size_t time_to_load) void Plater::priv::undo_redo_to(std::vector::const_iterator it_snapshot) { - bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active(); + bool temp_snapshot_was_taken = this->undo_redo_stack().temp_snapshot_active(); PrinterTechnology new_printer_technology = it_snapshot->snapshot_data.printer_technology; bool printer_technology_changed = this->printer_technology != new_printer_technology; if (printer_technology_changed) { @@ -3825,9 +3857,9 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); // Do the jump in time. - if (it_snapshot->timestamp < this->undo_redo_stack.active_snapshot_time() ? - this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), top_snapshot_data, it_snapshot->timestamp) : - this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) { + if (it_snapshot->timestamp < this->undo_redo_stack().active_snapshot_time() ? + this->undo_redo_stack().undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), top_snapshot_data, it_snapshot->timestamp) : + this->undo_redo_stack().redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) { if (printer_technology_changed) { // Switch to the other printer technology. Switch to the last printer active for that particular technology. AppConfig *app_config = wxGetApp().app_config; @@ -3877,9 +3909,9 @@ void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */) //if (temp_snapshot_was_taken) // Release the old snapshots always, as it may have happened, that some of the triangle meshes got deserialized from the snapshot, while some // triangle meshes may have gotten released from the scene or the background processing, therefore now being calculated into the Undo / Redo stack size. - this->undo_redo_stack.release_least_recently_used(); + this->undo_redo_stack().release_least_recently_used(); //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time) - this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances); + this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack().selection_deserialized().mode), this->undo_redo_stack().selection_deserialized().volumes_and_instances); this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo(); wxGetApp().obj_list()->update_after_undo_redo(); @@ -3894,7 +3926,7 @@ void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */) //FIXME what about the state of the manipulators? //FIXME what about the focus? Cursor in the side panel? - BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); } void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const @@ -4453,7 +4485,7 @@ void Plater::undo_to(int selection) } const int idx = p->get_active_snapshot_index() - selection - 1; - p->undo_redo_to(p->undo_redo_stack.snapshots()[idx].timestamp); + p->undo_redo_to(p->undo_redo_stack().snapshots()[idx].timestamp); } void Plater::redo_to(int selection) { @@ -4463,11 +4495,11 @@ void Plater::redo_to(int selection) } const int idx = p->get_active_snapshot_index() + selection + 1; - p->undo_redo_to(p->undo_redo_stack.snapshots()[idx].timestamp); + p->undo_redo_to(p->undo_redo_stack().snapshots()[idx].timestamp); } bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text) { - const std::vector& ss_stack = p->undo_redo_stack.snapshots(); + const std::vector& ss_stack = p->undo_redo_stack().snapshots(); const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -(++idx) : idx); if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) { @@ -4480,7 +4512,7 @@ bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** o void Plater::undo_redo_topmost_string_getter(const bool is_undo, std::string& out_text) { - const std::vector& ss_stack = p->undo_redo_stack.snapshots(); + const std::vector& ss_stack = p->undo_redo_stack().snapshots(); const int idx_in_ss_stack = p->get_active_snapshot_index() + (is_undo ? -1 : 0); if (0 < idx_in_ss_stack && idx_in_ss_stack < ss_stack.size() - 1) { @@ -4785,9 +4817,11 @@ bool Plater::can_copy_to_clipboard() const return true; } -bool Plater::can_undo() const { return p->undo_redo_stack.has_undo_snapshot(); } -bool Plater::can_redo() const { return p->undo_redo_stack.has_redo_snapshot(); } -const UndoRedo::Stack& Plater::undo_redo_stack() const { return p->undo_redo_stack; } +bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot(); } +bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); } +const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } +void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } +void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); } SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() : m_was_running(wxGetApp().plater()->is_background_process_running()) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index e0737c6b9f..d7f7f37911 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -197,7 +197,11 @@ public: void redo_to(int selection); bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text); void undo_redo_topmost_string_getter(const bool is_undo, std::string& out_text); - const Slic3r::UndoRedo::Stack& undo_redo_stack() const; + // For the memory statistics. + const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; + // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo. + void enter_gizmos_stack(); + void leave_gizmos_stack(); void on_extruders_change(int extruders_count); void on_config_change(const DynamicPrintConfig &config); diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 96a3e9a819..e9487ee155 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -58,7 +58,7 @@ std::string get_mem_info(bool format_as_html) std::string b_end = format_as_html ? "" : ""; std::string line_end = format_as_html ? "
" : "\n"; - const Slic3r::UndoRedo::Stack &stack = wxGetApp().plater()->undo_redo_stack(); + const Slic3r::UndoRedo::Stack &stack = wxGetApp().plater()->undo_redo_stack_main(); out << b_start << "RAM size reserved for the Undo / Redo stack [MB]: " << b_end << Slic3r::format_memsize_MB(stack.get_memory_limit()) << line_end; out << b_start << "RAM size occupied by the Undo / Redo stack [MB]: " << b_end << Slic3r::format_memsize_MB(stack.memsize()) << line_end << line_end; diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index daf6752b71..1d74123dd4 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -490,6 +490,21 @@ public: // Initially enable Undo / Redo stack to occupy maximum 10% of the total system physical memory. StackImpl() : m_memory_limit(std::min(Slic3r::total_physical_memory() / 10, size_t(1 * 16384 * 65536 / UNDO_REDO_DEBUG_LOW_MEM_FACTOR))), m_active_snapshot_time(0), m_current_time(0) {} + void clear() { + m_objects.clear(); + m_shared_ptr_to_object_id.clear(); + m_snapshots.clear(); + m_active_snapshot_time = 0; + m_current_time = 0; + m_selection.clear(); + } + + bool empty() const { + assert(m_objects.empty() == m_snapshots.empty()); + assert(! m_objects.empty() || (m_current_time == 0 && m_active_snapshot_time == 0)); + return m_snapshots.empty(); + } + void set_memory_limit(size_t memsize) { m_memory_limit = memsize; } size_t get_memory_limit() const { return m_memory_limit; } @@ -1020,6 +1035,9 @@ void StackImpl::release_least_recently_used() // Wrappers of the private implementation. Stack::Stack() : pimpl(new StackImpl()) {} Stack::~Stack() {} +void Stack::clear() { pimpl->clear(); } +bool Stack::empty() const { return pimpl->empty(); } + void Stack::set_memory_limit(size_t memsize) { pimpl->set_memory_limit(memsize); } size_t Stack::get_memory_limit() const { return pimpl->get_memory_limit(); } size_t Stack::memsize() const { return pimpl->memsize(); } diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 37cc5a049f..558449003e 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -71,7 +71,8 @@ struct Snapshot // Excerpt of Slic3r::GUI::Selection for serialization onto the Undo / Redo stack. struct Selection : public Slic3r::ObjectBase { - unsigned char mode; + void clear() { mode = 0; volumes_and_instances.clear(); } + unsigned char mode = 0; std::vector> volumes_and_instances; template void serialize(Archive &ar) { ar(mode, volumes_and_instances); } }; @@ -86,6 +87,9 @@ public: Stack(); ~Stack(); + void clear(); + bool empty() const; + // Set maximum memory threshold. If the threshold is exceeded, least recently used snapshots are released. void set_memory_limit(size_t memsize); size_t get_memory_limit() const; From 26c2b16f6169887e277834b11fc5c6f8f5c8845a Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 28 Jun 2019 15:24:28 +0200 Subject: [PATCH 425/627] Build: Remove SLIC3R_SYNTAXONLY --- CMakeLists.txt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba291e4509..cbb0e2ec49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,6 @@ option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1) option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1) option(SLIC3R_PERL_XS "Compile XS Perl module and enable Perl unit and integration tests" 0) option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0) -option(SLIC3R_SYNTAXONLY "Only perform source code correctness checking, no binary output (UNIX only)" 0) set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux") @@ -147,19 +146,6 @@ endif() if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUXX) # Adding -fext-numeric-literals to enable GCC extensions on definitions of quad float literals, which are required by Boost. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals" ) - - if (SLIC3R_SYNTAXONLY) - set(CMAKE_CXX_ARCHIVE_CREATE "true") - set(CMAKE_C_ARCHIVE_CREATE "true") - set(CMAKE_CXX_ARCHIVE_APPEND "true") - set(CMAKE_C_ARCHIVE_APPEND "true") - set(CMAKE_RANLIB "true") - set(CMAKE_C_LINK_EXECUTABLE "true") - set(CMAKE_CXX_LINK_EXECUTABLE "true") - - set(CMAKE_C_COMPILE_OBJECT " -fsyntax-only -c && touch ") - set(CMAKE_CXX_COMPILE_OBJECT " -fsyntax-only -c && touch ") - endif () endif() if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") From ba21d606af88b470de11652cdba5e29059f48e66 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 26 Jul 2019 13:52:51 +0200 Subject: [PATCH 426/627] Fix a ternary op type error in Tab Fix #2668 Fix #2676 --- src/slic3r/GUI/Tab.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 3d934770ff..31a2ce614c 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -914,14 +914,20 @@ void Tab::update_preset_description_line() { const Preset* parent = m_presets->get_selected_preset_parent(); const Preset& preset = m_presets->get_edited_preset(); - - wxString description_line = preset.is_default ? - _(L("It's a default preset.")) : preset.is_system ? - _(L("It's a system preset.")) : - wxString::Format(_(L("Current preset is inherited from %s")), (parent == nullptr ? - _(L("default preset"))+"." : - ":\n\t" + parent->name)); - + + wxString description_line; + + if (preset.is_default) { + description_line = _(L("This is a default preset.")); + } else if (preset.is_system) { + description_line = _(L("This is a system preset.")); + } else if (parent == nullptr) { + description_line = _(L("Current preset is inherited from the default preset.")); + } else { + description_line = wxString::Format( + _(L("Current preset is inherited from:\n\t%s")), GUI::from_u8(parent->name)); + } + if (preset.is_default || preset.is_system) description_line += "\n\t" + _(L("It can't be deleted or modified.")) + "\n\t" + _(L("Any modifications should be saved as a new preset inherited from this one.")) + From fe6236296095699df6f9063a0bdc5f7198ba9965 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 25 Jul 2019 11:23:23 +0200 Subject: [PATCH 427/627] Hiding the sla support structures after undo/redo --- src/libslic3r/Model.cpp | 4 ++-- src/libslic3r/Model.hpp | 2 +- src/libslic3r/SLA/SLACommon.hpp | 2 +- src/libslic3r/SLAPrint.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 15 ++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 6 +++--- 6 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index ecfb6697a5..858ae52b29 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1134,7 +1134,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b if (keep_upper) { upper->set_model(nullptr); upper->sla_support_points.clear(); - upper->sla_points_status = sla::PointsStatus::None; + upper->sla_points_status = sla::PointsStatus::NoPoints; upper->clear_volumes(); upper->input_file = ""; } @@ -1142,7 +1142,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b if (keep_lower) { lower->set_model(nullptr); lower->sla_support_points.clear(); - lower->sla_points_status = sla::PointsStatus::None; + lower->sla_points_status = sla::PointsStatus::NoPoints; lower->clear_volumes(); lower->input_file = ""; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 4bd2c7473c..a3281e5222 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -199,7 +199,7 @@ public: std::vector sla_support_points; // To keep track of where the points came from (used for synchronization between // the SLA gizmo and the backend). - sla::PointsStatus sla_points_status = sla::PointsStatus::None; + sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints; /* This vector accumulates the total translation applied to the object by the center_around_origin() method. Callers might want to apply the same translation diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index 874388e051..d95397fed5 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -18,7 +18,7 @@ namespace sla { // An enum to keep track of where the current points on the ModelObject came from. enum class PointsStatus { - None, // No points were generated so far. + NoPoints, // No points were generated so far. Generating, // The autogeneration algorithm triggered, but not yet finished. AutoGenerated, // Points were autogenerated (i.e. copied from the backend). UserModified // User has done some edits. diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index fb2a5d7df3..b3a075e268 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -388,9 +388,9 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf if (it_print_object_status != print_object_status.end()) update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); - model_object.sla_points_status = model_object_new.sla_points_status; model_object.sla_support_points = model_object_new.sla_support_points; } + model_object.sla_points_status = model_object_new.sla_points_status; // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. model_object.name = model_object_new.name; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6c9e79dbd9..7fdbbaabc9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1905,7 +1905,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re if (volume->volume_idx() < 0) { auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id) - mvs = &(*it); + // This can be an SLA support structure that should not be rendered (in case someone used undo + // to revert to before it was generated). We only reuse the volume if that's not the case. + if (m_model->objects[volume->composite_id.object_id]->sla_points_status != sla::PointsStatus::NoPoints) + mvs = &(*it); } else { auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower); if (it != model_volume_state.end() && it->geometry_id == key.geometry_id) @@ -2018,8 +2021,14 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id); auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower); assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id); - if (it->new_geometry()) - instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); + if (it->new_geometry()) { + // This can be an SLA support structure that should not be rendered (in case someone used undo + // to revert to before it was generated). If that's the case, we should not generate anything. + if (model_object->sla_points_status != sla::PointsStatus::NoPoints) + instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); + else + shift_zs[object_idx] = 0.; + } else { // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 6875a5b885..03f30f69f6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -84,7 +84,7 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S if (m_editing_mode_cache.empty() && m_model_object->sla_points_status != sla::PointsStatus::UserModified - && m_model_object->sla_points_status != sla::PointsStatus::None) + && m_model_object->sla_points_status != sla::PointsStatus::NoPoints) get_data_from_backend(); if (m_state == On) { @@ -961,7 +961,7 @@ RENDER_AGAIN: m_imgui->disabled_end(); // m_imgui->text(""); - // m_imgui->text(m_model_object->sla_points_status == sla::PointsStatus::None ? _(L("No points (will be autogenerated)")) : + // m_imgui->text(m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) : // (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) : // (m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) : // (m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS")))); @@ -1190,8 +1190,8 @@ void GLGizmoSlaSupports::editing_mode_discard_changes() else editing_mode_reload_cache();*/ + select_point(NoPoints); m_editing_mode_cache = m_old_cache; - m_editing_mode = false; m_unsaved_changes = false; } From 40a576a8adb73e755fcdb0a4a8d6331e1ffa098a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Sun, 28 Jul 2019 22:00:39 +0200 Subject: [PATCH 428/627] Implemented update of the override filaments options from/to config --- src/libslic3r/Config.hpp | 1 + src/slic3r/GUI/Field.cpp | 71 +++++++++++----- src/slic3r/GUI/Field.hpp | 18 ++-- src/slic3r/GUI/GUI.cpp | 7 ++ src/slic3r/GUI/OptionsGroup.cpp | 49 ++++++----- src/slic3r/GUI/Tab.cpp | 143 +++++++++++++++++++------------- src/slic3r/GUI/Tab.hpp | 5 +- 7 files changed, 189 insertions(+), 105 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 64fc69dd55..1615bcc8c7 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -982,6 +982,7 @@ public: ConfigOptionBoolsTempl() : ConfigOptionVector() {} explicit ConfigOptionBoolsTempl(size_t n, bool value) : ConfigOptionVector(n, (unsigned char)value) {} explicit ConfigOptionBoolsTempl(std::initializer_list il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); } + explicit ConfigOptionBoolsTempl(std::initializer_list il) { values.reserve(il.size()); for (unsigned char b : il) values.emplace_back(b); } explicit ConfigOptionBoolsTempl(const std::vector& vec) : ConfigOptionVector(vec) {} explicit ConfigOptionBoolsTempl(std::vector&& vec) : ConfigOptionVector(std::move(vec)) {} diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index e84e9637ff..cdf3445747 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -255,6 +255,7 @@ void TextCtrl::BUILD() { m_opt.default_value->getFloat() : m_opt.get_default_value()->get_at(m_opt_idx); text_value = double_to_string(val); + m_last_meaningful_value = text_value; break; } case coString: @@ -324,24 +325,7 @@ void TextCtrl::BUILD() { } propagate_value(); }), temp->GetId()); - /* - temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent& evt) - { -#ifdef __WXGTK__ - if (bChangedValueEvent) -#endif //__WXGTK__ - if(is_defined_input_value()) - on_change_field(); - }), temp->GetId()); -#ifdef __WXGTK__ - // to correct value updating on GTK we should: - // call on_change_field() on wxEVT_KEY_UP instead of wxEVT_TEXT - // and prevent value updating on wxEVT_KEY_DOWN - temp->Bind(wxEVT_KEY_DOWN, &TextCtrl::change_field_value, this); - temp->Bind(wxEVT_KEY_UP, &TextCtrl::change_field_value, this); -#endif //__WXGTK__ -*/ // select all text using Ctrl+A temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event) { @@ -362,6 +346,18 @@ void TextCtrl::propagate_value() on_kill_focus(); } +void TextCtrl::set_last_meaningful_value() +{ + dynamic_cast(window)->SetValue(boost::any_cast(m_last_meaningful_value)); + propagate_value(); +} + +void TextCtrl::set_na_value() +{ + dynamic_cast(window)->SetValue("nan"); + propagate_value(); +} + boost::any& TextCtrl::get_value() { wxString ret_str = static_cast(window)->GetValue(); @@ -408,6 +404,8 @@ void CheckBox::BUILD() { m_opt.get_default_value()->get_at(m_opt_idx) : false; + m_last_meaningful_value = static_cast(check_value); + // Set Label as a string of at least one space simbol to correct system scaling of a CheckBox auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(" "), wxDefaultPosition, size); temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -415,7 +413,10 @@ void CheckBox::BUILD() { temp->SetValue(check_value); if (m_opt.readonly) temp->Disable(); - temp->Bind(wxEVT_CHECKBOX, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId()); + temp->Bind(wxEVT_CHECKBOX, ([this](wxCommandEvent e) { + m_is_na_val = false; + on_change_field(); + }), temp->GetId()); temp->SetToolTip(get_tooltip_text(check_value ? "true" : "false")); @@ -423,6 +424,38 @@ void CheckBox::BUILD() { window = dynamic_cast(temp); } +void CheckBox::set_value(const boost::any& value, bool change_event) +{ + m_disable_change_event = !change_event; + if (m_opt.nullable) { + m_is_na_val = boost::any_cast(value) == ConfigOptionBoolsNullable::nil_value(); + if (!m_is_na_val) + m_last_meaningful_value = value; + dynamic_cast(window)->SetValue(m_is_na_val ? false : boost::any_cast(value) != 0); + } + else + dynamic_cast(window)->SetValue(boost::any_cast(value)); + m_disable_change_event = false; +} + +void CheckBox::set_last_meaningful_value() +{ + if (m_opt.nullable) { + m_is_na_val = false; + dynamic_cast(window)->SetValue(boost::any_cast(m_last_meaningful_value) != 0); + on_change_field(); + } +} + +void CheckBox::set_na_value() +{ + if (m_opt.nullable) { + m_is_na_val = true; + dynamic_cast(window)->SetValue(false); + on_change_field(); + } +} + boost::any& CheckBox::get_value() { // boost::any m_value; @@ -430,7 +463,7 @@ boost::any& CheckBox::get_value() if (m_opt.type == coBool) m_value = static_cast(value); else - m_value = static_cast(value); + m_value = m_is_na_val ? ConfigOptionBoolsNullable::nil_value() : static_cast(value); return m_value; } diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 990c40e6f9..6dbf4af069 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -123,6 +123,8 @@ public: /// subclasses should overload with a specific version /// Postcondition: Method does not fire the on_change event. virtual void set_value(const boost::any& value, bool change_event) = 0; + virtual void set_last_meaningful_value() {} + virtual void set_na_value() {} /// Gets a boost::any representing this control. /// subclasses should overload with a specific version @@ -247,6 +249,8 @@ protected: // current value boost::any m_value; + // last maeningful value + boost::any m_last_meaningful_value; int m_em_unit; @@ -289,9 +293,14 @@ public: virtual void set_value(const boost::any& value, bool change_event = false) { m_disable_change_event = !change_event; dynamic_cast(window)->SetValue(boost::any_cast(value)); + if (boost::any_cast(value) != "nan") + m_last_meaningful_value = value; m_disable_change_event = false; } + virtual void set_last_meaningful_value() override; + virtual void set_na_value() override; + boost::any& get_value() override; void msw_rescale() override; @@ -303,6 +312,7 @@ public: class CheckBox : public Field { using Field::Field; + bool m_is_na_val {false}; public: CheckBox(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} CheckBox(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} @@ -316,11 +326,9 @@ public: dynamic_cast(window)->SetValue(value); m_disable_change_event = false; } - void set_value(const boost::any& value, bool change_event = false) { - m_disable_change_event = !change_event; - dynamic_cast(window)->SetValue(boost::any_cast(value)); - m_disable_change_event = false; - } + void set_value(const boost::any& value, bool change_event = false) override; + void set_last_meaningful_value() override; + void set_na_value() override; boost::any& get_value() override; void msw_rescale() override; diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 8f41ed5a39..826f2d6fcf 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -148,6 +148,13 @@ void config_wizard(int reason) void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) { try{ + + if (config.def()->get(opt_key)->type == coBools && config.def()->get(opt_key)->nullable) { + ConfigOptionBoolsNullable* vec_new = new ConfigOptionBoolsNullable{ boost::any_cast(value) }; + config.option(opt_key)->set_at(vec_new, opt_index, 0); + return; + } + switch (config.def()->get(opt_key)->type) { case coFloatOrPercent:{ std::string str = boost::any_cast(value); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index f4f97a47d3..2aca87d872 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -372,30 +372,10 @@ void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const b auto option = m_options.at(opt_id).opt; - // get value -//! auto field_value = get_value(opt_id); - if (option.gui_flags.compare("serialized")==0) { - if (opt_index != -1) { - // die "Can't set serialized option indexed value" ; - } - change_opt_value(*m_config, opt_key, value); - } - else { - if (opt_index == -1) { - // change_opt_value(*m_config, opt_key, field_value); - //!? why field_value?? in this case changed value will be lose! No? - change_opt_value(*m_config, opt_key, value); - } - else { - change_opt_value(*m_config, opt_key, value, opt_index); -// auto value = m_config->get($opt_key); -// $value->[$opt_index] = $field_value; -// $self->config->set($opt_key, $value); - } - } + change_opt_value(*m_config, opt_key, value, opt_index == -1 ? 0 : opt_index); } - OptionsGroup::on_change_OG(opt_id, value); //!? Why doing this + OptionsGroup::on_change_OG(opt_id, value); } void ConfigOptionsGroup::back_to_initial_value(const std::string& opt_key) @@ -578,6 +558,31 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config boost::any ret; wxString text_value = wxString(""); const ConfigOptionDef* opt = config.def()->get(opt_key); + + if (opt->nullable) + { + switch (opt->type) + { + case coPercents: + case coFloats: { + double val = opt->type == coFloats ? + config.option(opt_key)->get_at(idx) : + config.option(opt_key)->get_at(idx); + ret = double_to_string(val); + } + break; + case coBools: + ret = config.option(opt_key)->values[idx]; + break; + case coInts: + ret = config.option(opt_key)->get_at(idx); + break; + default: + break; + } + return ret; + } + switch (opt->type) { case coFloatOrPercent:{ const auto &value = *config.option(opt_key); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 27f9fb0f35..780489670f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1491,89 +1491,114 @@ void TabPrint::OnActivate() Tab::OnActivate(); } -void TabFilament::add_overrides_page() +static void check_line(const bool is_checked, ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index) { - PageShp page = add_options_page(_(L("Overrides")), "wrench"); + Field* field = optgroup->get_fieldc(opt_key, opt_index); + if (field != nullptr) { + field->toggle(is_checked); + if (is_checked) + field->set_last_meaningful_value(); + else + field->set_na_value(); + } +}; - const DynamicPrintConfig& printer_cfg = wxGetApp().preset_bundle->printers.default_preset().config; +void TabFilament::add_filament_overrides_page() +{ + PageShp page = add_options_page(_(L("Filament Overrides")), "wrench"); - ConfigOptionsGroupShp optgroup = page->new_optgroup(_(L("Retraction"/*Overrides"*/))); + ConfigOptionsGroupShp optgroup = page->new_optgroup(_(L("Retraction"))); - auto append_single_option_line = [printer_cfg, optgroup, this](const std::string& opt_key, int opt_index) + auto append_single_option_line = [optgroup, this](const std::string& opt_key, int opt_index) { - const std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index); - const Option& option = Option(*printer_cfg.def()->get(opt_key), opt_id); + Line line = optgroup->create_single_option_line(optgroup->get_option(opt_key)); - Line line = optgroup->create_single_option_line(option); - - line.near_label_widget = [optgroup, opt_id](wxWindow* parent) { + line.near_label_widget = [this, optgroup, opt_key, opt_index](wxWindow* parent) { wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); - check_box->Bind(wxEVT_CHECKBOX, [optgroup, opt_id](wxCommandEvent& evt) - { - Field* field = optgroup->get_field(opt_id); - if (field != nullptr) - field->toggle(evt.IsChecked()); - }, check_box->GetId()); + check_box->Bind(wxEVT_CHECKBOX, [this, optgroup, opt_key, opt_index](wxCommandEvent& evt) { + check_line(evt.IsChecked(), optgroup, opt_key, opt_index); + }, check_box->GetId()); + + m_overrides_options[opt_key] = check_box; return check_box; }; optgroup->append_line(line); - - Field* field = optgroup->get_field(opt_id); - if (field != nullptr) - field->toggle(false); }; int extruder_idx = 0; // #ys_FIXME - append_single_option_line("retract_length", extruder_idx); - append_single_option_line("retract_lift", extruder_idx); + append_single_option_line("filament_retract_length", extruder_idx); + append_single_option_line("filament_retract_lift", extruder_idx); Line line = { _(L("Only lift Z")), "" }; - std::vector opt_ids; - opt_ids.reserve(2); - for (const std::string& opt_key : { "retract_lift_above", "retract_lift_below" }) - { - const std::string opt_id = extruder_idx == -1 ? opt_key : opt_key + "#" + std::to_string(extruder_idx); - opt_ids.push_back(opt_id); - const Option& option = Option(*printer_cfg.def()->get(opt_key), opt_id); + std::vector opt_keys = { "filament_retract_lift_above", "filament_retract_lift_below" }; + for (const std::string& opt_key : opt_keys) + line.append_option(optgroup->get_option(opt_key)); - line.append_option(option); - } - - line.near_label_widget = [optgroup, opt_ids](wxWindow* parent) { + line.near_label_widget = [this, optgroup, opt_keys, extruder_idx](wxWindow* parent) { wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); - check_box->Bind(wxEVT_CHECKBOX, [optgroup, opt_ids](wxCommandEvent& evt) - { - Field* field = nullptr; - for (const std::string& opt_id : opt_ids) { - field = optgroup->get_field(opt_id); - if (field != nullptr) - field->toggle(evt.IsChecked()); - } - }, check_box->GetId()); + check_box->Bind(wxEVT_CHECKBOX, [optgroup, opt_keys, extruder_idx](wxCommandEvent& evt) { + const bool is_checked = evt.IsChecked(); + for (const std::string& opt_key : opt_keys) + check_line(is_checked, optgroup, opt_key, extruder_idx); + }, check_box->GetId()); + + for (const std::string& opt_key : opt_keys) + m_overrides_options[opt_key] = check_box; + return check_box; }; optgroup->append_line(line); - Field* field = nullptr; - for (const std::string& opt_id : opt_ids) { - field = optgroup->get_field(opt_id); - if (field != nullptr) - field->toggle(false); - } + append_single_option_line("filament_retract_speed", extruder_idx); + append_single_option_line("filament_deretract_speed", extruder_idx); + append_single_option_line("filament_retract_restart_extra", extruder_idx); + append_single_option_line("filament_retract_before_travel", extruder_idx); + append_single_option_line("filament_retract_layer_change", extruder_idx); + append_single_option_line("filament_wipe", extruder_idx); + append_single_option_line("filament_retract_before_wipe", extruder_idx); +} - append_single_option_line("retract_speed", extruder_idx); - append_single_option_line("deretract_speed", extruder_idx); - append_single_option_line("retract_restart_extra", extruder_idx); - append_single_option_line("retract_before_travel", extruder_idx); - append_single_option_line("retract_layer_change", extruder_idx); - append_single_option_line("wipe", extruder_idx); - append_single_option_line("retract_before_wipe", extruder_idx); +void TabFilament::update_filament_overrides_page() +{ + const auto page_it = std::find_if(m_pages.begin(), m_pages.end(), [](const PageShp page) {return page->title() == _(L("Filament Overrides")); }); + if (page_it == m_pages.end()) + return; + PageShp page = *page_it; + + const auto og_it = std::find_if(page->m_optgroups.begin(), page->m_optgroups.end(), [](const ConfigOptionsGroupShp og) {return og->title == _(L("Retraction")); }); + if (og_it == page->m_optgroups.end()) + return; + ConfigOptionsGroupShp optgroup = *og_it; + + std::vector opt_keys = { "filament_retract_length", + "filament_retract_lift", + "filament_retract_lift_above", "filament_retract_lift_below", + "filament_retract_speed", + "filament_deretract_speed", + "filament_retract_restart_extra", + "filament_retract_before_travel", + "filament_retract_layer_change", + "filament_wipe", + "filament_retract_before_wipe" + }; + + int extruder_idx = 0; // #ys_FIXME + + for (const std::string& opt_key : opt_keys) + { + Field* field = optgroup->get_fieldc(opt_key, extruder_idx); + if (field != nullptr) { + const bool is_checked = !m_config->option(opt_key)->is_nil(); + m_overrides_options[opt_key]->SetValue(is_checked); + field->toggle(is_checked); + } + } } void TabFilament::build() @@ -1674,7 +1699,7 @@ void TabFilament::build() optgroup->append_line(line); - add_overrides_page(); + add_filament_overrides_page(); const int gcode_field_height = 15; // 150 @@ -1744,7 +1769,7 @@ void TabFilament::update() return; // ys_FIXME m_update_cnt++; -// Freeze(); + wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); m_cooling_description_line->SetText(text); text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); @@ -1759,7 +1784,9 @@ void TabFilament::update() for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) get_field(el)->toggle(fan_always_on); -// Thaw(); + + update_filament_overrides_page(); + m_update_cnt--; if (m_update_cnt == 0) diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 7172f595ae..6ff76f5c4a 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -338,7 +338,10 @@ class TabFilament : public Tab ogStaticText* m_volumetric_speed_description_line; ogStaticText* m_cooling_description_line; - void add_overrides_page(); + void add_filament_overrides_page(); + void update_filament_overrides_page(); + + std::map m_overrides_options; public: TabFilament(wxNotebook* parent) : // Tab(parent, _(L("Filament Settings")), L("filament")) {} From eeef3b42df40e26d129139744e0e87acdff82ac6 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 29 Jul 2019 13:07:49 +0200 Subject: [PATCH 429/627] SLA gizmo now uses the new separate undo/redo stack for manual editing mode --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 306 +++++++++++-------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 24 +- src/slic3r/Utils/UndoRedo.cpp | 2 +- 3 files changed, 205 insertions(+), 127 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 03f30f69f6..1daa576140 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -64,10 +64,11 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S return; } - if (m_model_object != model_object) + if (m_model_object != model_object) { + m_model_object = model_object; m_print_object_idx = -1; + } - m_model_object = model_object; m_active_instance = selection.get_instance_idx(); if (model_object && selection.is_from_single_instance()) @@ -79,12 +80,11 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S if (is_mesh_update_necessary()) { update_mesh(); - editing_mode_reload_cache(); + reload_cache(); } - if (m_editing_mode_cache.empty() - && m_model_object->sla_points_status != sla::PointsStatus::UserModified - && m_model_object->sla_points_status != sla::PointsStatus::NoPoints) + // If we triggered autogeneration before, check backend and fetch results if they are there + if (m_model_object->sla_points_status == sla::PointsStatus::Generating) get_data_from_backend(); if (m_state == On) { @@ -96,6 +96,8 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S } } + + void GLGizmoSlaSupports::on_render() const { const Selection& selection = m_parent.get_selection(); @@ -284,10 +286,11 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) glsafe(::glMultMatrixd(instance_matrix.data())); float render_color[3]; - for (int i = 0; i < (int)m_editing_mode_cache.size(); ++i) + size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); + for (size_t i = 0; i < cache_size; ++i) { - const sla::SupportPoint& support_point = m_editing_mode_cache[i].support_point; - const bool& point_selected = m_editing_mode_cache[i].selected; + const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i]; + const bool& point_selected = m_editing_mode ? m_editing_cache[i].selected : false; if (is_point_clipped(support_point.pos.cast())) continue; @@ -306,7 +309,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) render_color[2] = 1.0f; } else { // neigher hover nor picking - bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].support_point.is_new_island; + bool supports_new_island = m_lock_unique_islands && support_point.is_new_island; if (m_editing_mode) { render_color[0] = point_selected ? 1.0f : (supports_new_island ? 0.3f : 0.7f); render_color[1] = point_selected ? 0.3f : (supports_new_island ? 0.3f : 0.7f); @@ -331,24 +334,24 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) // Matrices set, we can render the point mark now. // If in editing mode, we'll also render a cone pointing to the sphere. if (m_editing_mode) { - if (m_editing_mode_cache[i].normal == Vec3f::Zero()) + if (m_editing_cache[i].normal == Vec3f::Zero()) update_cache_entry_normal(i); // in case the normal is not yet cached, find and cache it Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_mode_cache[i].normal.cast()); + q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); Eigen::AngleAxisd aa(q); glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2))); const float cone_radius = 0.25f; // mm const float cone_height = 0.75f; glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale)); + glsafe(::glTranslatef(0.f, 0.f, support_point.head_front_radius * RenderPointScale)); ::gluCylinder(m_quadric, 0.f, cone_radius, cone_height, 24, 1); glsafe(::glTranslatef(0.f, 0.f, cone_height)); ::gluDisk(m_quadric, 0.0, cone_radius, 24, 1); glsafe(::glPopMatrix()); } - ::gluSphere(m_quadric, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale, 24, 12); + ::gluSphere(m_quadric, support_point.head_front_radius * RenderPointScale, 24, 12); if (vol->is_left_handed()) glFrontFace(GL_CCW); @@ -390,6 +393,8 @@ bool GLGizmoSlaSupports::is_mesh_update_necessary() const && ((m_model_object->id() != m_current_mesh_object_id) || m_its == nullptr); } + + void GLGizmoSlaSupports::update_mesh() { if (! m_model_object) @@ -411,9 +416,11 @@ void GLGizmoSlaSupports::update_mesh() } m_current_mesh_object_id = m_model_object->id(); - m_editing_mode = false; + disable_editing_mode(); } + + // Unprojects the mouse position on the mesh and return the hit point and normal of the facet. // The function throws if no intersection if found. std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) @@ -499,7 +506,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } } else { - if (m_editing_mode_cache[m_hover_id].selected) + if (m_editing_cache[m_hover_id].selected) unselect_point(m_hover_id); else { if (!alt_down) @@ -520,8 +527,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_selection_empty) { try { std::pair pos_and_normal = unproject_on_mesh(mouse_position); // don't create anything if this throws - m_editing_mode_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); - m_unsaved_changes = true; + wxGetApp().plater()->take_snapshot(_(L("Add support point"))); + m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); m_parent.set_as_dirty(); m_wait_for_up_event = true; } @@ -543,8 +550,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // First collect positions of all the points in world coordinates. const Transform3d& instance_matrix = m_model_object->instances[m_active_instance]->get_transformation().get_matrix(); std::vector points; - for (unsigned int i=0; i()); points.back()(2) += m_z_shift; } @@ -564,7 +571,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // Iterate over all points in the rectangle and check that they are neither clipped by the // clipping plane nor obscured by the mesh. for (const unsigned int i : selected_idxs) { - const sla::SupportPoint &support_point = m_editing_mode_cache[i].support_point; + const sla::SupportPoint &support_point = m_editing_cache[i].support_point; if (!is_point_clipped(support_point.pos.cast())) { bool is_obscured = false; // Cast a ray in the direction of the camera and look for intersection with the mesh: @@ -704,10 +711,16 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous void GLGizmoSlaSupports::delete_selected_points(bool force) { - for (unsigned int idx=0; idxtake_snapshot(_(L("Delete support point"))); + + for (unsigned int idx=0; idxreslice_SLA_supports(*m_model_object); @@ -720,18 +733,21 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) void GLGizmoSlaSupports::on_update(const UpdateData& data) { - if (m_editing_mode && m_hover_id != -1 && (!m_editing_mode_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) { - std::pair pos_and_normal; - try { - pos_and_normal = unproject_on_mesh(data.mouse_pos.cast()); + if (! m_editing_mode) + return; + else { + if (m_hover_id != -1 && (! m_editing_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) { + std::pair pos_and_normal; + try { + pos_and_normal = unproject_on_mesh(data.mouse_pos.cast()); + } + catch (...) { return; } + m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first; + m_editing_cache[m_hover_id].support_point.is_new_island = false; + m_editing_cache[m_hover_id].normal = pos_and_normal.second; + // Do not update immediately, wait until the mouse is released. + // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } - catch (...) { return; } - m_editing_mode_cache[m_hover_id].support_point.pos = pos_and_normal.first; - m_editing_mode_cache[m_hover_id].support_point.is_new_island = false; - m_editing_mode_cache[m_hover_id].normal = pos_and_normal.second; - m_unsaved_changes = true; - // Do not update immediately, wait until the mouse is released. - // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } } @@ -766,7 +782,7 @@ std::vector GLGizmoSlaSupports::get_config_options(const st void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const { int idx = 0; - Eigen::Matrix pp = m_editing_mode_cache[i].support_point.pos; + Eigen::Matrix pp = m_editing_cache[i].support_point.pos; Eigen::Matrix cc; m_AABB.squared_distance( MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), @@ -774,7 +790,7 @@ void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const pp, idx, cc); Vec3f a = (m_its->vertices[m_its->indices[idx](1)] - m_its->vertices[m_its->indices[idx](0)]); Vec3f b = (m_its->vertices[m_its->indices[idx](2)] - m_its->vertices[m_its->indices[idx](0)]); - m_editing_mode_cache[i].normal = a.cross(b); + m_editing_cache[i].normal = a.cross(b); } @@ -883,13 +899,30 @@ RENDER_AGAIN: ImGui::SameLine(diameter_slider_left); ImGui::PushItemWidth(window_width - diameter_slider_left); - if (ImGui::SliderFloat("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f")) { - // value was changed - for (auto& cache_entry : m_editing_mode_cache) - if (cache_entry.selected) { + float initial_value = m_new_point_head_diameter; + ImGui::SliderFloat("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f"); + if (ImGui::IsItemClicked()) { + if (m_old_point_head_diameter == 0.f) + m_old_point_head_diameter = initial_value; + } + if (ImGui::IsItemEdited()) { + for (auto& cache_entry : m_editing_cache) + if (cache_entry.selected) cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; - m_unsaved_changes = true; - } + } + if (ImGui::IsItemDeactivatedAfterEdit()) { + // momentarily restore the old value to take snapshot + for (auto& cache_entry : m_editing_cache) + if (cache_entry.selected) + cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f; + float backup = m_new_point_head_diameter; + m_new_point_head_diameter = m_old_point_head_diameter; + wxGetApp().plater()->take_snapshot(_(L("Change point head diameter"))); + m_new_point_head_diameter = backup; + for (auto& cache_entry : m_editing_cache) + if (cache_entry.selected) + cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; + m_old_point_head_diameter = 0.f; } bool changed = m_lock_unique_islands; @@ -900,7 +933,7 @@ RENDER_AGAIN: remove_selected = m_imgui->button(m_desc.at("remove_selected")); m_imgui->disabled_end(); - m_imgui->disabled_begin(m_editing_mode_cache.empty()); + m_imgui->disabled_begin(m_editing_cache.empty()); remove_all = m_imgui->button(m_desc.at("remove_all")); m_imgui->disabled_end(); @@ -938,14 +971,8 @@ RENDER_AGAIN: m_model_object->config.opt("support_points_density_relative", true)->value = (int)density; } - if (value_changed) { // Update side panel -/* wxTheApp->CallAfter([]() { - * wxGetApp().obj_settings()->UpdateAndShow(true); - * wxGetApp().obj_list()->update_settings_items(); - * }); - * #lm_FIXME_delete_after_testing */ + if (value_changed) // Update side panel wxGetApp().obj_list()->update_and_show_object_settings_item(); - } bool generate = m_imgui->button(m_desc.at("auto_generate")); @@ -956,7 +983,7 @@ RENDER_AGAIN: if (m_imgui->button(m_desc.at("manual_editing"))) switch_to_editing_mode(); - m_imgui->disabled_begin(m_editing_mode_cache.empty()); + m_imgui->disabled_begin(m_normal_cache.empty()); remove_all = m_imgui->button(m_desc.at("remove_all")); m_imgui->disabled_end(); @@ -1003,11 +1030,13 @@ RENDER_AGAIN: if (remove_selected || remove_all) { force_refresh = false; m_parent.set_as_dirty(); - if (remove_all) + if (remove_all) { + if (!m_editing_mode) + switch_to_editing_mode(); select_point(AllPoints); - delete_selected_points(remove_all); - if (remove_all && !m_editing_mode) + delete_selected_points(true); // true - delete regardless of locked status editing_mode_apply_changes(); + } if (first_run) { first_run = false; goto RENDER_AGAIN; @@ -1045,6 +1074,8 @@ std::string GLGizmoSlaSupports::on_get_name() const return (_(L("SLA Support Points")) + " [L]").ToUTF8().data(); } + + void GLGizmoSlaSupports::on_set_state() { // m_model_object pointer can be invalid (for instance because of undo/redo action), @@ -1058,13 +1089,15 @@ void GLGizmoSlaSupports::on_set_state() } } + // If ModelObject pointer really changed, invalidate mesh and do everything // as if the gizmo was switched from Off state - if (m_model_object == nullptr || old_model_object != m_model_object) { + /*if (m_model_object == nullptr || old_model_object != m_model_object) { m_mesh = nullptr; m_its = nullptr; - m_old_state = Off; - } + if (m_state == On) + m_old_state = Off; + }*/ if (m_state == On && m_old_state != On) { // the gizmo was just turned on if (is_mesh_update_necessary()) @@ -1072,7 +1105,7 @@ void GLGizmoSlaSupports::on_set_state() // we'll now reload support points: if (m_model_object) - editing_mode_reload_cache(); + reload_cache(); m_parent.toggle_model_objects_visibility(false); if (m_model_object) @@ -1087,7 +1120,7 @@ void GLGizmoSlaSupports::on_set_state() // Following is called through CallAfter, because otherwise there was a problem // on OSX with the wxMessageDialog being shown several times when clicked into. if (m_model_object) { - if (m_unsaved_changes) { + if (m_editing_mode && unsaved_changes()) { wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points?")) + "\n", _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); if (dlg.ShowModal() == wxID_YES) @@ -1097,8 +1130,8 @@ void GLGizmoSlaSupports::on_set_state() } } m_parent.toggle_model_objects_visibility(true); - m_editing_mode = false; // so it is not active next time the gizmo opens - m_editing_mode_cache.clear(); + disable_editing_mode(); // so it is not active next time the gizmo opens + m_normal_cache.clear(); m_clipping_plane_distance = 0.f; // Release triangle mesh slicer and the AABB spatial search structure. m_AABB.deinit(); @@ -1117,6 +1150,22 @@ void GLGizmoSlaSupports::on_start_dragging() if (m_hover_id != -1) { select_point(NoPoints); select_point(m_hover_id); + m_dragged_point_initial_pos = m_editing_cache[m_hover_id].support_point.pos; + } +} + + +void GLGizmoSlaSupports::on_stop_dragging() +{ + Vec3f backup = m_editing_cache[m_hover_id].support_point.pos; + + if (backup != Vec3f::Zero() // some point was touched + && backup != m_dragged_point_initial_pos) // and it was moved, not just selected + { + m_editing_cache[m_hover_id].support_point.pos = m_dragged_point_initial_pos; + wxGetApp().plater()->take_snapshot(_(L("Move support point"))); + m_editing_cache[m_hover_id].support_point.pos = backup; + m_dragged_point_initial_pos = Vec3f::Zero(); } } @@ -1124,13 +1173,12 @@ void GLGizmoSlaSupports::on_start_dragging() void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) { - if (m_editing_mode) - editing_mode_discard_changes(); - ar(m_clipping_plane_distance, m_clipping_plane_normal, m_current_mesh_object_id, - m_editing_mode_cache + m_new_point_head_diameter, + m_normal_cache, + m_editing_cache ); } @@ -1141,7 +1189,9 @@ void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const ar(m_clipping_plane_distance, m_clipping_plane_normal, m_current_mesh_object_id, - m_editing_mode_cache + m_new_point_head_diameter, + m_normal_cache, + m_editing_cache ); } @@ -1149,27 +1199,37 @@ void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const void GLGizmoSlaSupports::select_point(int i) { + if (! m_editing_mode) { + std::cout << "DEBUGGING: select_point called when out of editing mode!" << std::endl; + std::abort(); + } + if (i == AllPoints || i == NoPoints) { - for (auto& point_and_selection : m_editing_mode_cache) + for (auto& point_and_selection : m_editing_cache) point_and_selection.selected = ( i == AllPoints ); m_selection_empty = (i == NoPoints); if (i == AllPoints) - m_new_point_head_diameter = m_editing_mode_cache[0].support_point.head_front_radius * 2.f; + m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f; } else { - m_editing_mode_cache[i].selected = true; + m_editing_cache[i].selected = true; m_selection_empty = false; - m_new_point_head_diameter = m_editing_mode_cache[i].support_point.head_front_radius * 2.f; + m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f; } } void GLGizmoSlaSupports::unselect_point(int i) { - m_editing_mode_cache[i].selected = false; + if (! m_editing_mode) { + std::cout << "DEBUGGING: unselect_point called when out of editing mode!" << std::endl; + std::abort(); + } + + m_editing_cache[i].selected = false; m_selection_empty = true; - for (const CacheEntry& ce : m_editing_mode_cache) { + for (const CacheEntry& ce : m_editing_cache) { if (ce.selected) { m_selection_empty = false; break; @@ -1179,21 +1239,15 @@ void GLGizmoSlaSupports::unselect_point(int i) + void GLGizmoSlaSupports::editing_mode_discard_changes() { - // If the points were autogenerated, they may not be on the ModelObject yet. - // Because the user probably messed with the cache, we will get the data - // from the backend again. - /*if (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated) { - get_data_from_backend(); - } - else - editing_mode_reload_cache();*/ - + if (! m_editing_mode) { + std::cout << "DEBUGGING: editing_mode_discard_changes called when out of editing mode!" << std::endl; + std::abort(); + } select_point(NoPoints); - m_editing_mode_cache = m_old_cache; - m_editing_mode = false; - m_unsaved_changes = false; + disable_editing_mode(); } @@ -1202,38 +1256,33 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() { // If there are no changes, don't touch the front-end. The data in the cache could have been // taken from the backend and copying them to ModelObject would needlessly invalidate them. - if (m_unsaved_changes) { - // In order to make the undo/redo snapshot, we must first temporarily restore - // the editing cache to the state before the edits were made. - std::vector backup = m_editing_mode_cache; - m_editing_mode_cache = m_old_cache; + disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken + + if (unsaved_changes()) { wxGetApp().plater()->take_snapshot(_(L("Support points edit"))); - m_editing_mode_cache = std::move(backup); + + m_normal_cache.clear(); + for (const CacheEntry& ce : m_editing_cache) + m_normal_cache.push_back(ce.support_point); m_model_object->sla_points_status = sla::PointsStatus::UserModified; m_model_object->sla_support_points.clear(); - for (const CacheEntry& cache_entry : m_editing_mode_cache) - m_model_object->sla_support_points.push_back(cache_entry.support_point); + m_model_object->sla_support_points = m_normal_cache; - // Recalculate support structures once the editing mode is left. - // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); wxGetApp().CallAfter([this]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object); }); } - m_editing_mode = false; - m_unsaved_changes = false; } -void GLGizmoSlaSupports::editing_mode_reload_cache() +void GLGizmoSlaSupports::reload_cache() { - m_editing_mode_cache.clear(); - - for (const sla::SupportPoint& point : m_model_object->sla_support_points) - m_editing_mode_cache.emplace_back(point, false); - - m_unsaved_changes = false; + m_normal_cache.clear(); + if (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated || m_model_object->sla_points_status == sla::PointsStatus::Generating) + get_data_from_backend(); + else + for (const sla::SupportPoint& point : m_model_object->sla_support_points) + m_normal_cache.emplace_back(point); } @@ -1242,19 +1291,16 @@ void GLGizmoSlaSupports::get_data_from_backend() { for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { if (po->model_object()->id() == m_model_object->id() && po->is_step_done(slaposSupportPoints)) { - m_editing_mode_cache.clear(); + m_normal_cache.clear(); const std::vector& points = po->get_support_points(); auto mat = po->trafo().inverse().cast(); for (unsigned int i=0; isla_points_status != sla::PointsStatus::UserModified) - m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated; + m_normal_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island)); + m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated; break; } } - m_unsaved_changes = false; // We don't copy the data into ModelObject, as this would stop the background processing. } @@ -1268,12 +1314,10 @@ void GLGizmoSlaSupports::auto_generate() "Are you sure you want to do it?\n" )), _(L("Warning")), wxICON_WARNING | wxYES | wxNO); - if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_editing_mode_cache.empty() || dlg.ShowModal() == wxID_YES) { + if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { wxGetApp().plater()->take_snapshot(_(L("Autogenerate support points"))); - m_model_object->sla_support_points.clear(); - m_model_object->sla_points_status = sla::PointsStatus::Generating; - m_editing_mode_cache.clear(); wxGetApp().CallAfter([this]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object); }); + m_model_object->sla_points_status = sla::PointsStatus::Generating; } } @@ -1281,15 +1325,37 @@ void GLGizmoSlaSupports::auto_generate() void GLGizmoSlaSupports::switch_to_editing_mode() { - if (m_model_object->sla_points_status != sla::PointsStatus::AutoGenerated) - editing_mode_reload_cache(); - m_unsaved_changes = false; + wxGetApp().plater()->enter_gizmos_stack(); m_editing_mode = true; - m_old_cache = m_editing_mode_cache; + m_editing_cache.clear(); + for (const sla::SupportPoint& sp : m_normal_cache) + m_editing_cache.emplace_back(sp); select_point(NoPoints); } +void GLGizmoSlaSupports::disable_editing_mode() +{ + if (m_editing_mode) { + m_editing_mode = false; + wxGetApp().plater()->leave_gizmos_stack(); + } +} + + + +bool GLGizmoSlaSupports::unsaved_changes() const +{ + if (m_editing_cache.size() != m_normal_cache.size()) + return true; + + for (size_t i=0; i m_editing_mode_cache; // a support point and whether it is currently selected - std::vector m_old_cache; // to restore after discarding changes or undo/redo + mutable std::vector m_editing_cache; // a support point and whether it is currently selected + std::vector m_normal_cache; // to restore after discarding changes or undo/redo float m_clipping_plane_distance = 0.f; mutable float m_old_clipping_plane_distance = 0.f; @@ -112,7 +123,6 @@ private: GLSelectionRectangle m_selection_rectangle; bool m_wait_for_up_event = false; - bool m_unsaved_changes = false; // Are there unsaved changes in manual mode? bool m_selection_empty = true; EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) @@ -133,20 +143,22 @@ private: void unselect_point(int i); void editing_mode_apply_changes(); void editing_mode_discard_changes(); - void editing_mode_reload_cache(); + void reload_cache(); void get_data_from_backend(); void auto_generate(); void switch_to_editing_mode(); + void disable_editing_mode(); void reset_clipping_plane_normal() const; protected: void on_set_state() override; virtual void on_set_hover_id() { - if ((int)m_editing_mode_cache.size() <= m_hover_id) + if (! m_editing_mode || (int)m_editing_cache.size() <= m_hover_id) m_hover_id = -1; } void on_start_dragging() override; + void on_stop_dragging() override; virtual void on_render_input_window(float x, float y, float bottom_limit) override; virtual std::string on_get_name() const; diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 1d74123dd4..b3b2628e29 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -854,7 +854,7 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GU model.update_links_bottom_up_recursive(); m_selection.volumes_and_instances.clear(); this->load_mutable_object(m_selection.id(), m_selection); - gizmos.reset_all_states(); + //gizmos.reset_all_states(); FIXME: is this really necessary? It is quite unpleasant for the gizmo undo/redo substack this->load_mutable_object(gizmos.id(), gizmos); // Sort the volumes so that we may use binary search. std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end()); From 7b5c8b7e1661d90213e5b8590a0a9097673960f4 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 29 Jul 2019 14:00:22 +0200 Subject: [PATCH 430/627] Implemented some upgrades for filament overrides --- src/slic3r/GUI/Field.cpp | 21 +++++++- src/slic3r/GUI/Field.hpp | 9 +--- src/slic3r/GUI/OptionsGroup.cpp | 11 ++-- src/slic3r/GUI/Tab.cpp | 91 ++++++++++++++------------------- 4 files changed, 66 insertions(+), 66 deletions(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index cdf3445747..14386f3810 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -136,6 +136,8 @@ bool Field::is_matched(const std::string& string, const std::string& pattern) return std::regex_match(string, regex_pattern); } +static wxString na_value() { return _(L("N/A")); } + void Field::get_value_by_opt_type(wxString& str) { switch (m_opt.type) { @@ -165,7 +167,9 @@ void Field::get_value_by_opt_type(wxString& str) val = 0.0; else { - if (!str.ToCDouble(&val)) + if (m_opt.nullable && str == na_value()) + val = ConfigOptionFloatsNullable::nil_value(); + else if (!str.ToCDouble(&val)) { show_error(m_parent, _(L("Invalid numeric input."))); set_value(double_to_string(val), true); @@ -346,6 +350,19 @@ void TextCtrl::propagate_value() on_kill_focus(); } +void TextCtrl::set_value(const boost::any& value, bool change_event/* = false*/) { + m_disable_change_event = !change_event; + if (m_opt.nullable) { + const bool m_is_na_val = boost::any_cast(value) == na_value(); + if (!m_is_na_val) + m_last_meaningful_value = value; + dynamic_cast(window)->SetValue(m_is_na_val ? na_value() : boost::any_cast(value)); + } + else + dynamic_cast(window)->SetValue(boost::any_cast(value)); + m_disable_change_event = false; +} + void TextCtrl::set_last_meaningful_value() { dynamic_cast(window)->SetValue(boost::any_cast(m_last_meaningful_value)); @@ -354,7 +371,7 @@ void TextCtrl::set_last_meaningful_value() void TextCtrl::set_na_value() { - dynamic_cast(window)->SetValue("nan"); + dynamic_cast(window)->SetValue(na_value()); propagate_value(); } diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 6dbf4af069..49ff55d039 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -290,14 +290,7 @@ public: dynamic_cast(window)->SetValue(wxString(value)); m_disable_change_event = false; } - virtual void set_value(const boost::any& value, bool change_event = false) { - m_disable_change_event = !change_event; - dynamic_cast(window)->SetValue(boost::any_cast(value)); - if (boost::any_cast(value) != "nan") - m_last_meaningful_value = value; - m_disable_change_event = false; - } - + virtual void set_value(const boost::any& value, bool change_event = false) override; virtual void set_last_meaningful_value() override; virtual void set_na_value() override; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 2aca87d872..656e07a0e3 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -565,10 +565,13 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config { case coPercents: case coFloats: { - double val = opt->type == coFloats ? - config.option(opt_key)->get_at(idx) : - config.option(opt_key)->get_at(idx); - ret = double_to_string(val); + if (config.option(opt_key)->is_nil()) + ret = _(L("N/A")); + else { + double val = opt->type == coFloats ? + config.option(opt_key)->get_at(idx) : + config.option(opt_key)->get_at(idx); + ret = double_to_string(val); } } break; case coBools: diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4ff64de366..7bdaa0f2ed 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1497,33 +1497,35 @@ void TabPrint::OnActivate() Tab::OnActivate(); } -static void check_line(const bool is_checked, ConfigOptionsGroupShp optgroup, const std::string& opt_key, int opt_index) -{ - Field* field = optgroup->get_fieldc(opt_key, opt_index); - if (field != nullptr) { - field->toggle(is_checked); - if (is_checked) - field->set_last_meaningful_value(); - else - field->set_na_value(); - } -}; - void TabFilament::add_filament_overrides_page() { PageShp page = add_options_page(_(L("Filament Overrides")), "wrench"); - ConfigOptionsGroupShp optgroup = page->new_optgroup(_(L("Retraction"))); auto append_single_option_line = [optgroup, this](const std::string& opt_key, int opt_index) { - Line line = optgroup->create_single_option_line(optgroup->get_option(opt_key)); + Line line {"",""}; + if (opt_key == "filament_retract_lift_above" || opt_key == "filament_retract_lift_below") { + Option opt = optgroup->get_option(opt_key); + opt.opt.label = opt.opt.full_label; + line = optgroup->create_single_option_line(opt); + } + else + line = optgroup->create_single_option_line(optgroup->get_option(opt_key)); line.near_label_widget = [this, optgroup, opt_key, opt_index](wxWindow* parent) { wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); check_box->Bind(wxEVT_CHECKBOX, [this, optgroup, opt_key, opt_index](wxCommandEvent& evt) { - check_line(evt.IsChecked(), optgroup, opt_key, opt_index); + const bool is_checked = evt.IsChecked(); + Field* field = optgroup->get_fieldc(opt_key, opt_index); + if (field != nullptr) { + field->toggle(is_checked); + if (is_checked) + field->set_last_meaningful_value(); + else + field->set_na_value(); + } }, check_box->GetId()); m_overrides_options[opt_key] = check_box; @@ -1533,41 +1535,21 @@ void TabFilament::add_filament_overrides_page() optgroup->append_line(line); }; - int extruder_idx = 0; // #ys_FIXME + const int extruder_idx = 0; // #ys_FIXME - append_single_option_line("filament_retract_length", extruder_idx); - append_single_option_line("filament_retract_lift", extruder_idx); - - Line line = { _(L("Only lift Z")), "" }; - - std::vector opt_keys = { "filament_retract_lift_above", "filament_retract_lift_below" }; - for (const std::string& opt_key : opt_keys) - line.append_option(optgroup->get_option(opt_key)); - - line.near_label_widget = [this, optgroup, opt_keys, extruder_idx](wxWindow* parent) { - wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); - - check_box->Bind(wxEVT_CHECKBOX, [optgroup, opt_keys, extruder_idx](wxCommandEvent& evt) { - const bool is_checked = evt.IsChecked(); - for (const std::string& opt_key : opt_keys) - check_line(is_checked, optgroup, opt_key, extruder_idx); - }, check_box->GetId()); - - for (const std::string& opt_key : opt_keys) - m_overrides_options[opt_key] = check_box; - - return check_box; - }; - - optgroup->append_line(line); - - append_single_option_line("filament_retract_speed", extruder_idx); - append_single_option_line("filament_deretract_speed", extruder_idx); - append_single_option_line("filament_retract_restart_extra", extruder_idx); - append_single_option_line("filament_retract_before_travel", extruder_idx); - append_single_option_line("filament_retract_layer_change", extruder_idx); - append_single_option_line("filament_wipe", extruder_idx); - append_single_option_line("filament_retract_before_wipe", extruder_idx); + for (const std::string opt_key : { "filament_retract_length", + "filament_retract_lift", + "filament_retract_lift_above", + "filament_retract_lift_below", + "filament_retract_speed", + "filament_deretract_speed", + "filament_retract_restart_extra", + "filament_retract_before_travel", + "filament_retract_layer_change", + "filament_wipe", + "filament_retract_before_wipe" + }) + append_single_option_line(opt_key, extruder_idx); } void TabFilament::update_filament_overrides_page() @@ -1584,7 +1566,8 @@ void TabFilament::update_filament_overrides_page() std::vector opt_keys = { "filament_retract_length", "filament_retract_lift", - "filament_retract_lift_above", "filament_retract_lift_below", + "filament_retract_lift_above", + "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", @@ -1594,13 +1577,17 @@ void TabFilament::update_filament_overrides_page() "filament_retract_before_wipe" }; - int extruder_idx = 0; // #ys_FIXME + const int extruder_idx = 0; // #ys_FIXME + + const bool have_retract_length = m_config->option("filament_retract_length")->is_nil() ? false : + m_config->opt_float("filament_retract_length", extruder_idx) > 0; for (const std::string& opt_key : opt_keys) { + bool is_checked = opt_key=="filament_retract_length" ? true : have_retract_length; Field* field = optgroup->get_fieldc(opt_key, extruder_idx); if (field != nullptr) { - const bool is_checked = !m_config->option(opt_key)->is_nil(); + is_checked &= !m_config->option(opt_key)->is_nil(); m_overrides_options[opt_key]->SetValue(is_checked); field->toggle(is_checked); } From fca2fc0d973741eb8472601328dbc9a344c8fdc3 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 29 Jul 2019 14:51:35 +0200 Subject: [PATCH 431/627] Deprecate the How to build on Windows doc for now --- doc/How to build - Windows.md | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/doc/How to build - Windows.md b/doc/How to build - Windows.md index 242af9dab2..b8829047bf 100644 --- a/doc/How to build - Windows.md +++ b/doc/How to build - Windows.md @@ -1,24 +1,37 @@ +# This how-to is out of date + +We have switched to MS Visual Studio 2019. + +We don't use MSVS 2013 any more. At the moment we are in the process of creating new pre-built dependency bundles +and updating this document. In the meantime, you will need to compile the dependencies yourself +[the same way as before](#building-the-dependencies-package-yourself) +except with CMake generators for MSVS 2019 instead of 2013. + +Thank you for understanding. + +--- + # Building PrusaSlicer on Microsoft Windows -The currently supported way of building PrusaSlicer on Windows is with CMake and MS Visual Studio 2013. +~~The currently supported way of building PrusaSlicer on Windows is with CMake and MS Visual Studio 2013. You can use the free [Visual Studio 2013 Community Edition](https://www.visualstudio.com/vs/older-downloads/). -CMake installer can be downloaded from [the official website](https://cmake.org/download/). +CMake installer can be downloaded from [the official website](https://cmake.org/download/).~~ -Building with newer versions of MSVS (2015, 2017) may work too as reported by some of our users. +~~Building with newer versions of MSVS (2015, 2017) may work too as reported by some of our users.~~ _Note:_ Thanks to [**@supermerill**](https://github.com/supermerill) for testing and inspiration for this guide. ### Dependencies On Windows PrusaSlicer is built against statically built libraries. -We provide a prebuilt package of all the needed dependencies. This package only works on Visual Studio 2013, so if you are using a newer version of Visual Studio, you need to compile the dependencies yourself as per [below](#building-the-dependencies-package-yourself). +~~We provide a prebuilt package of all the needed dependencies. This package only works on Visual Studio 2013, so~~ if you are using a newer version of Visual Studio, you need to compile the dependencies yourself as per [below](#building-the-dependencies-package-yourself). The package comes in a several variants: - - [64 bit, Release mode only](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=destdir-64.7z) (41 MB, 578 MB unpacked) - - [64 bit, Release and Debug mode](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=destdir-64-dev.7z) (88 MB, 1.3 GB unpacked) - - [32 bit, Release mode only](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=destdir-32.7z) (38 MB, 520 MB unpacked) - - [32 bit, Release and Debug mode](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=destdir-32-dev.7z) (74 MB, 1.1 GB unpacked) + - ~~64 bit, Release mode only (41 MB, 578 MB unpacked)~~ + - ~~64 bit, Release and Debug mode (88 MB, 1.3 GB unpacked)~~ + - ~~32 bit, Release mode only (38 MB, 520 MB unpacked)~~ + - ~~32 bit, Release and Debug mode (74 MB, 1.1 GB unpacked)~~ When unsure, use the _Release mode only_ variant, the _Release and Debug_ variant is only needed for debugging & development. From e469ee76b827c41a4d7cc6ea81562dcfa6e5d3e1 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 29 Jul 2019 15:13:17 +0200 Subject: [PATCH 432/627] Implemented a highlighting of the delete button for setting (related to #1767) --- src/slic3r/GUI/GUI_ObjectSettings.cpp | 7 +++++++ src/slic3r/GUI/GUI_ObjectSettings.hpp | 1 + 2 files changed, 8 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 78c64e03e6..19a0e785ce 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -61,6 +61,7 @@ ObjectSettings::ObjectSettings(wxWindow* parent) : m_og->sizer->Add(m_settings_list_sizer, 1, wxEXPAND | wxLEFT, 5); m_bmp_delete = ScalableBitmap(parent, "cross"); + m_bmp_delete_focus = ScalableBitmap(parent, "cross_focus"); } bool ObjectSettings::update_settings_list() @@ -92,6 +93,9 @@ bool ObjectSettings::update_settings_list() auto btn = new ScalableButton(parent, wxID_ANY, m_bmp_delete); btn->SetToolTip(_(L("Remove parameter"))); + btn->SetBitmapFocus(m_bmp_delete_focus.bmp()); + btn->SetBitmapHover(m_bmp_delete_focus.bmp()); + btn->Bind(wxEVT_BUTTON, [opt_key, config, this](wxEvent &event) { wxGetApp().plater()->take_snapshot(wxString::Format(_(L("Delete Option %s")), opt_key)); config->erase(opt_key); @@ -122,6 +126,8 @@ bool ObjectSettings::update_settings_list() if (ctrl == nullptr) return; ctrl->SetBitmap_(m_bmp_delete); + ctrl->SetBitmapFocus(m_bmp_delete_focus.bmp()); + ctrl->SetBitmapHover(m_bmp_delete_focus.bmp()); }; const bool is_extruders_cat = cat.first == "Extruders"; @@ -166,6 +172,7 @@ void ObjectSettings::UpdateAndShow(const bool show) void ObjectSettings::msw_rescale() { m_bmp_delete.msw_rescale(); + m_bmp_delete_focus.msw_rescale(); for (auto group : m_og_settings) group->msw_rescale(); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.hpp b/src/slic3r/GUI/GUI_ObjectSettings.hpp index 01daa5622a..2a0c19c5c3 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.hpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.hpp @@ -39,6 +39,7 @@ class ObjectSettings : public OG_Settings std::vector > m_og_settings; ScalableBitmap m_bmp_delete; + ScalableBitmap m_bmp_delete_focus; public: ObjectSettings(wxWindow* parent); From 7d02811823977497a29dcd9b8d95c6b6f136a93e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 29 Jul 2019 15:21:29 +0200 Subject: [PATCH 433/627] Add missed bitmap --- resources/icons/cross_focus.svg | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 resources/icons/cross_focus.svg diff --git a/resources/icons/cross_focus.svg b/resources/icons/cross_focus.svg new file mode 100644 index 0000000000..ed004099ff --- /dev/null +++ b/resources/icons/cross_focus.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + From 47b344056182a094df2662329719d0a59263f328 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 29 Jul 2019 15:44:00 +0200 Subject: [PATCH 434/627] Fix of #2401 --- src/slic3r/GUI/GLCanvas3D.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6c9e79dbd9..accca2bbc0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2377,12 +2377,9 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); break; -#ifdef __APPLE__ - case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead. -#else /* __APPLE__ */ + case WXK_BACK: case WXK_DELETE: -#endif /* __APPLE__ */ - post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break; + post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break; default: evt.Skip(); } } else if (evt.HasModifiers()) { @@ -2390,11 +2387,8 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) } else { switch (keyCode) { -#ifdef __APPLE__ - case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead. -#else /* __APPLE__ */ + case WXK_BACK: case WXK_DELETE: -#endif /* __APPLE__ */ post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; case WXK_ESCAPE: { deselect_all(); break; } From 253d75523579fa862b6a81da15c801fca4158d0f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 29 Jul 2019 16:08:36 +0200 Subject: [PATCH 435/627] Fix for https://github.com/prusa3d/PrusaSlicer/commit/7b5c8b7e1661d90213e5b8590a0a9097673960f4 --- src/slic3r/GUI/Tab.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 7bdaa0f2ed..eeb2317ea7 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1585,12 +1585,14 @@ void TabFilament::update_filament_overrides_page() for (const std::string& opt_key : opt_keys) { bool is_checked = opt_key=="filament_retract_length" ? true : have_retract_length; + m_overrides_options[opt_key]->Enable(is_checked); + + is_checked &= !m_config->option(opt_key)->is_nil(); + m_overrides_options[opt_key]->SetValue(is_checked); + Field* field = optgroup->get_fieldc(opt_key, extruder_idx); - if (field != nullptr) { - is_checked &= !m_config->option(opt_key)->is_nil(); - m_overrides_options[opt_key]->SetValue(is_checked); + if (field != nullptr) field->toggle(is_checked); - } } } From 3b21c64c2e0d30c8725a4100475008f1c62743e7 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 29 Jul 2019 16:19:32 +0200 Subject: [PATCH 436/627] Fix of "Unable to slice in command line mode: Mixing configurations for FFF and SLA technologies" #2426 --- src/PrusaSlicer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 9983c691b5..b1ba30553e 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -94,7 +94,7 @@ int CLI::run(int argc, char **argv) PrinterTechnology other_printer_technology = get_printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; - } else if (printer_technology != other_printer_technology) { + } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; return 1; } @@ -114,7 +114,7 @@ int CLI::run(int argc, char **argv) PrinterTechnology other_printer_technology = get_printer_technology(m_print_config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; - } else if (printer_technology != other_printer_technology) { + } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; return 1; } From 05325e4f2e29bbeeb1d7674ebecd077a55dc0de8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 29 Jul 2019 16:56:00 +0200 Subject: [PATCH 437/627] Set "wipe_into_infill" and "wipe_into_objects" options to the "Wipe options" category instead of "Extruders" --- src/libslic3r/PrintConfig.cpp | 4 ++-- src/slic3r/GUI/GUI_ObjectList.cpp | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 7d451a4cbc..1c0fa914ec 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2187,7 +2187,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloat(0.)); def = this->add("wipe_into_infill", coBool); - def->category = L("Extruders"); + def->category = L("Wipe options"); def->label = L("Wipe into this object's infill"); def->tooltip = L("Purging after toolchange will done inside this object's infills. " "This lowers the amount of waste but may result in longer print time " @@ -2195,7 +2195,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionBool(false)); def = this->add("wipe_into_objects", coBool); - def->category = L("Extruders"); + def->category = L("Wipe options"); def->label = L("Wipe into this object"); def->tooltip = L("Object will be used to purge the nozzle after a toolchange to save material " "that would otherwise end up in the wipe tower and decrease print time. " diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 32ade56c94..8ae1585b30 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -32,7 +32,7 @@ SettingsBundle FREQ_SETTINGS_BUNDLE_FFF = { L("Support material") , { "support_material", "support_material_auto", "support_material_threshold", "support_material_pattern", "support_material_buildplate_only", "support_material_spacing" } }, - { L("Extruders") , { "wipe_into_infill", "wipe_into_objects" } } + { L("Wipe options") , { "wipe_into_infill", "wipe_into_objects" } } }; // pt_SLA @@ -87,6 +87,7 @@ ObjectList::ObjectList(wxWindow* parent) : CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel"); CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel"); + CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap(nullptr, "funnel"); // CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim"); // CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); @@ -560,6 +561,7 @@ void ObjectList::msw_rescale_icons() CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel"); CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel"); + CATEGORY_ICON[L("Wipe options")] = create_scaled_bitmap(nullptr, "funnel"); // CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim"); // CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time"); CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench"); From 3d9df02f5f02d77646b298bfcc34daeebceb01a6 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 29 Jul 2019 17:55:50 +0200 Subject: [PATCH 438/627] When accessing the localized web pages provided by Prusa Research, only those language codes are now passed to the Prusa Research web server, which are currently supported. For example, there is no web page for "en_UK", the "en_UK" code will be translated to "en_US". --- src/slic3r/GUI/GUI_App.cpp | 29 ++++++++++++++++++++++++++++- src/slic3r/GUI/GUI_App.hpp | 4 +++- src/slic3r/GUI/UpdateDialogs.cpp | 4 ++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 02290e83d9..1a864917dc 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -974,9 +974,36 @@ int GUI_App::extruders_edited_cnt() const preset.config.option("nozzle_diameter")->values.size(); } +wxString GUI_App::current_language_code_safe() const +{ + // Translate the language code to a code, for which Prusa Research maintains translations. + wxString language_code = this->current_language_code(); + size_t idx_underscore = language_code.find(language_code); + if (idx_underscore != wxString::npos) + language_code = language_code.substr(0, idx_underscore); + const std::map mapping { + { "cs", "cs_CZ", }, + { "de", "de_DE", }, + { "es", "es_ES", }, + { "fr", "fr_FR", }, + { "it", "it_IT", }, + { "ja", "ja_JP", }, + { "ko", "ko_KR", }, + { "pl", "pl_PL", }, + { "uk", "uk_UA", }, + { "zh", "zh_CN", }, + }; + auto it = mapping.find(language_code); + if (it != mapping.end()) + language_code = it->second; + else + language_code = "en_US"; + return language_code; +} + void GUI_App::open_web_page_localized(const std::string &http_address) { - wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code()); + wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code_safe()); } void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 8d0bcfe2f1..5c897a7010 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -143,7 +143,9 @@ public: bool checked_tab(Tab* tab); void load_current_presets(); - wxString current_language_code() { return m_wxLocale != nullptr ? m_wxLocale->GetCanonicalName() : wxString("en_US"); } + wxString current_language_code() const { return m_wxLocale != nullptr ? m_wxLocale->GetCanonicalName() : wxString("en_US"); } + // Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US". + wxString current_language_code_safe() const; virtual bool OnExceptionInMainLoop(); diff --git a/src/slic3r/GUI/UpdateDialogs.cpp b/src/slic3r/GUI/UpdateDialogs.cpp index 0f72b3b358..2ea9040075 100644 --- a/src/slic3r/GUI/UpdateDialogs.cpp +++ b/src/slic3r/GUI/UpdateDialogs.cpp @@ -55,7 +55,7 @@ MsgUpdateSlic3r::MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_on auto *link = new wxHyperlinkCtrl(this, wxID_ANY, _(L("Changelog && Download")), url_wx); content_sizer->Add(link); } else { - const auto lang_code = wxGetApp().current_language_code().ToStdString(); + const auto lang_code = wxGetApp().current_language_code_safe().ToStdString(); const std::string url_log = (boost::format(URL_CHANGELOG) % lang_code).str(); const wxString url_log_wx = from_u8(url_log); @@ -100,7 +100,7 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector &updates) : content_sizer->Add(text); content_sizer->AddSpacer(VERT_SPACING); - const auto lang_code = wxGetApp().current_language_code().ToStdString(); + const auto lang_code = wxGetApp().current_language_code_safe().ToStdString(); auto *versions = new wxBoxSizer(wxVERTICAL); for (const auto &update : updates) { From 7bad550c83c744316930560c05212351a96a4bb6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 29 Jul 2019 18:02:48 +0200 Subject: [PATCH 439/627] Fix of #2326 (Annoying warning dialog when set extrusions > 1mm) --- src/slic3r/GUI/Field.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index e84e9637ff..db935cc05d 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -193,17 +193,18 @@ void Field::get_value_by_opt_type(wxString& str) show_error(m_parent, _(L("Invalid numeric input."))); set_value(double_to_string(val), true); } - else if (m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max || - m_opt.sidetext.rfind("mm ") != std::string::npos && val > 1) + else if ((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max || + m_opt.sidetext.rfind("mm ") != std::string::npos && val > 1) && + (m_value.empty() || std::string(str.ToUTF8().data()) != boost::any_cast(m_value))) { - std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm"; - const int nVal = int(val); - wxString msg_text = wxString::Format(_(L("Do you mean %d%% instead of %d %s?\n" - "Select YES if you want to change this value to %d%%, \n" - "or NO if you are sure that %d %s is a correct value.")), nVal, nVal, sidetext, nVal, nVal, sidetext); + const std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm"; + const wxString stVal = double_to_string(val, 2); + const wxString msg_text = wxString::Format(_(L("Do you mean %s%% instead of %s %s?\n" + "Select YES if you want to change this value to %s%%, \n" + "or NO if you are sure that %s %s is a correct value.")), stVal, stVal, sidetext, stVal, stVal, sidetext); auto dialog = new wxMessageDialog(m_parent, msg_text, _(L("Parameter validation")), wxICON_WARNING | wxYES | wxNO); if (dialog->ShowModal() == wxID_YES) { - set_value(wxString::Format("%s%%", str), true); + set_value(wxString::Format("%s%%", stVal), false/*true*/); str += "%%"; } } From 7f33e23fbb2b9e439ab5c896502e78a2ba1eeb16 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 30 Jul 2019 08:39:38 +0200 Subject: [PATCH 440/627] Added new icons for mirroring buttons and 'drop modifier to bed' function --- resources/icons/drop_to_bed.png | Bin 528 -> 0 bytes resources/icons/drop_to_bed.svg | 15 ++++++++++++++ resources/icons/mirroring_off.png | Bin 589 -> 0 bytes resources/icons/mirroring_off.svg | 23 ++++++++++++++++++++++ resources/icons/mirroring_on.png | Bin 600 -> 0 bytes resources/icons/mirroring_on.svg | 23 ++++++++++++++++++++++ src/slic3r/GUI/GUI_ObjectManipulation.cpp | 8 ++++---- 7 files changed, 65 insertions(+), 4 deletions(-) delete mode 100644 resources/icons/drop_to_bed.png create mode 100644 resources/icons/drop_to_bed.svg delete mode 100644 resources/icons/mirroring_off.png create mode 100644 resources/icons/mirroring_off.svg delete mode 100644 resources/icons/mirroring_on.png create mode 100644 resources/icons/mirroring_on.svg diff --git a/resources/icons/drop_to_bed.png b/resources/icons/drop_to_bed.png deleted file mode 100644 index b60e1b137d9d12a1cfc177de1d51eb7c7093aa7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 528 zcmV+r0`L8aP)pZ4MY$`uvwNR!r;okMbKJk8Mr7Sc?B_4 zw3vhk*&h#jJRY=Z9WH}5j=0iAViLGQMuMqqF{$nne8*(H`8hk~-;c&PlNg4nEF!8fo zAB{$(kqK(G+PT~9o+ZhoNVD0z(sliTyuVApvaFM$sJEMv_I2*lK8?hfruj57K`9N$uNw6@pwG@@d=4U;%B$pZI{dCsB#M&HN# z(T$${eQO-sVHtp7jYUy>bJR3V$(^0?{b#F314aL;=?9riril=m#u&2t- + + + + + + + + + diff --git a/resources/icons/mirroring_off.png b/resources/icons/mirroring_off.png deleted file mode 100644 index c16655271a7e10d9d83047189086d4d6b509abc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 589 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S0wixl{&NRXEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZEE?e4>iGH4#PT$HOWYUVmw=D)W4<$2R?NV2M}KA?FdtNTmJp~!15S*|LT8;MoC zXsZ3%W24$-moh2c@9l*fmo+q>Gq62KSXA699k*xgMiDE{G|yhA4QhurtG<}@uu!C_ z<$>}m-=I62B~!k9UHS2l6URxP__|{b{!P~`Som^z%$(k+UE0pWDc5vL>wMPBh1~ac zOgPu;&>~>6W*@_Frwy&D=l0S8q@B+Qm*U`007}-kq7#>;Nm&p5M&yqHoBEQzy zY0CSzuP^(vnT8meSs9vI xnHp;w7+M(^oS(SL7)3*FeoAIqCAtPfD`O*whU@kljsZ0=c)I$ztaD0e0ssxu+3)}W diff --git a/resources/icons/mirroring_off.svg b/resources/icons/mirroring_off.svg new file mode 100644 index 0000000000..b68748e90a --- /dev/null +++ b/resources/icons/mirroring_off.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/mirroring_on.png b/resources/icons/mirroring_on.png deleted file mode 100644 index 6ddeccbe0a2a82019c5c2efd1773c20c0833c4d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 600 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S3?yCqj{O5tEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBC{YpM6XNqfL!5oT`ng2Uv7KO- z`>oK>c;wi#w-Y8-9OfwbtPfPmnB?v5!uX#__a2bLS>O>_%)p?h48n{ROYO^mg6t)p zzOL*KxFrR+#s37PM*)TAdb&7gOf=4WlTK;F^nd^Dc_ijpxxR1T zX#3n&TDl=r(%}>cpt3=meXk`S|u=mmKH$V*x Mp00i_>zopr04OBpdH?_b diff --git a/resources/icons/mirroring_on.svg b/resources/icons/mirroring_on.svg new file mode 100644 index 0000000000..55ea49516c --- /dev/null +++ b/resources/icons/mirroring_on.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 0a2877d922..a4aa3c6d85 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -195,8 +195,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : def.width = field_width - mirror_btn_width;//field_width/*50*/; // Load bitmaps to be used for the mirroring buttons: - m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on.png"); - m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off.png"); + m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on"); + m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off"); m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png"); static const char axes[] = { 'X', 'Y', 'Z' }; @@ -208,7 +208,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // We will add a button to toggle mirroring to each axis: auto mirror_button = [this, mirror_btn_width, axis_idx, label](wxWindow* parent) { wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width); - auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off.png", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label)); m_mirror_buttons[axis_idx].first = btn; @@ -334,7 +334,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : else if (option_name == "Position") { // Add drop to bed button auto drop_to_bed_button = [=](wxWindow* parent) { - auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed.png")); + auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed")); btn->SetToolTip(_(L("Drop to bed"))); m_drop_to_bed_button = btn; auto sizer = new wxBoxSizer(wxHORIZONTAL); From b7d6c93c36800cec7bbbc42f084adfca3d09c269 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 30 Jul 2019 12:06:51 +0200 Subject: [PATCH 441/627] Suppressed editing of overridden options only if "filament_retract_length" == 0 --- src/slic3r/GUI/Tab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index eeb2317ea7..493dad4751 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1579,7 +1579,7 @@ void TabFilament::update_filament_overrides_page() const int extruder_idx = 0; // #ys_FIXME - const bool have_retract_length = m_config->option("filament_retract_length")->is_nil() ? false : + const bool have_retract_length = m_config->option("filament_retract_length")->is_nil() || m_config->opt_float("filament_retract_length", extruder_idx) > 0; for (const std::string& opt_key : opt_keys) From 7c2e199472524c40bbd3f50650c2a0c7345bd4dd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 30 Jul 2019 14:16:07 +0200 Subject: [PATCH 442/627] Try to fix selection of overridden option when TextCtrl is focused Note: the problem was observed only under OSX --- src/slic3r/GUI/Field.cpp | 33 ++++++++++++++++++++++++++++++++- src/slic3r/GUI/Field.hpp | 1 + 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 14386f3810..ca1c57bc5a 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -342,9 +342,40 @@ void TextCtrl::BUILD() { window = dynamic_cast(temp); } +bool TextCtrl::value_was_changed() +{ + if (m_value.empty()) + return true; + + boost::any val = m_value; + wxString ret_str = static_cast(window)->GetValue(); + // update m_value! + get_value_by_opt_type(ret_str); + + switch (m_opt.type) { + case coInt: + return boost::any_cast(m_value) != boost::any_cast(val); + case coPercent: + case coPercents: + case coFloats: + case coFloat: { + if (m_opt.nullable && std::isnan(boost::any_cast(m_value)) && + std::isnan(boost::any_cast(val))) + return false; + return boost::any_cast(m_value) != boost::any_cast(val); + } + case coString: + case coStrings: + case coFloatOrPercent: + return boost::any_cast(m_value) != boost::any_cast(val); + default: + return true; + } +} + void TextCtrl::propagate_value() { - if (is_defined_input_value(window, m_opt.type)) + if (is_defined_input_value(window, m_opt.type) && value_was_changed()) on_change_field(); else on_kill_focus(); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 49ff55d039..6c16f90f27 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -281,6 +281,7 @@ public: ~TextCtrl() {} void BUILD(); + bool value_was_changed(); // Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER void propagate_value(); wxWindow* window {nullptr}; From 320f964847a45c785fcf2f727380b7b830809eb5 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 30 Jul 2019 14:24:42 +0200 Subject: [PATCH 443/627] Fixing zero elevation bug when concave hull overlap was not detected. Backported from tm_perf_optims --- src/libslic3r/MTUtils.hpp | 10 ++++++++++ src/libslic3r/SLA/SLABasePool.cpp | 9 ++++----- src/libslic3r/SLA/SLABasePool.hpp | 3 +++ src/libslic3r/SLA/SLASupportTree.cpp | 20 +++++++++++++++----- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 1fef4b475a..212752883c 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -320,6 +320,9 @@ using FloatingOnly = enable_if_t::value, O>; template using ScaledCoordOnly = enable_if_t::value, O>; +template +using IntegerOnly = enable_if_t::value, O>; + template using ArithmeticOnly = enable_if_t::value, O>; @@ -384,6 +387,13 @@ unscaled(const Eigen::Matrix &v) noexcept return v.template cast() * SCALING_FACTOR; } +template inline std::vector reserve_vector(size_t capacity) +{ + std::vector ret; + ret.reserve(capacity); + return ret; +} + } // namespace Slic3r #endif // MTUTILS_HPP diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 4ce84ba026..53dfef4045 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -591,8 +591,7 @@ inline Point centroid(const Polygon& poly) { /// with explicit bridges. Bridges are generated from each shape's centroid /// to the center of the "scene" which is the centroid calculated from the shape /// centroids (a star is created...) -Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, - ThrowOnCancel throw_on_cancel = [](){}) +Polygons concave_hull(const Polygons& polys, double maxd_mm, ThrowOnCancel thr) { namespace bgi = boost::geometry::index; using SpatElement = std::pair; @@ -600,7 +599,7 @@ Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, if(polys.empty()) return Polygons(); - const double max_dist = scaled(max_dist_mm); + const double max_dist = scaled(maxd_mm); Polygons punion = unify(polys); // could be redundant @@ -624,10 +623,10 @@ Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, idx = 0; std::transform(centroids.begin(), centroids.end(), std::back_inserter(punion), - [¢roids, &ctrindex, cc, max_dist, &idx, throw_on_cancel] + [¢roids, &ctrindex, cc, max_dist, &idx, thr] (const Point& c) { - throw_on_cancel(); + thr(); double dx = x(c) - x(cc), dy = y(c) - y(cc); double l = std::sqrt(dx * dx + dy * dy); double nx = dx / l, ny = dy / l; diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 67b9ccdcb5..eec426bbf5 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -41,6 +41,9 @@ void breakstick_holes(ExPolygon &poly, double stick_width, double penetration = 0.0); +Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, + ThrowOnCancel throw_on_cancel = [](){}); + struct PoolConfig { double min_wall_thickness_mm = 2; double min_wall_height_mm = 5; diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 4751dad6ce..d89836cb99 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -618,15 +618,25 @@ struct Pad { } } + ExPolygons concaveh = offset_ex( + concave_hull(basep, pcfg.max_merge_distance_mm, thr), + scaled(pcfg.min_wall_thickness_mm)); + // Punching the breaksticks across the offsetted polygon perimeters - ExPolygons pad_stickholes; pad_stickholes.reserve(modelbase.size()); + auto pad_stickholes = reserve_vector(modelbase.size()); for(auto& poly : modelbase_offs) { + bool overlap = false; + for (const ExPolygon &p : concaveh) + overlap = overlap || poly.overlaps(p); + + auto bb = poly.contour.bounding_box(); + bb.offset(scaled(pcfg.min_wall_thickness_mm)); + std::vector qres = - bindex.query(poly.contour.bounding_box(), - BoxIndex::qtIntersects); - - if (!qres.empty()) { + bindex.query(bb, BoxIndex::qtIntersects); + + if (!qres.empty() || overlap) { // The model silhouette polygon 'poly' HAS an intersection // with the support silhouettes. Include this polygon From 07608a80cdeed4a99669f0ac291855ecf647f7d9 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 29 Jul 2019 15:08:59 +0200 Subject: [PATCH 444/627] SLA gizmo - making sure the cone direction is correctly undone/redone --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 50 +++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 6 +-- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 1daa576140..267c4f899c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -64,7 +64,7 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S return; } - if (m_model_object != model_object) { + if (m_model_object != model_object || m_model_object_id != model_object->id()) { m_model_object = model_object; m_print_object_idx = -1; } @@ -105,7 +105,8 @@ void GLGizmoSlaSupports::on_render() const // If current m_model_object does not match selection, ask GLCanvas3D to turn us off if (m_state == On && (m_model_object != selection.get_model()->objects[selection.get_object_idx()] - || m_active_instance != selection.get_instance_idx())) { + || m_active_instance != selection.get_instance_idx() + || m_model_object_id != m_model_object->id())) { m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); return; } @@ -390,7 +391,7 @@ bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const bool GLGizmoSlaSupports::is_mesh_update_necessary() const { return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) - && ((m_model_object->id() != m_current_mesh_object_id) || m_its == nullptr); + && ((m_model_object->id() != m_model_object_id) || m_its == nullptr); } @@ -407,7 +408,7 @@ void GLGizmoSlaSupports::update_mesh() m_its = &m_mesh->its; // If this is different mesh than last time or if the AABB tree is uninitialized, recalculate it. - if (m_current_mesh_object_id != m_model_object->id() || (m_AABB.m_left == NULL && m_AABB.m_right == NULL)) + if (m_model_object_id != m_model_object->id() || (m_AABB.m_left == NULL && m_AABB.m_right == NULL)) { m_AABB.deinit(); m_AABB.init( @@ -415,7 +416,7 @@ void GLGizmoSlaSupports::update_mesh() MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3)); } - m_current_mesh_object_id = m_model_object->id(); + m_model_object_id = m_model_object->id(); disable_editing_mode(); } @@ -1080,25 +1081,14 @@ void GLGizmoSlaSupports::on_set_state() { // m_model_object pointer can be invalid (for instance because of undo/redo action), // we should recover it from the object id - const ModelObject* old_model_object = m_model_object; m_model_object = nullptr; for (const auto mo : wxGetApp().model().objects) { - if (mo->id() == m_current_mesh_object_id) { + if (mo->id() == m_model_object_id) { m_model_object = mo; break; } } - - // If ModelObject pointer really changed, invalidate mesh and do everything - // as if the gizmo was switched from Off state - /*if (m_model_object == nullptr || old_model_object != m_model_object) { - m_mesh = nullptr; - m_its = nullptr; - if (m_state == On) - m_old_state = Off; - }*/ - if (m_state == On && m_old_state != On) { // the gizmo was just turned on if (is_mesh_update_necessary()) update_mesh(); @@ -1150,23 +1140,27 @@ void GLGizmoSlaSupports::on_start_dragging() if (m_hover_id != -1) { select_point(NoPoints); select_point(m_hover_id); - m_dragged_point_initial_pos = m_editing_cache[m_hover_id].support_point.pos; + m_point_before_drag = m_editing_cache[m_hover_id]; } + else + m_point_before_drag = CacheEntry(); } void GLGizmoSlaSupports::on_stop_dragging() { - Vec3f backup = m_editing_cache[m_hover_id].support_point.pos; + if (m_hover_id != -1) { + CacheEntry backup = m_editing_cache[m_hover_id]; - if (backup != Vec3f::Zero() // some point was touched - && backup != m_dragged_point_initial_pos) // and it was moved, not just selected - { - m_editing_cache[m_hover_id].support_point.pos = m_dragged_point_initial_pos; - wxGetApp().plater()->take_snapshot(_(L("Move support point"))); - m_editing_cache[m_hover_id].support_point.pos = backup; - m_dragged_point_initial_pos = Vec3f::Zero(); + if (m_point_before_drag.support_point.pos != Vec3f::Zero() // some point was touched + && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected + { + m_editing_cache[m_hover_id] = m_point_before_drag; + wxGetApp().plater()->take_snapshot(_(L("Move support point"))); + m_editing_cache[m_hover_id] = backup; + } } + m_point_before_drag = CacheEntry(); } @@ -1175,7 +1169,7 @@ void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) { ar(m_clipping_plane_distance, m_clipping_plane_normal, - m_current_mesh_object_id, + m_model_object_id, m_new_point_head_diameter, m_normal_cache, m_editing_cache @@ -1188,7 +1182,7 @@ void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const { ar(m_clipping_plane_distance, m_clipping_plane_normal, - m_current_mesh_object_id, + m_model_object_id, m_new_point_head_diameter, m_normal_cache, m_editing_cache diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 889ddf0643..99184a90d9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -28,7 +28,7 @@ class GLGizmoSlaSupports : public GLGizmoBase { private: ModelObject* m_model_object = nullptr; - ObjectID m_current_mesh_object_id = 0; + ObjectID m_model_object_id = 0; int m_active_instance = -1; float m_active_instance_bb_radius; // to cache the bb mutable float m_z_shift = 0.f; @@ -105,8 +105,8 @@ private: bool m_editing_mode = false; // Is editing mode active? bool m_old_editing_state = false; // To keep track of whether the user toggled between the modes (needed for imgui refreshes). float m_new_point_head_diameter; // Size of a new point. - Vec3f m_dragged_point_initial_pos = Vec3f::Zero(); //undo/redo - float m_old_point_head_diameter = 0.f; // undo/redo + CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited + float m_old_point_head_diameter = 0.; // the same float m_minimal_point_distance = 20.f; mutable std::vector m_editing_cache; // a support point and whether it is currently selected std::vector m_normal_cache; // to restore after discarding changes or undo/redo From 66497cdb1f16f72b2538492f79d3929873c4cb7e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 30 Jul 2019 15:45:32 +0200 Subject: [PATCH 445/627] Fixed SPE-993 + Added new icons for layers editing --- resources/icons/edit_layers_all.svg | 41 +++++++++++++++++ resources/icons/edit_layers_some.svg | 69 ++++++++++++++++++++++++++++ src/slic3r/GUI/GUI_ObjectList.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 3 ++ src/slic3r/GUI/wxExtensions.cpp | 4 +- 5 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 resources/icons/edit_layers_all.svg create mode 100644 resources/icons/edit_layers_some.svg diff --git a/resources/icons/edit_layers_all.svg b/resources/icons/edit_layers_all.svg new file mode 100644 index 0000000000..4fccc1388d --- /dev/null +++ b/resources/icons/edit_layers_all.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/edit_layers_some.svg b/resources/icons/edit_layers_some.svg new file mode 100644 index 0000000000..7db56b3f09 --- /dev/null +++ b/resources/icons/edit_layers_some.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 8ae1585b30..a7d4ab16dc 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1293,7 +1293,7 @@ wxMenuItem* ObjectList::append_menu_item_split(wxMenu* menu) wxMenuItem* ObjectList::append_menu_item_layers_editing(wxMenu* menu) { return append_menu_item(menu, wxID_ANY, _(L("Edit Layers")), "", - [this](wxCommandEvent&) { layers_editing(); }, "layers", menu); + [this](wxCommandEvent&) { layers_editing(); }, "edit_layers_all", menu); } wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 31a2ce614c..26bd1116b7 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2186,6 +2186,9 @@ void TabPrinter::extruders_count_changed(size_t extruders_count) m_preset_bundle->update_multi_material_filament_presets(); is_count_changed = true; } + else if (m_extruders_count == 1 && + m_preset_bundle->project_config.option("wiping_volumes_matrix")->values.size()>1) + m_preset_bundle->update_multi_material_filament_presets(); /* This function should be call in any case because of correct updating/rebuilding * of unregular pages of a Printer Settings diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index e5e429a02d..9f36eceb93 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -450,7 +450,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } else if (type == itLayerRoot) { - m_bmp = create_scaled_bitmap(nullptr, "layers"); // FIXME: pass window ptr + m_bmp = create_scaled_bitmap(nullptr, "edit_layers_all"); // FIXME: pass window ptr m_name = _(L("Layers")); } @@ -484,7 +484,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; - m_bmp = create_scaled_bitmap(nullptr, "layers_white"); // FIXME: pass window ptr + m_bmp = create_scaled_bitmap(nullptr, "edit_layers_some"); // FIXME: pass window ptr #ifdef __WXGTK__ // it's necessary on GTK because of control have to know if this item will be container From cc5d74084cb59d23853fb3ddeee754568570ac08 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 30 Jul 2019 17:52:05 +0200 Subject: [PATCH 446/627] Fix memory leak in ProgressStatusBar --- src/slic3r/GUI/ProgressStatusBar.cpp | 28 ++++++++++++++-------------- src/slic3r/GUI/ProgressStatusBar.hpp | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/ProgressStatusBar.cpp b/src/slic3r/GUI/ProgressStatusBar.cpp index f848e663d2..33e4855cdd 100644 --- a/src/slic3r/GUI/ProgressStatusBar.cpp +++ b/src/slic3r/GUI/ProgressStatusBar.cpp @@ -14,20 +14,20 @@ namespace Slic3r { -ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id): - self(new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe, - id == -1? wxID_ANY : id)), - m_timer(new wxTimer(self)), - m_prog (new wxGauge(self, - wxGA_HORIZONTAL, - 100, - wxDefaultPosition, - wxDefaultSize)), - m_cancelbutton(new wxButton(self, - -1, - _(L("Cancel")), - wxDefaultPosition, - wxDefaultSize)) +ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id) + : self{new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe, + id == -1 ? wxID_ANY : id)} + , m_prog{new wxGauge(self, + wxGA_HORIZONTAL, + 100, + wxDefaultPosition, + wxDefaultSize)} + , m_cancelbutton{new wxButton(self, + -1, + _(L("Cancel")), + wxDefaultPosition, + wxDefaultSize)} + , m_timer{new wxTimer(self)} { m_prog->Hide(); m_cancelbutton->Hide(); diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index 413c6ffee5..1aab67d5aa 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -25,9 +25,9 @@ namespace Slic3r { class ProgressStatusBar { wxStatusBar *self; // we cheat! It should be the base class but: perl! - wxTimer *m_timer; wxGauge *m_prog; wxButton *m_cancelbutton; + std::unique_ptr m_timer; public: /// Cancel callback function type From 57008d0d777298d51bcbd9f5006678817c5217d4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 30 Jul 2019 17:54:25 +0200 Subject: [PATCH 447/627] Include SLACommon.hpp into the project. File is reformatted, but only the whitespace is changed. --- src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/SLA/SLACommon.hpp | 59 +++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index ac40e99bcd..4842b51239 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -165,6 +165,7 @@ add_library(libslic3r STATIC MinAreaBoundingBox.cpp miniz_extension.hpp miniz_extension.cpp + SLA/SLACommon.hpp SLA/SLABoilerPlate.hpp SLA/SLABasePool.hpp SLA/SLABasePool.cpp diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index d95397fed5..d8e258035e 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -24,37 +24,51 @@ enum class PointsStatus { UserModified // User has done some edits. }; -struct SupportPoint { +struct SupportPoint +{ Vec3f pos; float head_front_radius; - bool is_new_island; + bool is_new_island; - SupportPoint() : - pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false) {} + SupportPoint() + : pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false) + {} - SupportPoint(float pos_x, float pos_y, float pos_z, float head_radius, bool new_island) : - pos(pos_x, pos_y, pos_z), head_front_radius(head_radius), is_new_island(new_island) {} + SupportPoint(float pos_x, + float pos_y, + float pos_z, + float head_radius, + bool new_island) + : pos(pos_x, pos_y, pos_z) + , head_front_radius(head_radius) + , is_new_island(new_island) + {} - SupportPoint(Vec3f position, float head_radius, bool new_island) : - pos(position), head_front_radius(head_radius), is_new_island(new_island) {} + SupportPoint(Vec3f position, float head_radius, bool new_island) + : pos(position) + , head_front_radius(head_radius) + , is_new_island(new_island) + {} - SupportPoint(Eigen::Matrix data) : - pos(data(0), data(1), data(2)), head_front_radius(data(3)), is_new_island(data(4) != 0.f) {} + SupportPoint(Eigen::Matrix data) + : pos(data(0), data(1), data(2)) + , head_front_radius(data(3)) + , is_new_island(data(4) != 0.f) + {} - bool operator==(const SupportPoint& sp) const { return (pos==sp.pos) && head_front_radius==sp.head_front_radius && is_new_island==sp.is_new_island; } - bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); } + bool operator==(const SupportPoint &sp) const + { + return (pos == sp.pos) && head_front_radius == sp.head_front_radius && + is_new_island == sp.is_new_island; + } + bool operator!=(const SupportPoint &sp) const { return !(sp == (*this)); } - template void serialize(Archive &ar) { ar(pos, head_front_radius, is_new_island); } + template void serialize(Archive &ar) + { + ar(pos, head_front_radius, is_new_island); + } }; -/// An index-triangle structure for libIGL functions. Also serves as an -/// alternative (raw) input format for the SLASupportTree -/*struct EigenMesh3D { - Eigen::MatrixXd V; - Eigen::MatrixXi F; - double ground_level = 0; -};*/ - /// An index-triangle structure for libIGL functions. Also serves as an /// alternative (raw) input format for the SLASupportTree class EigenMesh3D { @@ -161,9 +175,6 @@ public: } }; - - - } // namespace sla } // namespace Slic3r From ca1f3dc6afbe995084f8a95b31284e5c9e8c14c0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 30 Jul 2019 17:55:22 +0200 Subject: [PATCH 448/627] Improved logging of SLA support tree creation. --- src/libslic3r/SLAPrint.cpp | 18 +++++++++++++----- src/libslic3r/SLAPrint.hpp | 15 ++++++++++----- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index b3a075e268..5669307ac7 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -928,12 +928,14 @@ void SLAPrint::process() double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; double init = m_report_status.status(); - ctl.statuscb = [this, d, init](unsigned st, const std::string&) + ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) { double current = init + st * d; if(std::round(m_report_status.status()) < std::round(current)) m_report_status(*this, current, - OBJ_STEP_LABELS(slaposSupportTree)); + OBJ_STEP_LABELS(slaposSupportTree), + SlicingStatus::DEFAULT, + logmsg); }; @@ -1914,11 +1916,17 @@ std::string SLAPrintStatistics::finalize_output_path(const std::string &path_in) return final_path; } -void SLAPrint::StatusReporter::operator()( - SLAPrint &p, double st, const std::string &msg, unsigned flags) +void SLAPrint::StatusReporter::operator()(SLAPrint & p, + double st, + const std::string &msg, + unsigned flags, + const std::string &logmsg) { m_st = st; - BOOST_LOG_TRIVIAL(info) << st << "% " << msg << log_memory_info(); + BOOST_LOG_TRIVIAL(info) + << st << "% " << msg << (logmsg.empty() ? "" : ": ") << logmsg + << log_memory_info(); + p.set_status(int(std::round(st)), msg, flags); } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 9620e9b68d..c4e58ab39b 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -447,16 +447,21 @@ private: // Estimated print time, material consumed. SLAPrintStatistics m_print_statistics; - - class StatusReporter { + + class StatusReporter + { double m_st = 0; + public: - void operator() (SLAPrint& p, double st, const std::string& msg, - unsigned flags = SlicingStatus::DEFAULT); + void operator()(SLAPrint & p, + double st, + const std::string &msg, + unsigned flags = SlicingStatus::DEFAULT, + const std::string &logmsg = ""); + double status() const { return m_st; } } m_report_status; - friend SLAPrintObject; }; From 1ab3268d55b7f8d3d4dcf2030fe4cd2899e947ae Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 30 Jul 2019 17:57:07 +0200 Subject: [PATCH 449/627] Performance optimizations and some cleanup. Optional heavy parallelism which is disabled by default. Would like to test it further in a next release cycle. --- src/libslic3r/SLA/SLASupportTree.cpp | 556 +++++++++++++++------------ src/libslic3r/SLA/SLASupportTree.hpp | 9 +- 2 files changed, 320 insertions(+), 245 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index d89836cb99..6f4ac6c21a 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -83,6 +83,42 @@ const unsigned SupportConfig::max_bridges_on_pillar = 3; using Coordf = double; using Portion = std::tuple; +// Set this to true to enable full parallelism in this module. +// Only the well tested parts will be concurrent if this is set to false. +const constexpr bool USE_FULL_CONCURRENCY = false; + +template struct _ccr {}; + +template<> struct _ccr +{ + using Mutex = SpinMutex; + + template + static inline void enumerate(It from, It to, Fn fn) + { + using TN = size_t; + auto iN = to - from; + TN N = iN < 0 ? 0 : TN(iN); + + tbb::parallel_for(TN(0), N, [from, fn](TN n) { fn(*(from + n), n); }); + } +}; + +template<> struct _ccr +{ + struct Mutex { inline void lock() {} inline void unlock() {} }; + + template + static inline void enumerate(It from, It to, Fn fn) + { + for (auto it = from; it != to; ++it) fn(*it, it - from); + } +}; + +using ccr = _ccr; +using ccr_seq = _ccr; +using ccr_par = _ccr; + inline Portion make_portion(double a, double b) { return std::make_tuple(a, b); } @@ -677,6 +713,7 @@ struct Pad { } tmesh.translate(0, 0, float(zlevel)); + tmesh.require_shared_vertices(); } bool empty() const { return tmesh.facets_count() == 0; } @@ -735,63 +772,84 @@ ClusteredPoints cluster( // The support pad is considered an auxiliary geometry and is not part of the // merged mesh. It can be retrieved using a dedicated method (pad()) class SLASupportTree::Impl { - std::map m_heads; + // For heads it is beneficial to use the same IDs as for the support points. + std::vector m_heads; + std::vector m_head_indices; + std::vector m_pillars; std::vector m_junctions; std::vector m_bridges; std::vector m_compact_bridges; Controller m_ctl; - + Pad m_pad; + + using Mutex = ccr::Mutex; + + mutable Mutex m_mutex; mutable TriangleMesh meshcache; mutable bool meshcache_valid = false; mutable double model_height = 0; // the full height of the model + public: double ground_level = 0; - + Impl() = default; inline Impl(const Controller& ctl): m_ctl(ctl) {} - + const Controller& ctl() const { return m_ctl; } - - template Head& add_head(unsigned id, Args&&... args) { - auto el = m_heads.emplace(std::piecewise_construct, - std::forward_as_tuple(id), - std::forward_as_tuple(std::forward(args)...)); - el.first->second.id = id; + + template Head& add_head(unsigned id, Args&&... args) + { + std::lock_guard lk(m_mutex); + m_heads.emplace_back(std::forward(args)...); + m_heads.back().id = id; + + if (id >= m_head_indices.size()) m_head_indices.resize(id + 1); + m_head_indices[id] = m_heads.size() - 1; + meshcache_valid = false; - return el.first->second; + return m_heads.back(); } - - template Pillar& add_pillar(unsigned headid, Args&&... args) { - auto it = m_heads.find(headid); - assert(it != m_heads.end()); - Head& head = it->second; + + template Pillar& add_pillar(unsigned headid, Args&&... args) + { + std::lock_guard lk(m_mutex); + + assert(headid < m_head_indices.size()); + Head &head = m_heads[m_head_indices[headid]]; + m_pillars.emplace_back(head, std::forward(args)...); Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); head.pillar_id = pillar.id; pillar.start_junction_id = head.id; pillar.starts_from_head = true; + meshcache_valid = false; return m_pillars.back(); } - - void increment_bridges(const Pillar& pillar) { + + void increment_bridges(const Pillar& pillar) + { + std::lock_guard lk(m_mutex); assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); - - if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) + + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) m_pillars[size_t(pillar.id)].bridges++; } - - void increment_links(const Pillar& pillar) { + + void increment_links(const Pillar& pillar) + { + std::lock_guard lk(m_mutex); assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); - + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) - m_pillars[size_t(pillar.id)].links++; + m_pillars[size_t(pillar.id)].links++; } - + template Pillar& add_pillar(Args&&...args) { + std::lock_guard lk(m_mutex); m_pillars.emplace_back(std::forward(args)...); Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); @@ -799,161 +857,175 @@ public: meshcache_valid = false; return m_pillars.back(); } - - const Head& pillar_head(long pillar_id) const { + + const Head& pillar_head(long pillar_id) const + { + std::lock_guard lk(m_mutex); assert(pillar_id >= 0 && pillar_id < long(m_pillars.size())); + const Pillar& p = m_pillars[size_t(pillar_id)]; assert(p.starts_from_head && p.start_junction_id >= 0); - auto it = m_heads.find(unsigned(p.start_junction_id)); - assert(it != m_heads.end()); - return it->second; + assert(size_t(p.start_junction_id) < m_head_indices.size()); + + return m_heads[m_head_indices[p.start_junction_id]]; } - - const Pillar& head_pillar(unsigned headid) const { - auto it = m_heads.find(headid); - assert(it != m_heads.end()); - const Head& h = it->second; + + const Pillar& head_pillar(unsigned headid) const + { + std::lock_guard lk(m_mutex); + assert(headid < m_head_indices.size()); + + const Head& h = m_heads[m_head_indices[headid]]; assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); - return pillar(h.pillar_id); + + return m_pillars[size_t(h.pillar_id)]; } - - template const Junction& add_junction(Args&&... args) { + + template const Junction& add_junction(Args&&... args) + { + std::lock_guard lk(m_mutex); m_junctions.emplace_back(std::forward(args)...); m_junctions.back().id = long(m_junctions.size() - 1); meshcache_valid = false; return m_junctions.back(); } - - template const Bridge& add_bridge(Args&&... args) { + + template const Bridge& add_bridge(Args&&... args) + { + std::lock_guard lk(m_mutex); m_bridges.emplace_back(std::forward(args)...); m_bridges.back().id = long(m_bridges.size() - 1); meshcache_valid = false; return m_bridges.back(); } - - template - const CompactBridge& add_compact_bridge(Args&&...args) { + + template const CompactBridge& add_compact_bridge(Args&&...args) + { + std::lock_guard lk(m_mutex); m_compact_bridges.emplace_back(std::forward(args)...); m_compact_bridges.back().id = long(m_compact_bridges.size() - 1); meshcache_valid = false; return m_compact_bridges.back(); } - - const std::map& heads() const { return m_heads; } - Head& head(unsigned idx) { + + Head &head(unsigned id) + { + std::lock_guard lk(m_mutex); + assert(id < m_head_indices.size()); + meshcache_valid = false; - auto it = m_heads.find(idx); - assert(it != m_heads.end()); - return it->second; + return m_heads[m_head_indices[id]]; } - const std::vector& pillars() const { return m_pillars; } - const std::vector& bridges() const { return m_bridges; } - const std::vector& junctions() const { return m_junctions; } - const std::vector& compact_bridges() const { - return m_compact_bridges; + + inline size_t pillarcount() const { + std::lock_guard lk(m_mutex); + return m_pillars.size(); } - - template inline const Pillar& pillar(T id) const { - static_assert(std::is_integral::value, "Invalid index type"); + + template inline IntegerOnly pillar(T id) const + { + std::lock_guard lk(m_mutex); assert(id >= 0 && size_t(id) < m_pillars.size() && size_t(id) < std::numeric_limits::max()); + return m_pillars[size_t(id)]; } - - const Pad& create_pad(const TriangleMesh& object_supports, - const ExPolygons& modelbase, - const PoolConfig& cfg) { + + const Pad &create_pad(const TriangleMesh &object_supports, + const ExPolygons & modelbase, + const PoolConfig & cfg) + { m_pad = Pad(object_supports, modelbase, ground_level, cfg); return m_pad; } - - void remove_pad() { - m_pad = Pad(); - } - + + void remove_pad() { m_pad = Pad(); } + const Pad& pad() const { return m_pad; } - + // WITHOUT THE PAD!!! - const TriangleMesh& merged_mesh() const { - if(meshcache_valid) return meshcache; - + const TriangleMesh &merged_mesh() const + { + if (meshcache_valid) return meshcache; + Contour3D merged; - - for(auto& headel : heads()) { - if(m_ctl.stopcondition()) break; - if(headel.second.is_valid()) - merged.merge(headel.second.mesh); + + for (auto &head : m_heads) { + if (m_ctl.stopcondition()) break; + if (head.is_valid()) merged.merge(head.mesh); } - - for(auto& stick : pillars()) { - if(m_ctl.stopcondition()) break; + + for (auto &stick : m_pillars) { + if (m_ctl.stopcondition()) break; merged.merge(stick.mesh); merged.merge(stick.base); } - - for(auto& j : junctions()) { - if(m_ctl.stopcondition()) break; + + for (auto &j : m_junctions) { + if (m_ctl.stopcondition()) break; merged.merge(j.mesh); } - - for(auto& cb : compact_bridges()) { - if(m_ctl.stopcondition()) break; + + for (auto &cb : m_compact_bridges) { + if (m_ctl.stopcondition()) break; merged.merge(cb.mesh); } - - for(auto& bs : bridges()) { - if(m_ctl.stopcondition()) break; + + for (auto &bs : m_bridges) { + if (m_ctl.stopcondition()) break; merged.merge(bs.mesh); } - - if(m_ctl.stopcondition()) { + + if (m_ctl.stopcondition()) { // In case of failure we have to return an empty mesh meshcache = TriangleMesh(); return meshcache; } - + meshcache = mesh(merged); - + // The mesh will be passed by const-pointer to TriangleMeshSlicer, // which will need this. if (!meshcache.empty()) meshcache.require_shared_vertices(); - - // TODO: Is this necessary? - //meshcache.repair(); - - BoundingBoxf3&& bb = meshcache.bounding_box(); - model_height = bb.max(Z) - bb.min(Z); - + + BoundingBoxf3 &&bb = meshcache.bounding_box(); + model_height = bb.max(Z) - bb.min(Z); + meshcache_valid = true; return meshcache; } - + // WITH THE PAD - double full_height() const { - if(merged_mesh().empty() && !pad().empty()) + double full_height() const + { + if (merged_mesh().empty() && !pad().empty()) return get_pad_fullheight(pad().cfg); - + double h = mesh_height(); - if(!pad().empty()) h += sla::get_pad_elevation(pad().cfg); + if (!pad().empty()) h += sla::get_pad_elevation(pad().cfg); return h; } - + // WITHOUT THE PAD!!! - double mesh_height() const { - if(!meshcache_valid) merged_mesh(); + double mesh_height() const + { + if (!meshcache_valid) merged_mesh(); return model_height; } // Intended to be called after the generation is fully complete - void clear_support_data() { + void merge_and_cleanup() + { merged_mesh(); // in case the mesh is not generated, it should be... - m_heads.clear(); - m_pillars.clear(); - m_junctions.clear(); - m_bridges.clear(); - m_compact_bridges.clear(); + + // Doing clear() does not garantee to release the memory. + m_heads = {}; + m_head_indices = {}; + m_pillars = {}; + m_junctions = {}; + m_bridges = {}; + m_compact_bridges = {}; } - }; // This function returns the position of the centroid in the input 'clust' @@ -1122,11 +1194,10 @@ class SLASupportTree::Algorithm { // Now a and b vectors are perpendicular to v and to each other. // Together they define the plane where we have to iterate with the // given angles in the 'phis' vector - tbb::parallel_for(size_t(0), phis.size(), - [&phis, &hits, &m, sd, r_pin, r_back, s, a, b, c] - (size_t i) + ccr_par::enumerate(phis.begin(), phis.end(), + [&hits, &m, sd, r_pin, r_back, s, a, b, c] + (double phi, size_t i) { - double& phi = phis[i]; double sinphi = std::sin(phi); double cosphi = std::cos(phi); @@ -1225,12 +1296,11 @@ class SLASupportTree::Algorithm { // Hit results std::array hits; - - tbb::parallel_for(size_t(0), phis.size(), - [&m, &phis, a, b, sd, dir, r, s, ins_check, &hits] - (size_t i) + + ccr_par::enumerate(phis.begin(), phis.end(), + [&m, a, b, sd, dir, r, s, ins_check, &hits] + (double phi, size_t i) { - double& phi = phis[i]; double sinphi = std::sin(phi); double cosphi = std::cos(phi); @@ -1458,7 +1528,7 @@ class SLASupportTree::Algorithm { if(nearest_id >= 0) { auto nearpillarID = unsigned(nearest_id); - if(nearpillarID < m_result.pillars().size()) { + if(nearpillarID < m_result.pillarcount()) { if(!connect_to_nearpillar(head, nearpillarID)) { nearest_id = -1; // continue searching spindex.remove(ne); // without the current pillar @@ -1646,46 +1716,52 @@ public: using libnest2d::opt::initvals; using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::StopCriteria; - - for(unsigned i = 0, fidx = 0; i < filtered_indices.size(); ++i) + + ccr::Mutex mutex; + auto addfn = [&mutex](PtIndices &container, unsigned val) { + std::lock_guard lk(mutex); + container.emplace_back(val); + }; + + ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), + [this, &nmls, addfn](unsigned fidx, size_t i) { m_thr(); - - fidx = filtered_indices[i]; + auto n = nmls.row(i); - + // for all normals we generate the spherical coordinates and // saturate the polar angle to 45 degrees from the bottom then // convert back to standard coordinates to get the new normal. // Then we just create a quaternion from the two normals // (Quaternion::FromTwoVectors) and apply the rotation to the // arrow head. - + double z = n(2); double r = 1.0; // for normalized vector double polar = std::acos(z / r); double azimuth = std::atan2(n(1), n(0)); - + // skip if the tilt is not sane if(polar >= PI - m_cfg.normal_cutoff_angle) { - + // We saturate the polar angle to 3pi/4 polar = std::max(polar, 3*PI / 4); - + // save the head (pinpoint) position Vec3d hp = m_points.row(fidx); - + double w = m_cfg.head_width_mm + m_cfg.head_back_radius_mm + 2*m_cfg.head_front_radius_mm; - + double pin_r = double(m_support_pts[fidx].head_front_radius); - + // Reassemble the now corrected normal auto nn = Vec3d(std::cos(azimuth) * std::sin(polar), std::sin(azimuth) * std::sin(polar), std::cos(polar)).normalized(); - + // check available distance EigenMesh3D::hit_result t = pinhead_mesh_intersect(hp, // touching point @@ -1693,37 +1769,37 @@ public: pin_r, m_cfg.head_back_radius_mm, w); - + if(t.distance() <= w) { - + // Let's try to optimize this angle, there might be a // viable normal that doesn't collide with the model // geometry and its very close to the default. - + StopCriteria stc; stc.max_iterations = m_cfg.optimizer_max_iterations; stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; stc.stop_score = w; // space greater than w is enough GeneticOptimizer solver(stc); solver.seed(0); // we want deterministic behavior - + auto oresult = solver.optimize_max( [this, pin_r, w, hp](double plr, double azm) - { - auto n = Vec3d(std::cos(azm) * std::sin(plr), - std::sin(azm) * std::sin(plr), - std::cos(plr)).normalized(); + { + auto n = Vec3d(std::cos(azm) * std::sin(plr), + std::sin(azm) * std::sin(plr), + std::cos(plr)).normalized(); - double score = pinhead_mesh_intersect( hp, n, pin_r, - m_cfg.head_back_radius_mm, w); - - return score; - }, - initvals(polar, azimuth), // start with what we have - bound(3*PI/4, PI), // Must not exceed the tilt limit - bound(-PI, PI) // azimuth can be a full search - ); + double score = pinhead_mesh_intersect( + hp, n, pin_r, m_cfg.head_back_radius_mm, w); + return score; + }, + initvals(polar, azimuth), // start with what we have + bound(3*PI/4, PI), // Must not exceed the tilt limit + bound(-PI, PI) // azimuth can be a full search + ); + if(oresult.score > w) { polar = std::get<0>(oresult.optimum); azimuth = std::get<1>(oresult.optimum); @@ -1733,25 +1809,25 @@ public: t = oresult.score; } } - + // save the verified and corrected normal m_support_nmls.row(fidx) = nn; - + if (t.distance() > w) { // Check distance from ground, we might have zero elevation. if (hp(Z) + w * nn(Z) < m_result.ground_level) { - m_iheadless.emplace_back(fidx); + addfn(m_iheadless, fidx); } else { // mark the point for needing a head. - m_iheads.emplace_back(fidx); + addfn(m_iheads, fidx); } } else if (polar >= 3 * PI / 4) { // Headless supports do not tilt like the headed ones // so the normal should point almost to the ground. - m_iheadless.emplace_back(fidx); + addfn(m_iheadless, fidx); } } - } + }); m_thr(); } @@ -1939,11 +2015,17 @@ public: }; std::vector modelpillars; + ccr::Mutex mutex; // TODO: connect these to the ground pillars if possible - for(auto item : m_iheads_onmodel) { m_thr(); - unsigned idx = item.first; - EigenMesh3D::hit_result hit = item.second; + ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), + [this, routedown, &modelpillars, &mutex] + (const std::pair &el, + size_t) + { + m_thr(); + unsigned idx = el.first; + EigenMesh3D::hit_result hit = el.second; auto& head = m_result.head(idx); Vec3d hjp = head.junction_point(); @@ -1952,7 +2034,7 @@ public: // Search nearby pillar // ///////////////////////////////////////////////////////////////// - if(search_pillar_and_connect(head)) { head.transform(); continue; } + if(search_pillar_and_connect(head)) { head.transform(); return; } // ///////////////////////////////////////////////////////////////// // Try straight path @@ -1974,7 +2056,7 @@ public: } if(std::isinf(tdown)) { // we heave found a route to the ground - routedown(head, head.dir, d); continue; + routedown(head, head.dir, d); return; } // ///////////////////////////////////////////////////////////////// @@ -2036,7 +2118,7 @@ public: } if(std::isinf(tdown)) { // we heave found a route to the ground - routedown(head, bridgedir, d); continue; + routedown(head, bridgedir, d); return; } // ///////////////////////////////////////////////////////////////// @@ -2079,8 +2161,9 @@ public: pill.base = tailhead.mesh; // Experimental: add the pillar to the index for cascading + std::lock_guard lk(mutex); modelpillars.emplace_back(unsigned(pill.id)); - continue; + return; } // We have failed to route this head. @@ -2088,7 +2171,7 @@ public: << "Failed to route model facing support point." << " ID: " << idx; head.invalidate(); - } + }); for(auto pillid : modelpillars) { auto& pillar = m_result.pillar(pillid); @@ -2175,8 +2258,8 @@ public: // Search for the pair amongst the remembered pairs if(pairs.find(hashval) != pairs.end()) continue; - - const Pillar& neighborpillar = m_result.pillars()[re.second]; + + const Pillar& neighborpillar = m_result.pillar(re.second); // this neighbor is occupied, skip if(neighborpillar.links >= neighbors) continue; @@ -2212,7 +2295,7 @@ public: // lonely pillars. One or even two additional pillar might get inserted // depending on the length of the lonely pillar. - size_t pillarcount = m_result.pillars().size(); + size_t pillarcount = m_result.pillarcount(); // Again, go through all pillars, this time in the whole support tree // not just the index. @@ -2364,6 +2447,8 @@ public: m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist)); } } + + void merge_result() { m_result.merge_and_cleanup(); } }; bool SLASupportTree::generate(const std::vector &support_points, @@ -2372,9 +2457,9 @@ bool SLASupportTree::generate(const std::vector &support_points, const Controller &ctl) { if(support_points.empty()) return false; - + Algorithm alg(cfg, mesh, support_points, *m_impl, ctl.cancelfn); - + // Let's define the individual steps of the processing. We can experiment // later with the ordering and the dependencies between them. enum Steps { @@ -2386,55 +2471,58 @@ bool SLASupportTree::generate(const std::vector &support_points, ROUTING_NONGROUND, CASCADE_PILLARS, HEADLESS, + MERGE_RESULT, DONE, ABORT, NUM_STEPS //... }; - + // Collect the algorithm steps into a nice sequence std::array, NUM_STEPS> program = { [] () { // Begin... // Potentially clear up the shared data (not needed for now) }, - + std::bind(&Algorithm::filter, &alg), - + std::bind(&Algorithm::add_pinheads, &alg), - + std::bind(&Algorithm::classify, &alg), - + std::bind(&Algorithm::routing_to_ground, &alg), - + std::bind(&Algorithm::routing_to_model, &alg), - + std::bind(&Algorithm::interconnect_pillars, &alg), - + std::bind(&Algorithm::routing_headless, &alg), - + + std::bind(&Algorithm::merge_result, &alg), + [] () { // Done }, - + [] () { // Abort } }; - + Steps pc = BEGIN; - + if(cfg.ground_facing_only) { program[ROUTING_NONGROUND] = []() { BOOST_LOG_TRIVIAL(info) - << "Skipping model-facing supports as requested."; + << "Skipping model-facing supports as requested."; }; program[HEADLESS] = []() { BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as" " requested."; }; } - + // Let's define a simple automaton that will run our program. auto progress = [&ctl, &pc] () { static const std::array stepstr { @@ -2446,10 +2534,11 @@ bool SLASupportTree::generate(const std::vector &support_points, "Routing supports to model surface", "Interconnecting pillars", "Processing small holes", + "Merging support mesh", "Done", "Abort" }; - + static const std::array stepstate { 0, 10, @@ -2458,13 +2547,14 @@ bool SLASupportTree::generate(const std::vector &support_points, 60, 70, 80, - 90, + 85, + 99, 100, 0 }; - + if(ctl.stopcondition()) pc = ABORT; - + switch(pc) { case BEGIN: pc = FILTER; break; case FILTER: pc = PINHEADS; break; @@ -2473,20 +2563,22 @@ bool SLASupportTree::generate(const std::vector &support_points, case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break; case CASCADE_PILLARS: pc = HEADLESS; break; - case HEADLESS: pc = DONE; break; + case HEADLESS: pc = MERGE_RESULT; break; + case MERGE_RESULT: pc = DONE; break; case DONE: case ABORT: break; default: ; } + ctl.statuscb(stepstate[pc], stepstr[pc]); }; - + // Just here we run the computation... while(pc < DONE) { progress(); program[pc](); } - + return pc == ABORT; } @@ -2504,44 +2596,40 @@ void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { outmesh.merge(get_pad()); } -std::vector SLASupportTree::slice(float layerh, float init_layerh) const +std::vector SLASupportTree::slice( + const std::vector &heights, float cr) const { - if(init_layerh < 0) init_layerh = layerh; - auto& stree = get(); - - const auto modelh = float(stree.full_height()); - auto gndlvl = float(this->m_impl->ground_level); - const Pad& pad = m_impl->pad(); - if(!pad.empty()) gndlvl -= float(get_pad_elevation(pad.cfg)); - - std::vector heights; - heights.reserve(size_t(modelh/layerh) + 1); - - for(float h = gndlvl + init_layerh; h < gndlvl + modelh; h += layerh) { - heights.emplace_back(h); + const TriangleMesh &sup_mesh = m_impl->merged_mesh(); + const TriangleMesh &pad_mesh = get_pad(); + + std::vector sup_slices; + if (!sup_mesh.empty()) { + TriangleMeshSlicer sup_slicer(&sup_mesh); + sup_slicer.slice(heights, cr, &sup_slices, m_impl->ctl().cancelfn); } - - TriangleMesh fullmesh = m_impl->merged_mesh(); - fullmesh.merge(get_pad()); - if (!fullmesh.empty()) fullmesh.require_shared_vertices(); - TriangleMeshSlicer slicer(&fullmesh); - std::vector ret; - slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn); - - return ret; -} - -std::vector SLASupportTree::slice(const std::vector &heights, - float cr) const -{ - TriangleMesh fullmesh = m_impl->merged_mesh(); - fullmesh.merge(get_pad()); - if (!fullmesh.empty()) fullmesh.require_shared_vertices(); - TriangleMeshSlicer slicer(&fullmesh); - std::vector ret; - slicer.slice(heights, cr, &ret, get().ctl().cancelfn); - - return ret; + + auto bb = pad_mesh.bounding_box(); + auto maxzit = std::upper_bound(heights.begin(), heights.end(), bb.max.z()); + + auto padgrid = reserve_vector(heights.end() - maxzit); + std::copy(heights.begin(), maxzit, std::back_inserter(padgrid)); + + std::vector pad_slices; + if (!pad_mesh.empty()) { + TriangleMeshSlicer pad_slicer(&pad_mesh); + pad_slicer.slice(padgrid, cr, &pad_slices, m_impl->ctl().cancelfn); + } + + size_t len = std::min(heights.size(), pad_slices.size()); + len = std::min(len, sup_slices.size()); + + for (size_t i = 0; i < len; ++i) { + std::copy(pad_slices[i].begin(), pad_slices[i].end(), + std::back_inserter(sup_slices[i])); + pad_slices[i] = {}; + } + + return sup_slices; } const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase, @@ -2568,16 +2656,6 @@ SLASupportTree::SLASupportTree(const std::vector &points, { m_impl->ground_level = emesh.ground_level() - cfg.object_elevation_mm; generate(points, emesh, cfg, ctl); - m_impl->clear_support_data(); -} - -SLASupportTree::SLASupportTree(const SLASupportTree &c): - m_impl(new Impl(*c.m_impl)) {} - -SLASupportTree &SLASupportTree::operator=(const SLASupportTree &c) -{ - m_impl = make_unique(*c.m_impl); - return *this; } SLASupportTree::~SLASupportTree() {} diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 8602d8a46d..d7f15c17bd 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -171,9 +171,9 @@ public: const EigenMesh3D& em, const SupportConfig& cfg = {}, const Controller& ctl = {}); - - SLASupportTree(const SLASupportTree&); - SLASupportTree& operator=(const SLASupportTree&); + + SLASupportTree(const SLASupportTree&) = delete; + SLASupportTree& operator=(const SLASupportTree&) = delete; ~SLASupportTree(); @@ -183,9 +183,6 @@ public: void merged_mesh_with_pad(TriangleMesh&) const; - /// Get the sliced 2d layers of the support geometry. - std::vector slice(float layerh, float init_layerh = -1.0) const; - std::vector slice(const std::vector &, float closing_radius) const; From bdd694ddcb28665c2182690e1c774cd09c40334e Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 31 Jul 2019 08:36:08 +0200 Subject: [PATCH 450/627] Added member bool printable to ModelObject and ModelInstance --- src/libslic3r/Model.cpp | 3 +++ src/libslic3r/Model.hpp | 53 +++++++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 858ae52b29..97d045ae05 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -644,6 +644,9 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->sla_points_status = rhs.sla_points_status; this->layer_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment this->layer_height_profile = rhs.layer_height_profile; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + this->printable = rhs.printable; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ this->origin_translation = rhs.origin_translation; m_bounding_box = rhs.m_bounding_box; m_bounding_box_valid = rhs.m_bounding_box_valid; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a3281e5222..18e3f8fb63 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -192,6 +192,10 @@ public: // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. // The pairs of are packed into a 1D array. std::vector layer_height_profile; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // Whether or not this object is printable + bool printable; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // This vector holds position of selected support points for SLA. The data are // saved in mesh coordinates to allow using them for several instances. @@ -304,11 +308,17 @@ public: private: friend class Model; // This constructor assigns new ID to this ModelObject and its config. - explicit ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()), +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()), +// explicit ModelObject(Model* model) : m_model(model), origin_translation(Vec3d::Zero()), +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) - { assert(this->id().valid()); } - explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) - { assert(this->id().invalid()); assert(this->config.id().invalid()); } + { assert(this->id().valid()); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) +// explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + { assert(this->id().invalid()); assert(this->config.id().invalid()); } ~ModelObject(); void assign_new_unique_ids_recursive() override; @@ -370,8 +380,11 @@ private: template void serialize(Archive &ar) { ar(cereal::base_class(this)); Internal::StaticSerializationWrapper config_wrapper(config); - ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation, - m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, printable, origin_translation, +// ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation, +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); } }; @@ -595,6 +608,10 @@ private: public: // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) EPrintVolumeState print_volume_state; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // Whether or not this instance is printable + bool printable; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ModelObject* get_object() const { return this->object; } @@ -639,8 +656,11 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - bool is_printable() const { return print_volume_state == PVS_Inside; } - +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + bool is_printable() const { return printable && (print_volume_state == PVS_Inside); } +// bool is_printable() const { return print_volume_state == PVS_Inside; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // Getting the input polygon for arrange arrangement::ArrangePolygon get_arrange_polygon() const; @@ -667,10 +687,16 @@ private: ModelObject* object; // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject *object) : print_volume_state(PVS_Inside), object(object) { assert(this->id().valid()); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + explicit ModelInstance(ModelObject* object) : print_volume_state(PVS_Inside), printable(true), object(object) { assert(this->id().valid()); } +// explicit ModelInstance(ModelObject* object) : print_volume_state(PVS_Inside), object(object) { assert(this->id().valid()); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Constructor, which assigns a new unique ID. explicit ModelInstance(ModelObject *object, const ModelInstance &other) : - m_transformation(other.m_transformation), print_volume_state(PVS_Inside), object(object) { assert(this->id().valid() && this->id() != other.id()); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + m_transformation(other.m_transformation), print_volume_state(PVS_Inside), printable(true), object(object) {assert(this->id().valid() && this->id() != other.id());} +// m_transformation(other.m_transformation), print_volume_state(PVS_Inside), object(object) { assert(this->id().valid() && this->id() != other.id()); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ explicit ModelInstance(ModelInstance &&rhs) = delete; ModelInstance& operator=(const ModelInstance &rhs) = delete; @@ -681,8 +707,11 @@ private: // Used for deserialization, therefore no IDs are allocated. ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); } template void serialize(Archive &ar) { - ar(m_transformation, print_volume_state); - } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + ar(m_transformation, print_volume_state, printable); +// ar(m_transformation, print_volume_state); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + } }; class ModelWipeTower final : public ObjectBase From 7746825ab4c5c25c949ff8edd7631585d40e131f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 31 Jul 2019 09:46:45 +0200 Subject: [PATCH 451/627] Temporary commented copy/paste for Layers --- src/slic3r/GUI/GUI_ObjectList.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a7d4ab16dc..a3ddd9498a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -798,17 +798,19 @@ void ObjectList::show_context_menu() void ObjectList::copy() { - if (m_selection_mode & smLayer) - fill_layer_config_ranges_cache(); - else + // if (m_selection_mode & smLayer) + // fill_layer_config_ranges_cache(); + // else { + // m_layer_config_ranges_cache.clear(); wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); + // } } void ObjectList::paste() { - if (!m_layer_config_ranges_cache.empty()) - paste_layers_into_list(); - else + // if (!m_layer_config_ranges_cache.empty()) + // paste_layers_into_list(); + // else wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); } @@ -2927,6 +2929,7 @@ void ObjectList::select_item_all_children() // update selection mode for non-multiple selection void ObjectList::update_selection_mode() { + m_selected_layers_range_idx=-1; // All items are unselected if (!GetSelection()) { From b1a1ed63945971ac1b97c049a5f80173b1cf426d Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 31 Jul 2019 10:12:13 +0200 Subject: [PATCH 452/627] Toggle instance printable member by 3D scene context menu --- src/slic3r/GUI/GUI_ObjectList.cpp | 15 +++++++++++++++ src/slic3r/GUI/GUI_ObjectList.hpp | 3 +++ src/slic3r/GUI/Plater.cpp | 15 +++++++++++++++ src/slic3r/GUI/wxExtensions.cpp | 20 ++++++++++++++++++++ src/slic3r/GUI/wxExtensions.hpp | 5 +++++ 5 files changed, 58 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a7d4ab16dc..2a6dd6a195 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1378,6 +1378,21 @@ wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu, wxWind [this](wxCommandEvent&) { split_instances(); }, "", menu, [](){return wxGetApp().plater()->can_set_instance_to_object(); }, parent); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +wxMenuItem* ObjectList::append_menu_item_printable(wxMenu* menu, wxWindow* parent) +{ + return append_menu_check_item(menu, wxID_ANY, _(L("Printable")), "", [this](wxCommandEvent&) { + int instance_idx = wxGetApp().plater()->canvas3D()->get_selection().get_instance_idx(); + if (instance_idx != -1) + { + int obj_idx = wxGetApp().plater()->get_selected_object_idx(); + (*m_objects)[obj_idx]->instances[instance_idx]->printable = !(*m_objects)[obj_idx]->instances[instance_idx]->printable; + + } + }, menu); +} +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + void ObjectList::append_menu_items_osx(wxMenu* menu) { append_menu_item(menu, wxID_ANY, _(L("Rename")), "", diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 39558d1c54..9802adddfc 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -225,6 +225,9 @@ public: wxMenuItem* append_menu_item_settings(wxMenu* menu); wxMenuItem* append_menu_item_change_type(wxMenu* menu); wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxMenuItem* append_menu_item_printable(wxMenu* menu, wxWindow* parent); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void append_menu_items_osx(wxMenu* menu); wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); void append_menu_item_export_stl(wxMenu* menu) const ; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 38ad580374..a0995c8921 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3490,6 +3490,11 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ sidebar->obj_list()->append_menu_item_instance_to_object(menu, q); menu->AppendSeparator(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + wxMenuItem* menu_item_printable = sidebar->obj_list()->append_menu_item_printable(menu, q); + menu->AppendSeparator(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + append_menu_item(menu, wxID_ANY, _(L("Reload from Disk")), _(L("Reload the selected file from Disk")), [this](wxCommandEvent&) { reload_from_disk(); }); @@ -3497,6 +3502,16 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ [this](wxCommandEvent&) { q->export_stl(false, true); }); menu->AppendSeparator(); + +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { + const Selection& selection = get_selection(); + int instance_idx = selection.get_instance_idx(); + evt.Enable(instance_idx != -1); + if (instance_idx != -1) + evt.Check(model.objects[selection.get_object_idx()]->instances[instance_idx]->printable); + }, menu_item_printable->GetId()); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 9f36eceb93..e707a74768 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -157,6 +157,26 @@ wxMenuItem* append_menu_radio_item(wxMenu* menu, int id, const wxString& string, return item; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string, const wxString& description, + std::function cb, wxEvtHandler* event_handler) +{ + if (id == wxID_ANY) + id = wxNewId(); + + wxMenuItem* item = menu->AppendCheckItem(id, string, description); + +#ifdef __WXMSW__ + if (event_handler != nullptr && event_handler != menu) + event_handler->Bind(wxEVT_MENU, cb, id); + else +#endif // __WXMSW__ + menu->Bind(wxEVT_MENU, cb, id); + + return item; +} +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200; const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200; const unsigned int wxCheckListBoxComboPopup::DefaultItemHeight = 18; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index d7d5fcac21..56349f9e21 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -43,6 +43,11 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin wxMenuItem* append_menu_radio_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, wxEvtHandler* event_handler); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string, const wxString& description, + std::function cb, wxEvtHandler* event_handler); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + class wxDialog; void edit_tooltip(wxString& tooltip); void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector& btn_ids); From 99f34f8321a063ec7a051ab769702a4401b92f5a Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 31 Jul 2019 10:18:49 +0200 Subject: [PATCH 453/627] SLA gizmo: fixed a crash with multiple selection --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 267c4f899c..3ec268af9b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -59,7 +59,7 @@ bool GLGizmoSlaSupports::on_init() void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const Selection& selection) { - if (selection.is_empty()) { + if (! model_object || selection.is_empty()) { m_model_object = nullptr; return; } From ab0d1af3ca798dcd9e203b70609d692c44e85ee5 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 31 Jul 2019 10:33:03 +0200 Subject: [PATCH 454/627] Fixed a problem with deleting button in SLA gizmo --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 3ec268af9b..8929bd5dcb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -1031,13 +1031,18 @@ RENDER_AGAIN: if (remove_selected || remove_all) { force_refresh = false; m_parent.set_as_dirty(); + bool was_in_editing = m_editing_mode; + if (! was_in_editing) + switch_to_editing_mode(); if (remove_all) { - if (!m_editing_mode) - switch_to_editing_mode(); select_point(AllPoints); delete_selected_points(true); // true - delete regardless of locked status - editing_mode_apply_changes(); } + if (remove_selected) + delete_selected_points(false); // leave locked points + if (! was_in_editing) + editing_mode_apply_changes(); + if (first_run) { first_run = false; goto RENDER_AGAIN; @@ -1172,7 +1177,8 @@ void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) m_model_object_id, m_new_point_head_diameter, m_normal_cache, - m_editing_cache + m_editing_cache, + m_selection_empty ); } @@ -1185,7 +1191,8 @@ void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const m_model_object_id, m_new_point_head_diameter, m_normal_cache, - m_editing_cache + m_editing_cache, + m_selection_empty ); } From 03820a38cf4c551d29c22b854a769373f5492fa1 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 31 Jul 2019 11:01:50 +0200 Subject: [PATCH 455/627] Render non printable instances with a darker color --- src/slic3r/GUI/3DScene.cpp | 19 +++++++++++++++++++ src/slic3r/GUI/3DScene.hpp | 7 +++++++ src/slic3r/GUI/GUI_ObjectList.cpp | 15 ++++++++------- src/slic3r/GUI/Plater.cpp | 3 +++ src/slic3r/GUI/Selection.cpp | 26 ++++++++++++++++++++++++++ src/slic3r/GUI/Selection.hpp | 4 ++++ 6 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index dba5958466..d27d4a78cd 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -200,6 +200,9 @@ const float GLVolume::HOVER_DESELECT_COLOR[4] = { 1.0f, 0.75f, 0.75f, 1.0f }; const float GLVolume::OUTSIDE_COLOR[4] = { 0.0f, 0.38f, 0.8f, 1.0f }; const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f }; const float GLVolume::DISABLED_COLOR[4] = { 0.25f, 0.25f, 0.25f, 1.0f }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +const float GLVolume::NON_PRINTABLE_COLOR[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const float GLVolume::MODEL_COLOR[4][4] = { { 1.0f, 1.0f, 0.0f, 1.f }, { 1.0f, 0.5f, 0.5f, 1.f }, @@ -218,6 +221,9 @@ GLVolume::GLVolume(float r, float g, float b, float a) , extruder_id(0) , selected(false) , disabled(false) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + , printable(true) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ , is_active(true) , zoom_to_volumes(true) , shader_outside_printer_detection_enabled(false) @@ -271,10 +277,23 @@ void GLVolume::set_render_color() set_render_color(DISABLED_COLOR, 4); else if (is_outside && shader_outside_printer_detection_enabled) set_render_color(OUTSIDE_COLOR, 4); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +// else if (!printable) +// set_render_color(NON_PRINTABLE_COLOR, 4); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ else set_render_color(color, 4); } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + if (!printable) + { + render_color[0] /= 4; + render_color[1] /= 4; + render_color[2] /= 4; + } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + if (force_transparent) render_color[3] = color[3]; } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 8ae57eeaea..723c555019 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -231,6 +231,9 @@ public: static const float OUTSIDE_COLOR[4]; static const float SELECTED_OUTSIDE_COLOR[4]; static const float DISABLED_COLOR[4]; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + static const float NON_PRINTABLE_COLOR[4]; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static const float MODEL_COLOR[4][4]; static const float SLA_SUPPORT_COLOR[4]; static const float SLA_PAD_COLOR[4]; @@ -294,6 +297,10 @@ public: bool selected; // Is this object disabled from selection? bool disabled; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + // Is this object printable? + bool printable; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Whether or not this volume is active for rendering bool is_active; // Whether or not to use this volume when applying zoom_to_volumes() diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 2a6dd6a195..58e96e4bce 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1382,13 +1382,14 @@ wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu, wxWind wxMenuItem* ObjectList::append_menu_item_printable(wxMenu* menu, wxWindow* parent) { return append_menu_check_item(menu, wxID_ANY, _(L("Printable")), "", [this](wxCommandEvent&) { - int instance_idx = wxGetApp().plater()->canvas3D()->get_selection().get_instance_idx(); - if (instance_idx != -1) - { - int obj_idx = wxGetApp().plater()->get_selected_object_idx(); - (*m_objects)[obj_idx]->instances[instance_idx]->printable = !(*m_objects)[obj_idx]->instances[instance_idx]->printable; - - } + wxGetApp().plater()->canvas3D()->get_selection().toggle_instance_printable_state(); +// int instance_idx = wxGetApp().plater()->canvas3D()->get_selection().get_instance_idx(); +// if (instance_idx != -1) +// { +// int obj_idx = wxGetApp().plater()->get_selected_object_idx(); +// (*m_objects)[obj_idx]->instances[instance_idx]->printable = !(*m_objects)[obj_idx]->instances[instance_idx]->printable; +// +// } }, menu); } //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a0995c8921..50c6f5b67c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3509,7 +3509,10 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ int instance_idx = selection.get_instance_idx(); evt.Enable(instance_idx != -1); if (instance_idx != -1) + { evt.Check(model.objects[selection.get_object_idx()]->instances[instance_idx]->printable); + view3D->set_as_dirty(); + } }, menu_item_printable->GetId()); //@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 211863627f..4db126b80a 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1459,6 +1459,32 @@ std::vector Selection::get_unselected_volume_idxs_from(const std:: return idxs; } +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +void Selection::toggle_instance_printable_state() +{ + int instance_idx = get_instance_idx(); + if (instance_idx == -1) + return; + + int obj_idx = get_object_idx(); + if ((0 <= obj_idx) && (obj_idx < (int)m_model->objects.size())) + { + ModelObject* model_object = m_model->objects[obj_idx]; + if ((0 <= instance_idx) && (instance_idx < (int)model_object->instances.size())) + { + ModelInstance* instance = model_object->instances[instance_idx]; + instance->printable = !instance->printable; + + for (GLVolume* volume : *m_volumes) + { + if ((volume->object_idx() == obj_idx) && (volume->instance_idx() == instance_idx)) + volume->printable = instance->printable; + } + } + } +} +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + void Selection::update_valid() { m_valid = (m_volumes != nullptr) && (m_model != nullptr); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 0f71cefc44..94fa569099 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -336,6 +336,10 @@ public: // returns the list of idxs of the volumes contained in the given list but not in the selection std::vector get_unselected_volume_idxs_from(const std::vector& volume_idxs) const; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + void toggle_instance_printable_state(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + private: void update_valid(); void update_type(); From 0647d3ac1ed5a60e97cfee23f4edb5a402cba6b3 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 31 Jul 2019 11:12:50 +0200 Subject: [PATCH 456/627] Code cleanup --- src/libslic3r/Model.cpp | 2 -- src/libslic3r/Model.hpp | 25 ------------------------- src/slic3r/GUI/3DScene.cpp | 11 ----------- src/slic3r/GUI/3DScene.hpp | 5 ----- src/slic3r/GUI/GUI_ObjectList.cpp | 9 --------- src/slic3r/GUI/GUI_ObjectList.hpp | 2 -- src/slic3r/GUI/Plater.cpp | 4 ---- src/slic3r/GUI/Selection.cpp | 2 -- src/slic3r/GUI/Selection.hpp | 2 -- src/slic3r/GUI/wxExtensions.cpp | 2 -- src/slic3r/GUI/wxExtensions.hpp | 2 -- 11 files changed, 66 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 97d045ae05..25cd92d865 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -644,9 +644,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->sla_points_status = rhs.sla_points_status; this->layer_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment this->layer_height_profile = rhs.layer_height_profile; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ this->printable = rhs.printable; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ this->origin_translation = rhs.origin_translation; m_bounding_box = rhs.m_bounding_box; m_bounding_box_valid = rhs.m_bounding_box_valid; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 18e3f8fb63..239ede4893 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -192,10 +192,8 @@ public: // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. // The pairs of are packed into a 1D array. std::vector layer_height_profile; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Whether or not this object is printable bool printable; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // This vector holds position of selected support points for SLA. The data are // saved in mesh coordinates to allow using them for several instances. @@ -308,16 +306,10 @@ public: private: friend class Model; // This constructor assigns new ID to this ModelObject and its config. -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()), -// explicit ModelObject(Model* model) : m_model(model), origin_translation(Vec3d::Zero()), -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) { assert(this->id().valid()); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) -// explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ { assert(this->id().invalid()); assert(this->config.id().invalid()); } ~ModelObject(); void assign_new_unique_ids_recursive() override; @@ -380,10 +372,7 @@ private: template void serialize(Archive &ar) { ar(cereal::base_class(this)); Internal::StaticSerializationWrapper config_wrapper(config); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, printable, origin_translation, -// ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation, -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); } }; @@ -608,10 +597,8 @@ private: public: // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) EPrintVolumeState print_volume_state; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Whether or not this instance is printable bool printable; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ModelObject* get_object() const { return this->object; } @@ -656,10 +643,7 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ bool is_printable() const { return printable && (print_volume_state == PVS_Inside); } -// bool is_printable() const { return print_volume_state == PVS_Inside; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Getting the input polygon for arrange arrangement::ArrangePolygon get_arrange_polygon() const; @@ -687,16 +671,10 @@ private: ModelObject* object; // Constructor, which assigns a new unique ID. -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ explicit ModelInstance(ModelObject* object) : print_volume_state(PVS_Inside), printable(true), object(object) { assert(this->id().valid()); } -// explicit ModelInstance(ModelObject* object) : print_volume_state(PVS_Inside), object(object) { assert(this->id().valid()); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Constructor, which assigns a new unique ID. explicit ModelInstance(ModelObject *object, const ModelInstance &other) : -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ m_transformation(other.m_transformation), print_volume_state(PVS_Inside), printable(true), object(object) {assert(this->id().valid() && this->id() != other.id());} -// m_transformation(other.m_transformation), print_volume_state(PVS_Inside), object(object) { assert(this->id().valid() && this->id() != other.id()); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ explicit ModelInstance(ModelInstance &&rhs) = delete; ModelInstance& operator=(const ModelInstance &rhs) = delete; @@ -707,10 +685,7 @@ private: // Used for deserialization, therefore no IDs are allocated. ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); } template void serialize(Archive &ar) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ar(m_transformation, print_volume_state, printable); -// ar(m_transformation, print_volume_state); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } }; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index d27d4a78cd..92137d1a6c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -200,9 +200,6 @@ const float GLVolume::HOVER_DESELECT_COLOR[4] = { 1.0f, 0.75f, 0.75f, 1.0f }; const float GLVolume::OUTSIDE_COLOR[4] = { 0.0f, 0.38f, 0.8f, 1.0f }; const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f }; const float GLVolume::DISABLED_COLOR[4] = { 0.25f, 0.25f, 0.25f, 1.0f }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -const float GLVolume::NON_PRINTABLE_COLOR[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const float GLVolume::MODEL_COLOR[4][4] = { { 1.0f, 1.0f, 0.0f, 1.f }, { 1.0f, 0.5f, 0.5f, 1.f }, @@ -221,9 +218,7 @@ GLVolume::GLVolume(float r, float g, float b, float a) , extruder_id(0) , selected(false) , disabled(false) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ , printable(true) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ , is_active(true) , zoom_to_volumes(true) , shader_outside_printer_detection_enabled(false) @@ -277,22 +272,16 @@ void GLVolume::set_render_color() set_render_color(DISABLED_COLOR, 4); else if (is_outside && shader_outside_printer_detection_enabled) set_render_color(OUTSIDE_COLOR, 4); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -// else if (!printable) -// set_render_color(NON_PRINTABLE_COLOR, 4); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ else set_render_color(color, 4); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!printable) { render_color[0] /= 4; render_color[1] /= 4; render_color[2] /= 4; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (force_transparent) render_color[3] = color[3]; diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 723c555019..8dc7b96491 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -231,9 +231,6 @@ public: static const float OUTSIDE_COLOR[4]; static const float SELECTED_OUTSIDE_COLOR[4]; static const float DISABLED_COLOR[4]; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ - static const float NON_PRINTABLE_COLOR[4]; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ static const float MODEL_COLOR[4][4]; static const float SLA_SUPPORT_COLOR[4]; static const float SLA_PAD_COLOR[4]; @@ -297,10 +294,8 @@ public: bool selected; // Is this object disabled from selection? bool disabled; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Is this object printable? bool printable; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ // Whether or not this volume is active for rendering bool is_active; // Whether or not to use this volume when applying zoom_to_volumes() diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 58e96e4bce..a33e442ffa 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1378,21 +1378,12 @@ wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu, wxWind [this](wxCommandEvent&) { split_instances(); }, "", menu, [](){return wxGetApp().plater()->can_set_instance_to_object(); }, parent); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxMenuItem* ObjectList::append_menu_item_printable(wxMenu* menu, wxWindow* parent) { return append_menu_check_item(menu, wxID_ANY, _(L("Printable")), "", [this](wxCommandEvent&) { wxGetApp().plater()->canvas3D()->get_selection().toggle_instance_printable_state(); -// int instance_idx = wxGetApp().plater()->canvas3D()->get_selection().get_instance_idx(); -// if (instance_idx != -1) -// { -// int obj_idx = wxGetApp().plater()->get_selected_object_idx(); -// (*m_objects)[obj_idx]->instances[instance_idx]->printable = !(*m_objects)[obj_idx]->instances[instance_idx]->printable; -// -// } }, menu); } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void ObjectList::append_menu_items_osx(wxMenu* menu) { diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 9802adddfc..cbffaaa0cf 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -225,9 +225,7 @@ public: wxMenuItem* append_menu_item_settings(wxMenu* menu); wxMenuItem* append_menu_item_change_type(wxMenu* menu); wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu, wxWindow* parent); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxMenuItem* append_menu_item_printable(wxMenu* menu, wxWindow* parent); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void append_menu_items_osx(wxMenu* menu); wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); void append_menu_item_export_stl(wxMenu* menu) const ; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 50c6f5b67c..f6289efbc9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3490,10 +3490,8 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ sidebar->obj_list()->append_menu_item_instance_to_object(menu, q); menu->AppendSeparator(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxMenuItem* menu_item_printable = sidebar->obj_list()->append_menu_item_printable(menu, q); menu->AppendSeparator(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ append_menu_item(menu, wxID_ANY, _(L("Reload from Disk")), _(L("Reload the selected file from Disk")), [this](wxCommandEvent&) { reload_from_disk(); }); @@ -3503,7 +3501,6 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ menu->AppendSeparator(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { const Selection& selection = get_selection(); int instance_idx = selection.get_instance_idx(); @@ -3514,7 +3511,6 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ view3D->set_as_dirty(); } }, menu_item_printable->GetId()); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 4db126b80a..4e7b4e4ad9 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1459,7 +1459,6 @@ std::vector Selection::get_unselected_volume_idxs_from(const std:: return idxs; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::toggle_instance_printable_state() { int instance_idx = get_instance_idx(); @@ -1483,7 +1482,6 @@ void Selection::toggle_instance_printable_state() } } } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void Selection::update_valid() { diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 94fa569099..c27b4cc29d 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -336,9 +336,7 @@ public: // returns the list of idxs of the volumes contained in the given list but not in the selection std::vector get_unselected_volume_idxs_from(const std::vector& volume_idxs) const; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ void toggle_instance_printable_state(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ private: void update_valid(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index e707a74768..3886a35aa3 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -157,7 +157,6 @@ wxMenuItem* append_menu_radio_item(wxMenu* menu, int id, const wxString& string, return item; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, wxEvtHandler* event_handler) { @@ -175,7 +174,6 @@ wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string, return item; } -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200; const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 56349f9e21..785634b301 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -43,10 +43,8 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin wxMenuItem* append_menu_radio_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, wxEvtHandler* event_handler); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, wxEvtHandler* event_handler); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ class wxDialog; void edit_tooltip(wxString& tooltip); From 9548dfd88f00a6d0c8d94faf4ef36fafd3dfc84f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 31 Jul 2019 11:52:24 +0200 Subject: [PATCH 457/627] Fixed selection of full object from objects list --- src/slic3r/GUI/GUI_ObjectList.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a3ddd9498a..a948529b9a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2804,7 +2804,8 @@ void ObjectList::update_selections_on_canvas() std::vector volume_idxs; Selection::EMode mode = Selection::Volume; - auto add_to_selection = [this, &volume_idxs](const wxDataViewItem& item, const Selection& selection, int instance_idx, Selection::EMode& mode) + bool single_selection = sel_cnt == 1; + auto add_to_selection = [this, &volume_idxs, &single_selection](const wxDataViewItem& item, const Selection& selection, int instance_idx, Selection::EMode& mode) { const ItemType& type = m_objects_model->GetItemType(item); const int obj_idx = m_objects_model->GetObjectIdByItem(item); @@ -2823,6 +2824,7 @@ void ObjectList::update_selections_on_canvas() else { mode = Selection::Instance; + single_selection = false; std::vector idxs = selection.get_volume_idxs_from_object(obj_idx); volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); } @@ -2864,7 +2866,7 @@ void ObjectList::update_selections_on_canvas() // add volume_idxs = selection.get_unselected_volume_idxs_from(volume_idxs); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Selection-Add from list"))); - selection.add_volumes(mode, volume_idxs, sel_cnt == 1); + selection.add_volumes(mode, volume_idxs, single_selection); } wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state(); From 599f2e07dbf5c595299780905276758224656909 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 31 Jul 2019 12:40:47 +0200 Subject: [PATCH 458/627] Config parameters accessible from SLA gizmo are now saved on the undo/redo stack --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 33 +++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 3 +- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 8929bd5dcb..19b0c791ca 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -900,6 +900,10 @@ RENDER_AGAIN: ImGui::SameLine(diameter_slider_left); ImGui::PushItemWidth(window_width - diameter_slider_left); + // Following is a nasty way to: + // - save the initial value of the slider before one starts messing with it + // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene + // - take correct undo/redo snapshot after the user is done with moving the slider float initial_value = m_new_point_head_diameter; ImGui::SliderFloat("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f"); if (ImGui::IsItemClicked()) { @@ -960,20 +964,35 @@ RENDER_AGAIN: float density = static_cast(opts[0])->value; float minimal_point_distance = static_cast(opts[1])->value; - bool value_changed = ImGui::SliderFloat("", &minimal_point_distance, 0.f, 20.f, "%.f mm"); - if (value_changed) - m_model_object->config.opt("support_points_minimal_distance", true)->value = minimal_point_distance; + ImGui::SliderFloat("", &minimal_point_distance, 0.f, 20.f, "%.f mm"); + bool slider_clicked = ImGui::IsItemClicked(); // someone clicked the slider + bool slider_edited = ImGui::IsItemEdited(); // someone is dragging the slider + bool slider_released = ImGui::IsItemDeactivatedAfterEdit(); // someone has just released the slider m_imgui->text(m_desc.at("points_density")); ImGui::SameLine(settings_sliders_left); - if (ImGui::SliderFloat(" ", &density, 0.f, 200.f, "%.f %%")) { - value_changed = true; + ImGui::SliderFloat(" ", &density, 0.f, 200.f, "%.f %%"); + slider_clicked |= ImGui::IsItemClicked(); + slider_edited |= ImGui::IsItemEdited(); + slider_released |= ImGui::IsItemDeactivatedAfterEdit(); + + if (slider_clicked) { // stash the values of the settings so we know what to revert to after undo + m_minimal_point_distance_stash = minimal_point_distance; + m_density_stash = density; + } + if (slider_edited) { + m_model_object->config.opt("support_points_minimal_distance", true)->value = minimal_point_distance; m_model_object->config.opt("support_points_density_relative", true)->value = (int)density; } - - if (value_changed) // Update side panel + if (slider_released) { + m_model_object->config.opt("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash; + m_model_object->config.opt("support_points_density_relative", true)->value = (int)m_density_stash; + wxGetApp().plater()->take_snapshot(_(L("Support parameter change"))); + m_model_object->config.opt("support_points_minimal_distance", true)->value = minimal_point_distance; + m_model_object->config.opt("support_points_density_relative", true)->value = (int)density; wxGetApp().obj_list()->update_and_show_object_settings_item(); + } bool generate = m_imgui->button(m_desc.at("auto_generate")); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 99184a90d9..fb312e6644 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -107,7 +107,8 @@ private: float m_new_point_head_diameter; // Size of a new point. CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited float m_old_point_head_diameter = 0.; // the same - float m_minimal_point_distance = 20.f; + float m_minimal_point_distance_stash = 0.f; // and again + float m_density_stash = 0.f; // and again mutable std::vector m_editing_cache; // a support point and whether it is currently selected std::vector m_normal_cache; // to restore after discarding changes or undo/redo From aeb29b11849e2e3e9a0282c289a9a7d165bfdbc8 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 31 Jul 2019 13:00:35 +0200 Subject: [PATCH 459/627] SLA points on all objects are assigned Generating status before the background processing starts --- src/slic3r/GUI/Plater.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 38ad580374..7c494b4dcd 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4392,6 +4392,12 @@ void Plater::reslice() { // Stop arrange and (or) optimize rotation tasks. this->stop_jobs(); + + if (printer_technology() == ptSLA) { + for (auto& object : model().objects) + if (object->sla_points_status == sla::PointsStatus::NoPoints) + object->sla_points_status = sla::PointsStatus::Generating; + } //FIXME Don't reslice if export of G-code or sending to OctoPrint is running. // bitmask of UpdateBackgroundProcessReturnState From 1ba9100994102ab4bc99fa004ae52a02fbed0357 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 31 Jul 2019 14:00:43 +0200 Subject: [PATCH 460/627] Fix of gizmo deserialization after Undo / Redo: Deselect all gizmos but the gizmo to be deserialized. --- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index e1978e60dd..b1c000ceb7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -108,11 +108,15 @@ public: ar(m_current); GLGizmoBase* curr = get_current(); - if (curr != nullptr) - { - curr->set_state(GLGizmoBase::On); - curr->load(ar); - } + for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { + GLGizmoBase* gizmo = it->second; + if (gizmo != nullptr) { + gizmo->set_hover_id(-1); + gizmo->set_state((it->second == curr) ? GLGizmoBase::On : GLGizmoBase::Off); + if (gizmo == curr) + gizmo->load(ar); + } + } } template From add171043c73067a4d426f9752408d4c35c49d8d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 31 Jul 2019 15:24:28 +0200 Subject: [PATCH 461/627] Updated icons for layers editing --- resources/icons/edit_layers_all.svg | 52 +++++++------------- resources/icons/edit_layers_some.svg | 71 ++++------------------------ 2 files changed, 26 insertions(+), 97 deletions(-) diff --git a/resources/icons/edit_layers_all.svg b/resources/icons/edit_layers_all.svg index 4fccc1388d..fe4f26c52c 100644 --- a/resources/icons/edit_layers_all.svg +++ b/resources/icons/edit_layers_all.svg @@ -3,39 +3,23 @@ - - - - - - - - - - - - - - - - - + + + + + + + diff --git a/resources/icons/edit_layers_some.svg b/resources/icons/edit_layers_some.svg index 7db56b3f09..fb1e7f8b17 100644 --- a/resources/icons/edit_layers_some.svg +++ b/resources/icons/edit_layers_some.svg @@ -2,68 +2,13 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + From 4a3d7cfb0f3d9da8dfac3deef384f463be39d912 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 31 Jul 2019 15:30:03 +0200 Subject: [PATCH 462/627] Follow-up of https://github.com/prusa3d/PrusaSlicer/commit/9548dfd88f00a6d0c8d94faf4ef36fafd3dfc84f -> an hopefully smarter fix --- src/slic3r/GUI/GUI_ObjectList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a948529b9a..70632a3377 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2824,7 +2824,7 @@ void ObjectList::update_selections_on_canvas() else { mode = Selection::Instance; - single_selection = false; + single_selection &= (obj_idx != selection.get_object_idx()); std::vector idxs = selection.get_volume_idxs_from_object(obj_idx); volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end()); } From 93f86b795d048e5368986dbbdf51e898a841f14c Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 31 Jul 2019 16:36:56 +0200 Subject: [PATCH 463/627] asserts for accessing deleted object list nodes. --- src/slic3r/GUI/wxExtensions.cpp | 10 ++++++++++ src/slic3r/GUI/wxExtensions.hpp | 12 +++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 9f36eceb93..22eeed77cb 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -496,6 +496,15 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent set_action_icon(); } +#ifndef NDEBUG +bool ObjectDataViewModelNode::valid() +{ + // Verify that the object was not deleted yet. + assert(m_idx >= -1); + return m_idx >= -1; +} +#endif /* NDEBUG */ + void ObjectDataViewModelNode::set_action_icon() { m_action_icon_name = m_type & itObject ? "advanced_plus" : @@ -1417,6 +1426,7 @@ wxDataViewItem ObjectDataViewModel::GetParent(const wxDataViewItem &item) const return wxDataViewItem(0); ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); + assert(node != nullptr && node->valid()); // objects nodes has no parent too if (node->m_type == itObject) diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index d7d5fcac21..cbf1f6dfa6 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -158,7 +158,7 @@ DECLARE_VARIANT_OBJECT(DataViewBitmapText) // ---------------------------------------------------------------------------- -// ObjectDataViewModelNode: a node inside PrusaObjectDataViewModel +// ObjectDataViewModelNode: a node inside ObjectDataViewModel // ---------------------------------------------------------------------------- enum ItemType { @@ -251,6 +251,10 @@ public: ObjectDataViewModelNode *child = m_children[i]; delete child; } +#ifndef NDEBUG + // Indicate that the object was deleted. + m_idx = -2; +#endif /* NDEBUG */ } bool IsContainer() const @@ -260,6 +264,7 @@ public: ObjectDataViewModelNode* GetParent() { + assert(m_parent == nullptr || m_parent->valid()); return m_parent; } MyObjectTreeModelNodePtrArray& GetChildren() @@ -346,6 +351,11 @@ public: bool update_settings_digest(const std::vector& categories); int volume_type() const { return int(m_volume_type); } void msw_rescale(); + +#ifndef NDEBUG + bool valid(); +#endif /* NDEBUG */ + private: friend class ObjectDataViewModel; }; From cf6cc1d8635bca5420d57480dbbe2a7da9545ea6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 31 Jul 2019 17:14:32 +0200 Subject: [PATCH 464/627] Refactored LockButton class --- resources/icons/lock_closed_f.svg | 10 +++++++++ resources/icons/lock_open_f.svg | 11 +++++++++ src/slic3r/GUI/wxExtensions.cpp | 37 ++++++++++++++----------------- src/slic3r/GUI/wxExtensions.hpp | 12 +++++----- 4 files changed, 43 insertions(+), 27 deletions(-) create mode 100644 resources/icons/lock_closed_f.svg create mode 100644 resources/icons/lock_open_f.svg diff --git a/resources/icons/lock_closed_f.svg b/resources/icons/lock_closed_f.svg new file mode 100644 index 0000000000..2920ea0aae --- /dev/null +++ b/resources/icons/lock_closed_f.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/resources/icons/lock_open_f.svg b/resources/icons/lock_open_f.svg new file mode 100644 index 0000000000..9440d9266d --- /dev/null +++ b/resources/icons/lock_open_f.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 9f36eceb93..e85de5cc2c 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -2743,21 +2743,20 @@ LockButton::LockButton( wxWindow *parent, const wxSize& size /*= wxDefaultSize*/): wxButton(parent, id, wxEmptyString, pos, size, wxBU_EXACTFIT | wxNO_BORDER) { - m_bmp_lock_on = ScalableBitmap(this, "one_layer_lock_on.png"); - m_bmp_lock_off = ScalableBitmap(this, "one_layer_lock_off.png"); - m_bmp_unlock_on = ScalableBitmap(this, "one_layer_unlock_on.png"); - m_bmp_unlock_off = ScalableBitmap(this, "one_layer_unlock_off.png"); + m_bmp_lock_closed = ScalableBitmap(this, "lock_closed"); + m_bmp_lock_closed_f = ScalableBitmap(this, "lock_closed_f"); + m_bmp_lock_open = ScalableBitmap(this, "lock_open"); + m_bmp_lock_open_f = ScalableBitmap(this, "lock_open_f"); #ifdef __WXMSW__ SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #endif // __WXMSW__ - SetBitmap(m_bmp_unlock_on.bmp()); - SetBitmapDisabled(m_bmp_lock_on.bmp()); + SetBitmap(m_bmp_lock_open.bmp()); + SetBitmapDisabled(m_bmp_lock_open.bmp()); + SetBitmapHover(m_bmp_lock_closed_f.bmp()); //button events - Bind(wxEVT_BUTTON, &LockButton::OnButton, this); - Bind(wxEVT_ENTER_WINDOW, &LockButton::OnEnterBtn, this); - Bind(wxEVT_LEAVE_WINDOW, &LockButton::OnLeaveBtn, this); + Bind(wxEVT_BUTTON, &LockButton::OnButton, this); } void LockButton::OnButton(wxCommandEvent& event) @@ -2766,7 +2765,7 @@ void LockButton::OnButton(wxCommandEvent& event) return; m_is_pushed = !m_is_pushed; - enter_button(true); + update_button_bitmaps(); event.Skip(); } @@ -2774,23 +2773,21 @@ void LockButton::OnButton(wxCommandEvent& event) void LockButton::SetLock(bool lock) { m_is_pushed = lock; - enter_button(true); + update_button_bitmaps(); } void LockButton::msw_rescale() { - m_bmp_lock_on .msw_rescale(); - m_bmp_lock_off .msw_rescale(); - m_bmp_unlock_on .msw_rescale(); - m_bmp_unlock_off.msw_rescale(); + m_bmp_lock_closed.msw_rescale(); + m_bmp_lock_closed_f.msw_rescale(); + m_bmp_lock_open.msw_rescale(); + m_bmp_lock_open_f.msw_rescale(); } -void LockButton::enter_button(const bool enter) +void LockButton::update_button_bitmaps() { - const wxBitmap& icon = m_is_pushed ? - enter ? m_bmp_lock_off.bmp() : m_bmp_lock_on.bmp() : - enter ? m_bmp_unlock_off.bmp() : m_bmp_unlock_on.bmp(); - SetBitmap(icon); + SetBitmap(m_is_pushed ? m_bmp_lock_closed.bmp() : m_bmp_lock_open.bmp()); + SetBitmapHover(m_is_pushed ? m_bmp_lock_closed_f.bmp() : m_bmp_lock_open_f.bmp()); Refresh(); Update(); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index d7d5fcac21..6d8d23aecb 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -843,8 +843,6 @@ public: ~LockButton() {} void OnButton(wxCommandEvent& event); - void OnEnterBtn(wxMouseEvent& event) { enter_button(true); event.Skip(); } - void OnLeaveBtn(wxMouseEvent& event) { enter_button(false); event.Skip(); } bool IsLocked() const { return m_is_pushed; } void SetLock(bool lock); @@ -856,16 +854,16 @@ public: void msw_rescale(); protected: - void enter_button(const bool enter); + void update_button_bitmaps(); private: bool m_is_pushed = false; bool m_disabled = false; - ScalableBitmap m_bmp_lock_on; - ScalableBitmap m_bmp_lock_off; - ScalableBitmap m_bmp_unlock_on; - ScalableBitmap m_bmp_unlock_off; + ScalableBitmap m_bmp_lock_closed; + ScalableBitmap m_bmp_lock_closed_f; + ScalableBitmap m_bmp_lock_open; + ScalableBitmap m_bmp_lock_open_f; }; From 47c97140620ffd95da73a1eac10ea4d9a4fc75ff Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 31 Jul 2019 17:38:06 +0200 Subject: [PATCH 465/627] Added Real-Time DXT1/DXT5 C compression library to CopyrightsDialog + updated icons for mirroring --- resources/icons/mirroring_off.svg | 14 +++++++------- resources/icons/mirroring_on.svg | 16 ++++++++-------- src/slic3r/GUI/AboutDialog.cpp | 3 +++ 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/resources/icons/mirroring_off.svg b/resources/icons/mirroring_off.svg index b68748e90a..4ed9da7482 100644 --- a/resources/icons/mirroring_off.svg +++ b/resources/icons/mirroring_off.svg @@ -4,19 +4,19 @@ viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"> - + - + - + - - + + diff --git a/resources/icons/mirroring_on.svg b/resources/icons/mirroring_on.svg index 55ea49516c..8481246a3c 100644 --- a/resources/icons/mirroring_on.svg +++ b/resources/icons/mirroring_on.svg @@ -2,21 +2,21 @@ - + - + - + - + - - + + diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index ad58d94822..d2c76366b7 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -106,6 +106,9 @@ void CopyrightsDialog::fill_entries() "2001-2016 Expat maintainers" , "http://www.libexpat.org/" }, { "AVRDUDE" , "2018 Free Software Foundation, Inc." , "http://savannah.nongnu.org/projects/avrdude" }, { "Shinyprofiler" , "2007-2010 Aidin Abedi" , "http://code.google.com/p/shinyprofiler/" }, + { "Real-Time DXT1/DXT5 C compression library" + , "Based on original by fabian \"ryg\" giesen v1.04. " + "Custom version, modified by Yann Collet" , "https://github.com/Cyan4973/RygsDXTc" }, { "Icons for STL and GCODE files." , "Akira Yasuda" , "http://3dp0.com/icons-for-stl-and-gcode/" } }; From f913bbf8ef22aa35744e93755fa0e9aec93442cf Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 1 Aug 2019 09:03:06 +0200 Subject: [PATCH 466/627] Reverted order of items in recent projects list --- src/slic3r/GUI/MainFrame.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b08df1690e..d3da148a05 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -411,6 +411,7 @@ void MainFrame::init_menubar() }, wxID_FILE1, wxID_FILE9); std::vector recent_projects = wxGetApp().app_config->get_recent_projects(); + std::reverse(recent_projects.begin(), recent_projects.end()); for (const std::string& project : recent_projects) { m_recent_projects.AddFileToHistory(from_u8(project)); From 1ca8ff1285a10d723469d6723708f32078e0693b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 1 Aug 2019 10:58:52 +0200 Subject: [PATCH 467/627] Changed snapshot names for reset rotation/reset scale/drop to bed functions --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 46 ++++++++++++----------- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 10 ++--- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index a4aa3c6d85..7dd73ece91 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -286,9 +286,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn, wxBU_EXACTFIT); btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { - change_scale_value(0, 100.); - change_scale_value(1, 100.); - change_scale_value(2, 100.); + wxGetApp().plater()->take_snapshot(_(L("Reset scale"))); + // the empty strings prevent taking another three snapshots + change_scale_value(0, 100., std::string()); + change_scale_value(1, 100., std::string()); + change_scale_value(2, 100., std::string()); }); return sizer; }; @@ -323,7 +325,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - canvas->do_rotate(L("Set Rotation")); + canvas->do_rotate(L("Reset Rotation")); UpdateAndShow(true); }); @@ -350,9 +352,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); - change_position_value(0, diff.x()); - change_position_value(1, diff.y()); - change_position_value(2, diff.z()); + // Take an undo/redo snapshot and prevent change_position_value from doing it three times. + wxGetApp().plater()->take_snapshot(_(L("Drop to bed"))); + change_position_value(0, diff.x(), std::string()); + change_position_value(1, diff.y(), std::string()); + change_position_value(2, diff.z(), std::string()); } }); return sizer; @@ -697,7 +701,7 @@ void ObjectManipulation::reset_settings_value() // m_dirty = true; } -void ObjectManipulation::change_position_value(int axis, double value) +void ObjectManipulation::change_position_value(int axis, double value, const std::string& snapshot_name) { if (std::abs(m_cache.position_rounded(axis) - value) < EPSILON) return; @@ -709,14 +713,14 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.start_dragging(); selection.translate(position - m_cache.position, selection.requires_local_axes()); - canvas->do_move(L("Set Position")); + canvas->do_move(snapshot_name); m_cache.position = position; m_cache.position_rounded(axis) = DBL_MAX; this->UpdateAndShow(true); } -void ObjectManipulation::change_rotation_value(int axis, double value) +void ObjectManipulation::change_rotation_value(int axis, double value, const std::string& snapshot_name) { if (std::abs(m_cache.rotation_rounded(axis) - value) < EPSILON) return; @@ -740,14 +744,14 @@ void ObjectManipulation::change_rotation_value(int axis, double value) selection.rotate( (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), transformation_type); - canvas->do_rotate(L("Set Orientation")); + canvas->do_rotate(snapshot_name); m_cache.rotation = rotation; m_cache.rotation_rounded(axis) = DBL_MAX; this->UpdateAndShow(true); } -void ObjectManipulation::change_scale_value(int axis, double value) +void ObjectManipulation::change_scale_value(int axis, double value, const std::string& snapshot_name) { if (std::abs(m_cache.scale_rounded(axis) - value) < EPSILON) return; @@ -755,7 +759,7 @@ void ObjectManipulation::change_scale_value(int axis, double value) Vec3d scale = m_cache.scale; scale(axis) = value; - this->do_scale(axis, scale); + this->do_scale(axis, scale, snapshot_name); m_cache.scale = scale; m_cache.scale_rounded(axis) = DBL_MAX; @@ -763,7 +767,7 @@ void ObjectManipulation::change_scale_value(int axis, double value) } -void ObjectManipulation::change_size_value(int axis, double value) +void ObjectManipulation::change_size_value(int axis, double value, const std::string& snapshot_name) { if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON) return; @@ -781,14 +785,14 @@ void ObjectManipulation::change_size_value(int axis, double value) selection.get_unscaled_instance_bounding_box().size() : wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); - this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); + this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2)), snapshot_name); m_cache.size = size; m_cache.size_rounded(axis) = DBL_MAX; this->UpdateAndShow(true); } -void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const +void ObjectManipulation::do_scale(int axis, const Vec3d &scale, const std::string& snapshot_name) const { Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); Vec3d scaling_factor = scale; @@ -805,7 +809,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const selection.start_dragging(); selection.scale(scaling_factor * 0.01, transformation_type); - wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); + wxGetApp().plater()->canvas3D()->do_scale(snapshot_name); } void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any& value) @@ -833,13 +837,13 @@ void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any double new_value = boost::any_cast(m_og->get_value(opt_key)); if (boost::starts_with(opt_key, "position_")) - change_position_value(axis, new_value); + change_position_value(axis, new_value, L("Set Position")); else if (boost::starts_with(opt_key, "rotation_")) - change_rotation_value(axis, new_value); + change_rotation_value(axis, new_value, L("Set Orientation")); else if (boost::starts_with(opt_key, "scale_")) - change_scale_value(axis, new_value); + change_scale_value(axis, new_value, L("Set Scale")); else if (boost::starts_with(opt_key, "size_")) - change_size_value(axis, new_value); + change_size_value(axis, new_value, L("Set Scale")); } void ObjectManipulation::on_fill_empty_value(const std::string& opt_key) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index e4e190b5bc..bda7aa55f0 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -135,11 +135,11 @@ private: void update_mirror_buttons_visibility(); // change values - void change_position_value(int axis, double value); - void change_rotation_value(int axis, double value); - void change_scale_value(int axis, double value); - void change_size_value(int axis, double value); - void do_scale(int axis, const Vec3d &scale) const; + void change_position_value(int axis, double value, const std::string& snapshot_name); + void change_rotation_value(int axis, double value, const std::string& snapshot_name); + void change_scale_value(int axis, double value, const std::string& snapshot_name); + void change_size_value(int axis, double value, const std::string& snapshot_name); + void do_scale(int axis, const Vec3d &scale, const std::string& snapshot_name) const; void on_change(t_config_option_key opt_key, const boost::any& value); void on_fill_empty_value(const std::string& opt_key); From 299739eeda87841d2c1779e9edfc6dcb09118b7a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 1 Aug 2019 11:01:18 +0200 Subject: [PATCH 468/627] Refactoring into GLTexture::Compressor --- src/slic3r/GUI/GLTexture.cpp | 16 ++++++++++------ src/slic3r/GUI/GLTexture.hpp | 1 + 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index f3421f1508..516f8b934f 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -27,10 +27,13 @@ namespace GUI { void GLTexture::Compressor::reset() { - // force compression completion, if any - m_abort_compressing = true; - // wait for compression completion, if any - while (m_is_compressing) {} + if (m_is_compressing) + { + // force compression completion, if any + m_abort_compressing = true; + // wait for compression completion, if any + while (m_is_compressing) {} + } m_levels.clear(); } @@ -42,8 +45,6 @@ void GLTexture::Compressor::add_level(unsigned int w, unsigned int h, const std: void GLTexture::Compressor::start_compressing() { - m_is_compressing = true; - m_abort_compressing = false; std::thread t(&GLTexture::Compressor::compress, this); t.detach(); } @@ -97,6 +98,9 @@ void GLTexture::Compressor::compress() { // reference: https://github.com/Cyan4973/RygsDXTc + m_is_compressing = true; + m_abort_compressing = false; + for (Level& level : m_levels) { if (m_abort_compressing) diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index 7fc5b8fcf6..ec362944d4 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -32,6 +32,7 @@ namespace GUI { public: explicit Compressor(GLTexture& texture) : m_texture(texture), m_is_compressing(false), m_abort_compressing(false) {} + ~Compressor() { reset(); } void reset(); From 576643c7eddca1f2fb516f2cccdd27ec07fad32c Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 1 Aug 2019 11:53:22 +0200 Subject: [PATCH 469/627] Bumped up the version to 2.1.0-alpha0. Updated the bundled profiles to integrate retract overrides for PET and FLEX materials. --- resources/profiles/PrusaResearch.idx | 2 + resources/profiles/PrusaResearch.ini | 233 ++++++++++++++++++++++++--- src/libslic3r/GCode/WipeTower.hpp | 2 +- version.inc | 6 +- 4 files changed, 217 insertions(+), 26 deletions(-) diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index 81b1ca15fd..a7fd819d41 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,3 +1,5 @@ +min_slic3r_version = 2.1.0-alpha0 +1.0.0-alpha0 Filament specific retract for PET and similar copolymers, and for FLEX min_slic3r_version = 1.42.0-alpha6 0.8.3 FW version and SL1 materials update 0.8.2 FFF and SL1 settings update diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index f17066636d..b78eb7f540 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.8.3 +config_version = 1.0.0-alpha0 # Where to get the updates from? config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ changelog_url = http://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -811,7 +811,7 @@ compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and # MK2 MMU # [print:0.35mm FAST sol full 0.6 nozzle] inherits = *0.35mm*; *0.6nozzle*; *soluble_support* -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1 +compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_model=="MK2SMM" and nozzle_diameter[0]==0.6 and num_extruders>1 external_perimeter_extrusion_width = 0.6 external_perimeter_speed = 30 notes = Set your solluble extruder in Multiple Extruders > Support material/raft interface extruder @@ -876,8 +876,8 @@ solid_infill_speed = 70 top_solid_infill_speed = 45 external_perimeter_extrusion_width = 0.7 perimeter_extrusion_width = 0.7 -infill_extrusion_width = 0.72 -solid_infill_extrusion_width = 0.72 +infill_extrusion_width = 0.7 +solid_infill_extrusion_width = 0.7 # XXXXXXXXXXXXXXXXXXXXXX # XXX----- MK2.5 ----XXX @@ -1009,12 +1009,22 @@ max_fan_speed = 50 min_fan_speed = 30 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{elsif nozzle_diameter[0]==0.6}24{else}45{endif} ; Filament gcode" temperature = 240 +filament_retract_length = 1.4 +filament_retract_lift = 0.2 +compatible_printers_condition = printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) [filament:*PET06*] inherits = *PET* -compatible_printers_condition = nozzle_diameter[0]==0.6 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +compatible_printers_condition = nozzle_diameter[0]==0.6 and printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) filament_max_volumetric_speed = 15 +[filament:*PETMMU1*] +inherits = *PET* +filament_retract_length = nil +filament_retract_speed = nil +filament_retract_lift = 0.2 +compatible_printers_condition = printer_model=="MK2SMM" + [filament:*ABS*] inherits = *common* bed_temperature = 110 @@ -1039,7 +1049,7 @@ inherits = *common* bed_temperature = 50 bridge_fan_speed = 100 # For now, all but selected filaments are disabled for the MMU 2.0 -compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 && ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) +compatible_printers_condition = nozzle_diameter[0]>0.35 and printer_model!="MK2SMM" and num_extruders==1 && ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) cooling = 0 disable_fan_first_layers = 1 extrusion_multiplier = 1.2 @@ -1052,8 +1062,10 @@ first_layer_bed_temperature = 50 first_layer_temperature = 240 max_fan_speed = 90 min_fan_speed = 70 -start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" +start_filament_gcode = "M900 K0"; Filament gcode" temperature = 240 +filament_retract_length = 0.4 +filament_retract_lift = 0 [filament:ColorFabb Brass Bronze] inherits = *PLA* @@ -1124,7 +1136,7 @@ temperature = 270 [filament:ColorFabb XT-CF20] inherits = *PET* -compatible_printers_condition = nozzle_diameter[0]>0.35 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +compatible_printers_condition = nozzle_diameter[0]>0.35 and printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) extrusion_multiplier = 1.2 filament_cost = 80.65 filament_density = 1.35 @@ -1134,6 +1146,8 @@ first_layer_bed_temperature = 90 first_layer_temperature = 260 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" temperature = 260 +filament_retract_length = nil +filament_retract_lift = 0.2 [filament:ColorFabb nGen] inherits = *PET* @@ -1163,13 +1177,15 @@ first_layer_temperature = 260 max_fan_speed = 35 min_fan_speed = 20 temperature = 260 +filament_retract_length = nil +filament_retract_lift = 0 +compatible_printers_condition = nozzle_diameter[0]>0.35 and num_extruders==1 && ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK3.*/ and single_extruder_multi_material) [filament:E3D Edge] inherits = *PET* filament_cost = 56.9 filament_density = 1.26 filament_type = EDGE -filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" [filament:E3D PC-ABS] inherits = *ABS* @@ -1195,11 +1211,10 @@ first_layer_temperature = 265 temperature = 265 filament_type = ASA -[filament:Fillamentum CPE HG100 HM100] +[filament:Fillamentum CPE] inherits = *PET* filament_cost = 54.1 filament_density = 1.25 -filament_notes = "CPE HG100 , CPE HM100" filament_type = CPE first_layer_bed_temperature = 90 first_layer_temperature = 275 @@ -1238,6 +1253,15 @@ filament_cost = 25.4 filament_density = 1.24 filament_notes = "List of materials tested with standard PLA print settings:\n\nDas Filament\nEsun PLA\nEUMAKERS PLA\nFiberlogy HD-PLA\nFillamentum PLA\nFloreon3D\nHatchbox PLA\nPlasty Mladec PLA\nPrimavalue PLA\nProto pasta Matte Fiber\nVerbatim PLA\nVerbatim BVOH" +[filament:Generic FLEX] +inherits = *FLEX* +filament_cost = 82 +filament_density = 1.22 +filament_max_volumetric_speed = 1.2 +filament_retract_length = 0 +filament_retract_speed = nil +filament_retract_lift = nil + [filament:Polymaker PC-Max] inherits = *ABS* filament_cost = 77.3 @@ -1313,7 +1337,7 @@ inherits = *PET* filament_cost = 27.82 filament_density = 1.27 filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nPlasty Mladec PETG" -compatible_printers_condition = nozzle_diameter[0]!=0.6 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +compatible_printers_condition = nozzle_diameter[0]!=0.6 and printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) [filament:Prusament PETG] inherits = *PET* @@ -1322,7 +1346,7 @@ temperature = 250 filament_cost = 24.99 filament_density = 1.27 filament_type = PETG -compatible_printers_condition = nozzle_diameter[0]!=0.6 and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) +compatible_printers_condition = nozzle_diameter[0]!=0.6 and printer_model!="MK2SMM" and ! (printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material) [filament:Prusa PET 0.6 nozzle] inherits = *PET06* @@ -1340,7 +1364,7 @@ filament_type = PETG [filament:*PET MMU2*] inherits = Prusa PET -compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material +compatible_printers_condition = nozzle_diameter[0]!=0.6 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material temperature = 230 first_layer_temperature = 230 filament_cooling_final_speed = 1 @@ -1354,6 +1378,13 @@ filament_unload_time = 12 filament_unloading_speed = 20 filament_unloading_speed_start = 120 filament_loading_speed_start = 19 +filament_retract_length = 1.4 +filament_retract_lift = 0.2 + +[filament:*PET MMU2 06*] +inherits = *PET MMU2* +compatible_printers_condition = nozzle_diameter[0]==0.6 and printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK(2.5|3).*/ and single_extruder_multi_material +filament_max_volumetric_speed = 13 [filament:Generic PET MMU2] inherits = *PET MMU2* @@ -1365,6 +1396,16 @@ inherits = *PET MMU2* inherits = *PET MMU2* filament_type = PETG +[filament:Generic PET MMU2 0.6 nozzle] +inherits = *PET MMU2 06* + +[filament:Prusa PET MMU2 0.6 nozzle] +inherits = *PET MMU2 06* + +[filament:Prusament PETG MMU2 0.6 nozzle] +inherits = *PET MMU2 06* +filament_type = PETG + [filament:Prusa PLA] inherits = *PLA* filament_cost = 25.4 @@ -1407,6 +1448,7 @@ inherits = *PLA MMU2* inherits = *FLEX* filament_cost = 82 filament_density = 1.22 +filament_max_volumetric_speed = 1.35 [filament:Taulman Bridge] inherits = *common* @@ -1544,6 +1586,130 @@ min_fan_speed = 100 start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}10{endif}; Filament gcode" temperature = 220 +## Filaments MMU1 + +[filament:ColorFabb HT MMU1] +inherits = *PETMMU1* +bed_temperature = 110 +bridge_fan_speed = 30 +cooling = 1 +disable_fan_first_layers = 3 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_cost = 58.66 +filament_density = 1.18 +first_layer_bed_temperature = 105 +first_layer_temperature = 270 +max_fan_speed = 20 +min_fan_speed = 10 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}45{endif}; Filament gcode" +temperature = 270 + +[filament:ColorFabb XT MMU1] +inherits = *PETMMU1* +filament_type = PET +filament_cost = 62.9 +filament_density = 1.27 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +temperature = 270 + +[filament:ColorFabb XT-CF20 MMU1] +inherits = *PETMMU1* +compatible_printers_condition = nozzle_diameter[0]>0.35 and printer_model=="MK2SMM" +extrusion_multiplier = 1.2 +filament_cost = 80.65 +filament_density = 1.35 +filament_colour = #804040 +filament_max_volumetric_speed = 1 +first_layer_bed_temperature = 90 +first_layer_temperature = 260 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" +temperature = 260 + +[filament:ColorFabb nGen MMU1] +inherits = *PETMMU1* +filament_cost = 21.2 +filament_density = 1.2 +bridge_fan_speed = 40 +fan_always_on = 0 +fan_below_layer_time = 10 +filament_type = NGEN +first_layer_temperature = 240 +max_fan_speed = 35 +min_fan_speed = 20 + +[filament:E3D Edge MMU1] +inherits = *PETMMU1* +filament_cost = 56.9 +filament_density = 1.26 +filament_type = EDGE +filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" + +[filament:Fillamentum CPE MMU1] +inherits = *PETMMU1* +filament_cost = 54.1 +filament_density = 1.25 +filament_type = CPE +first_layer_bed_temperature = 90 +first_layer_temperature = 275 +max_fan_speed = 50 +min_fan_speed = 50 +temperature = 275 + +[filament:Generic PET MMU1] +inherits = *PETMMU1* +filament_cost = 27.82 +filament_density = 1.27 +filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nFillamentum CPE GH100\nPlasty Mladec PETG" + +[filament:Prusa PET MMU1] +inherits = *PETMMU1* +filament_cost = 27.82 +filament_density = 1.27 +filament_notes = "List of manufacturers tested with standard PET print settings:\n\nE3D Edge\nPlasty Mladec PETG" + +[filament:Prusament PETG MMU1] +inherits = *PETMMU1* +first_layer_temperature = 240 +temperature = 250 +filament_cost = 24.99 +filament_density = 1.27 +filament_type = PETG + +[filament:Taulman T-Glase MMU1] +inherits = *PETMMU1* +filament_cost = 40 +filament_density = 1.27 +bridge_fan_speed = 40 +cooling = 0 +fan_always_on = 0 +first_layer_bed_temperature = 90 +first_layer_temperature = 240 +max_fan_speed = 5 +min_fan_speed = 0 +start_filament_gcode = "M900 K{if printer_notes=~/.*PRINTER_HAS_BOWDEN.*/}200{else}30{endif}; Filament gcode" + +[filament:SemiFlex or Flexfill 98A MMU1] +inherits = *FLEX* +filament_cost = 82 +filament_density = 1.22 +filament_max_volumetric_speed = 1.35 +filament_retract_length = nil +filament_retract_speed = nil +filament_retract_lift = nil +compatible_printers_condition = printer_model=="MK2SMM" + +[filament:Generic FLEX MMU1] +inherits = *FLEX* +filament_cost = 82 +filament_density = 1.22 +filament_max_volumetric_speed = 1.2 +filament_retract_length = 0 +filament_retract_speed = nil +filament_retract_lift = nil +compatible_printers_condition = printer_model=="MK2SMM" + [sla_print:*common*] compatible_printers_condition = printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_SL1.*/ layer_height = 0.05 @@ -2328,6 +2494,21 @@ extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors\n +[printer:Original Prusa i3 MK2.5S MMU2S 0.6 nozzle] +inherits = Original Prusa i3 MK2.5S MMU2S +nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 +max_layer_height = 0.40 +min_layer_height = 0.15 +printer_variant = 0.6 +default_print_profile = 0.20mm NORMAL 0.6 nozzle + +[printer:Original Prusa i3 MK2.5 MMU2 0.6 nozzle] +inherits = Original Prusa i3 MK2.5 MMU2 +nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 +max_layer_height = 0.40 +min_layer_height = 0.15 +printer_variant = 0.6 +default_print_profile = 0.20mm NORMAL 0.6 nozzle # XXXXXXXXXXXXXXXXX # XXX--- MK3 ---XXX @@ -2488,15 +2669,23 @@ extruder_colour = #FF8000;#DB5182;#00FFFF;#FF4F4F;#9FFF9F start_gcode = M115 U3.7.2 ; tell printer latest fw version\nG90 ; use absolute coordinates\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG21 ; set units to millimeters\n\n; Send the filament type to the MMU2.0 unit.\n; E stands for extruder number, F stands for filament type (0: default; 1:flex; 2: PVA)\nM403 E0 F{"" + ((filament_type[0]=="FLEX") ? 1 : ((filament_type[0]=="PVA") ? 2 : 0))}\nM403 E1 F{"" + ((filament_type[1]=="FLEX") ? 1 : ((filament_type[1]=="PVA") ? 2 : 0))}\nM403 E2 F{"" + ((filament_type[2]=="FLEX") ? 1 : ((filament_type[2]=="PVA") ? 2 : 0))}\nM403 E3 F{"" + ((filament_type[3]=="FLEX") ? 1 : ((filament_type[3]=="PVA") ? 2 : 0))}\nM403 E4 F{"" + ((filament_type[4]=="FLEX") ? 1 : ((filament_type[4]=="PVA") ? 2 : 0))}\n\n{if not has_single_extruder_multi_material_priming}\n;go outside print area\nG1 Y-3.0 F1000.0\nG1 Z0.4 F1000.0\n; select extruder\nT[initial_tool]\n; initial load\nG1 X55.0 E29.0 F1073.0\nG1 X5.0 E29.0 F1800.0\nG1 X55.0 E8.0 F2000.0\nG1 Z0.3 F1000.0\nG92 E0.0\nG1 X240.0 E25.0 F2200.0\nG1 Y-2.0 F1000.0\nG1 X55.0 E25 F1400.0\nG1 Z0.20 F1000.0\nG1 X5.0 E4.0 F1000.0\nG92 E0.0\n{endif}\n\nM221 S{if layer_height<0.075}100{else}95{endif}\nG92 E0.0\n end_gcode = {if has_wipe_tower}\nG1 E-15.0000 F3000\n{else}\nG1 X0 Y210 F7200\nG1 E2 F5000\nG1 E2 F5500\nG1 E2 F6000\nG1 E-15.0000 F5800\nG1 E-20.0000 F5500\nG1 E10.0000 F3000\nG1 E-10.0000 F3100\nG1 E10.0000 F3150\nG1 E-10.0000 F3250\nG1 E10.0000 F3300\n{endif}\n\n; Unload filament\nM702 C\n\nG4 ; wait\nM104 S0 ; turn off temperature\nM140 S0 ; turn off heatbed\nM107 ; turn off fan\n; Lift print head a bit\n{if layer_z < max_print_height}G1 Z{z_offset+min(layer_z+30, max_print_height)}{endif} ; Move print head up\nG1 X0 Y200 F3000 ; home X axis\nM84 ; disable motors\n -# 0.6 nozzle MMU printer profile - only for single mode for now +## 0.6mm nozzle MMU2/S printer profiles -# [printer:Original Prusa i3 MK3S MMU2S 0.6 nozzle] -# inherits = Original Prusa i3 MK3S MMU2S -# nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 -# max_layer_height = 0.40 -# min_layer_height = 0.15 -# printer_variant = 0.6 -# default_print_profile = 0.30mm QUALITY 0.6 nozzle MK3 +[printer:Original Prusa i3 MK3S MMU2S 0.6 nozzle] +inherits = Original Prusa i3 MK3S MMU2S +nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 +max_layer_height = 0.40 +min_layer_height = 0.15 +printer_variant = 0.6 +default_print_profile = 0.30mm QUALITY 0.6 nozzle MK3 + +[printer:Original Prusa i3 MK3 MMU2 0.6 nozzle] +inherits = Original Prusa i3 MK3 MMU2 +nozzle_diameter = 0.6,0.6,0.6,0.6,0.6 +max_layer_height = 0.40 +min_layer_height = 0.15 +printer_variant = 0.6 +default_print_profile = 0.30mm QUALITY 0.6 nozzle MK3 [printer:Original Prusa SL1] printer_technology = SLA diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 6dfad1b8c5..3c6b4afca2 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -134,7 +134,7 @@ public: m_filpar[idx].cooling_final_speed = cooling_final_speed; } - m_filpar[idx].filament_area = (M_PI/4.f) * pow(filament_diameter, 2); // all extruders are assumed to have the same filament diameter at this point + m_filpar[idx].filament_area = float((M_PI/4.f) * pow(filament_diameter, 2)); // all extruders are assumed to have the same filament diameter at this point m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM if (max_volumetric_speed != 0.f) diff --git a/version.inc b/version.inc index 8f5ff57d1c..4e1607bda1 100644 --- a/version.inc +++ b/version.inc @@ -3,7 +3,7 @@ set(SLIC3R_APP_NAME "PrusaSlicer") set(SLIC3R_APP_KEY "PrusaSlicer") -set(SLIC3R_VERSION "2.0.0") +set(SLIC3R_VERSION "2.1.0-alpha0") set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN") -set(SLIC3R_RC_VERSION "2,0,0,0") -set(SLIC3R_RC_VERSION_DOTS "2.0.0.0") +set(SLIC3R_RC_VERSION "2,1,0,0") +set(SLIC3R_RC_VERSION_DOTS "2.1.0.0") From 81ab43ea9adfd3498e9a5619b9cef02e5b564d6e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 1 Aug 2019 11:18:11 +0200 Subject: [PATCH 470/627] Revert "Changed snapshot names for reset rotation/reset scale/drop to bed functions" This reverts commit 1ca8ff1285a10d723469d6723708f32078e0693b. --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 46 +++++++++++------------ src/slic3r/GUI/GUI_ObjectManipulation.hpp | 10 ++--- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 7dd73ece91..a4aa3c6d85 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -286,11 +286,9 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn, wxBU_EXACTFIT); btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { - wxGetApp().plater()->take_snapshot(_(L("Reset scale"))); - // the empty strings prevent taking another three snapshots - change_scale_value(0, 100., std::string()); - change_scale_value(1, 100., std::string()); - change_scale_value(2, 100., std::string()); + change_scale_value(0, 100.); + change_scale_value(1, 100.); + change_scale_value(2, 100.); }); return sizer; }; @@ -325,7 +323,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - canvas->do_rotate(L("Reset Rotation")); + canvas->do_rotate(L("Set Rotation")); UpdateAndShow(true); }); @@ -352,11 +350,9 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); - // Take an undo/redo snapshot and prevent change_position_value from doing it three times. - wxGetApp().plater()->take_snapshot(_(L("Drop to bed"))); - change_position_value(0, diff.x(), std::string()); - change_position_value(1, diff.y(), std::string()); - change_position_value(2, diff.z(), std::string()); + change_position_value(0, diff.x()); + change_position_value(1, diff.y()); + change_position_value(2, diff.z()); } }); return sizer; @@ -701,7 +697,7 @@ void ObjectManipulation::reset_settings_value() // m_dirty = true; } -void ObjectManipulation::change_position_value(int axis, double value, const std::string& snapshot_name) +void ObjectManipulation::change_position_value(int axis, double value) { if (std::abs(m_cache.position_rounded(axis) - value) < EPSILON) return; @@ -713,14 +709,14 @@ void ObjectManipulation::change_position_value(int axis, double value, const std Selection& selection = canvas->get_selection(); selection.start_dragging(); selection.translate(position - m_cache.position, selection.requires_local_axes()); - canvas->do_move(snapshot_name); + canvas->do_move(L("Set Position")); m_cache.position = position; m_cache.position_rounded(axis) = DBL_MAX; this->UpdateAndShow(true); } -void ObjectManipulation::change_rotation_value(int axis, double value, const std::string& snapshot_name) +void ObjectManipulation::change_rotation_value(int axis, double value) { if (std::abs(m_cache.rotation_rounded(axis) - value) < EPSILON) return; @@ -744,14 +740,14 @@ void ObjectManipulation::change_rotation_value(int axis, double value, const std selection.rotate( (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), transformation_type); - canvas->do_rotate(snapshot_name); + canvas->do_rotate(L("Set Orientation")); m_cache.rotation = rotation; m_cache.rotation_rounded(axis) = DBL_MAX; this->UpdateAndShow(true); } -void ObjectManipulation::change_scale_value(int axis, double value, const std::string& snapshot_name) +void ObjectManipulation::change_scale_value(int axis, double value) { if (std::abs(m_cache.scale_rounded(axis) - value) < EPSILON) return; @@ -759,7 +755,7 @@ void ObjectManipulation::change_scale_value(int axis, double value, const std::s Vec3d scale = m_cache.scale; scale(axis) = value; - this->do_scale(axis, scale, snapshot_name); + this->do_scale(axis, scale); m_cache.scale = scale; m_cache.scale_rounded(axis) = DBL_MAX; @@ -767,7 +763,7 @@ void ObjectManipulation::change_scale_value(int axis, double value, const std::s } -void ObjectManipulation::change_size_value(int axis, double value, const std::string& snapshot_name) +void ObjectManipulation::change_size_value(int axis, double value) { if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON) return; @@ -785,14 +781,14 @@ void ObjectManipulation::change_size_value(int axis, double value, const std::st selection.get_unscaled_instance_bounding_box().size() : wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); - this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2)), snapshot_name); + this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2))); m_cache.size = size; m_cache.size_rounded(axis) = DBL_MAX; this->UpdateAndShow(true); } -void ObjectManipulation::do_scale(int axis, const Vec3d &scale, const std::string& snapshot_name) const +void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const { Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); Vec3d scaling_factor = scale; @@ -809,7 +805,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale, const std::strin selection.start_dragging(); selection.scale(scaling_factor * 0.01, transformation_type); - wxGetApp().plater()->canvas3D()->do_scale(snapshot_name); + wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); } void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any& value) @@ -837,13 +833,13 @@ void ObjectManipulation::on_change(t_config_option_key opt_key, const boost::any double new_value = boost::any_cast(m_og->get_value(opt_key)); if (boost::starts_with(opt_key, "position_")) - change_position_value(axis, new_value, L("Set Position")); + change_position_value(axis, new_value); else if (boost::starts_with(opt_key, "rotation_")) - change_rotation_value(axis, new_value, L("Set Orientation")); + change_rotation_value(axis, new_value); else if (boost::starts_with(opt_key, "scale_")) - change_scale_value(axis, new_value, L("Set Scale")); + change_scale_value(axis, new_value); else if (boost::starts_with(opt_key, "size_")) - change_size_value(axis, new_value, L("Set Scale")); + change_size_value(axis, new_value); } void ObjectManipulation::on_fill_empty_value(const std::string& opt_key) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index bda7aa55f0..e4e190b5bc 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -135,11 +135,11 @@ private: void update_mirror_buttons_visibility(); // change values - void change_position_value(int axis, double value, const std::string& snapshot_name); - void change_rotation_value(int axis, double value, const std::string& snapshot_name); - void change_scale_value(int axis, double value, const std::string& snapshot_name); - void change_size_value(int axis, double value, const std::string& snapshot_name); - void do_scale(int axis, const Vec3d &scale, const std::string& snapshot_name) const; + void change_position_value(int axis, double value); + void change_rotation_value(int axis, double value); + void change_scale_value(int axis, double value); + void change_size_value(int axis, double value); + void do_scale(int axis, const Vec3d &scale) const; void on_change(t_config_option_key opt_key, const boost::any& value); void on_fill_empty_value(const std::string& opt_key); From 98e08e356feee4c74de978cc9c30bd3806adfb49 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 1 Aug 2019 12:27:32 +0200 Subject: [PATCH 471/627] Changed snapshot names for reset scale/reset rotation/drop to bed functions (it's done a bit simpler this time) --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index a4aa3c6d85..40f27d7c0c 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -286,6 +286,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(btn, wxBU_EXACTFIT); btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Reset scale"))); change_scale_value(0, 100.); change_scale_value(1, 100.); change_scale_value(2, 100.); @@ -323,7 +324,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); selection.synchronize_unselected_volumes(); // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. - canvas->do_rotate(L("Set Rotation")); + canvas->do_rotate(L("Reset Rotation")); UpdateAndShow(true); }); @@ -350,6 +351,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Drop to bed"))); change_position_value(0, diff.x()); change_position_value(1, diff.y()); change_position_value(2, diff.z()); From c2a43dc8644ad59ac5ee5647b18ee956de2d419d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 1 Aug 2019 11:35:43 +0200 Subject: [PATCH 472/627] Mirroring buttons: Fixed scaling issues and ensured proper hiding on Win --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 14 ++++++++----- src/slic3r/GUI/wxExtensions.cpp | 24 +++++++++++++++++++++-- src/slic3r/GUI/wxExtensions.hpp | 4 ++++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 40f27d7c0c..2bbf11170e 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -210,6 +210,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width); auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label)); + btn->SetBitmapDisabled_(m_mirror_bitmap_hidden); m_mirror_buttons[axis_idx].first = btn; m_mirror_buttons[axis_idx].second = mbShown; @@ -648,13 +649,13 @@ void ObjectManipulation::update_mirror_buttons_visibility() wxGetApp().CallAfter([this, new_states]{ for (int i=0; i<3; ++i) { if (new_states[i] != m_mirror_buttons[i].second) { - const wxBitmap* bmp; + const ScalableBitmap* bmp; switch (new_states[i]) { - case mbHidden : bmp = &m_mirror_bitmap_hidden.bmp(); m_mirror_buttons[i].first->Enable(false); break; - case mbShown : bmp = &m_mirror_bitmap_off.bmp(); m_mirror_buttons[i].first->Enable(true); break; - case mbActive : bmp = &m_mirror_bitmap_on.bmp(); m_mirror_buttons[i].first->Enable(true); break; + case mbHidden : bmp = &m_mirror_bitmap_hidden; m_mirror_buttons[i].first->Enable(false); break; + case mbShown : bmp = &m_mirror_bitmap_off; m_mirror_buttons[i].first->Enable(true); break; + case mbActive : bmp = &m_mirror_bitmap_on; m_mirror_buttons[i].first->Enable(true); break; } - m_mirror_buttons[i].first->SetBitmap(*bmp); + m_mirror_buttons[i].first->SetBitmap_(*bmp); m_mirror_buttons[i].second = new_states[i]; } } @@ -927,6 +928,9 @@ void ObjectManipulation::msw_rescale() m_reset_rotation_button->msw_rescale(); m_drop_to_bed_button->msw_rescale(); + for (int id = 0; id < 3; ++id) + m_mirror_buttons[id].first->msw_rescale(); + get_og()->msw_rescale(); } diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 0f8d3ce4e0..a33b7248c3 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -2966,6 +2966,13 @@ ScalableButton::ScalableButton( wxWindow * parent, #endif // __WXMSW__ SetBitmap(create_scaled_bitmap(parent, icon_name)); + + if (size != wxDefaultSize) + { + const int em = em_unit(parent); + m_width = size.x/em; + m_height= size.y/em; + } } @@ -2992,11 +2999,24 @@ void ScalableButton::SetBitmap_(const ScalableBitmap& bmp) m_current_icon_name = bmp.name(); } +void ScalableButton::SetBitmapDisabled_(const ScalableBitmap& bmp) +{ + SetBitmapDisabled(bmp.bmp()); + m_disabled_icon_name = bmp.name(); +} + void ScalableButton::msw_rescale() { - const wxBitmap bmp = create_scaled_bitmap(m_parent, m_current_icon_name); + SetBitmap(create_scaled_bitmap(m_parent, m_current_icon_name)); + if (!m_disabled_icon_name.empty()) + SetBitmapDisabled(create_scaled_bitmap(m_parent, m_disabled_icon_name)); - SetBitmap(bmp); + if (m_width > 0 || m_height>0) + { + const int em = em_unit(m_parent); + wxSize size(m_width * em, m_height * em); + SetMinSize(size); + } } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 725f7c507e..2a8d8fccf5 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -904,12 +904,16 @@ public: ~ScalableButton() {} void SetBitmap_(const ScalableBitmap& bmp); + void SetBitmapDisabled_(const ScalableBitmap &bmp); void msw_rescale(); private: wxWindow* m_parent; std::string m_current_icon_name = ""; + std::string m_disabled_icon_name = ""; + int m_width {-1}; // should be multiplied to em_unit + int m_height{-1}; // should be multiplied to em_unit }; From 59db1f7f3678a58314f515d2729bcbb4fef03a92 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 1 Aug 2019 14:58:04 +0200 Subject: [PATCH 473/627] Code refactoring to add PrintableItem column to ObjectList --- resources/icons/eye_closed.svg | 13 ++++ resources/icons/eye_open.svg | 11 ++++ src/slic3r/GUI/GUI_ObjectList.cpp | 73 +++++++++++--------- src/slic3r/GUI/wxExtensions.cpp | 106 ++++++++++++++++++------------ src/slic3r/GUI/wxExtensions.hpp | 43 +++++++----- 5 files changed, 159 insertions(+), 87 deletions(-) create mode 100644 resources/icons/eye_closed.svg create mode 100644 resources/icons/eye_open.svg diff --git a/resources/icons/eye_closed.svg b/resources/icons/eye_closed.svg new file mode 100644 index 0000000000..127d53ca3b --- /dev/null +++ b/resources/icons/eye_closed.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/resources/icons/eye_open.svg b/resources/icons/eye_open.svg new file mode 100644 index 0000000000..a87cf3a83f --- /dev/null +++ b/resources/icons/eye_open.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index bb824ab005..bbb7d7115c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -212,16 +212,20 @@ void ObjectList::create_objects_ctrl() EnableDropTarget(wxDF_UNICODETEXT); #endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE - // column 0(Icon+Text) of the view control: + // column ItemName(Icon+Text) of the view control: // And Icon can be consisting of several bitmaps AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(), - 0, 20*wxGetApp().em_unit()/*200*/, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); + colName, 20*wxGetApp().em_unit()/*200*/, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); - // column 1 of the view control: + // column PrintableProperty (Icon) of the view control: + AppendBitmapColumn(" ", colPrint, wxDATAVIEW_CELL_INERT, int(2 * wxGetApp().em_unit()), + wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); + + // column Extruder of the view control: AppendColumn(create_objects_list_extruder_column(4)); - // column 2 of the view control: - AppendBitmapColumn(" ", 2, wxDATAVIEW_CELL_INERT, int(2.5 * wxGetApp().em_unit())/*25*/, + // column ItemEditing of the view control: + AppendBitmapColumn("Editing", colEditing, wxDATAVIEW_CELL_INERT, int(2.5 * wxGetApp().em_unit())/*25*/, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); } @@ -321,7 +325,7 @@ void ObjectList::set_tooltip_for_item(const wxPoint& pt) return; } - if (col->GetTitle() == " " && GetSelectedItemsCount()<2) + if (col->GetTitle() == _(L("Editing")) && GetSelectedItemsCount()<2) GetMainWindow()->SetToolTip(_(L("Right button click the icon to change the object settings"))); else if (col->GetTitle() == _("Name")) { @@ -377,7 +381,7 @@ wxDataViewColumn* ObjectList::create_objects_list_extruder_column(int extruders_ choices.Add(wxString::Format("%d", i)); wxDataViewChoiceRenderer *c = new wxDataViewChoiceRenderer(choices, wxDATAVIEW_CELL_EDITABLE, wxALIGN_CENTER_HORIZONTAL); - wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, 1, + wxDataViewColumn* column = new wxDataViewColumn(_(L("Extruder")), c, colExtruder, 8*wxGetApp().em_unit()/*80*/, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); return column; } @@ -397,7 +401,7 @@ void ObjectList::update_extruder_values_for_items(const int max_extruder) else extruder = wxString::Format("%d", object->config.option("extruder")->value); - m_objects_model->SetValue(extruder, item, 1); + m_objects_model->SetValue(extruder, item, colExtruder); if (object->volumes.size() > 1) { for (auto id = 0; id < object->volumes.size(); id++) { @@ -409,7 +413,7 @@ void ObjectList::update_extruder_values_for_items(const int max_extruder) else extruder = wxString::Format("%d", object->volumes[id]->config.option("extruder")->value); - m_objects_model->SetValue(extruder, item, 1); + m_objects_model->SetValue(extruder, item, colExtruder); } } } @@ -421,7 +425,7 @@ void ObjectList::update_objects_list_extruder_column(int extruders_count) if (printer_technology() == ptSLA) extruders_count = 1; - wxDataViewChoiceRenderer* ch_render = dynamic_cast(GetColumn(1)->GetRenderer()); + wxDataViewChoiceRenderer* ch_render = dynamic_cast(GetColumn(colExtruder)->GetRenderer()); if (ch_render->GetChoices().GetCount() - 1 == extruders_count) return; @@ -430,21 +434,21 @@ void ObjectList::update_objects_list_extruder_column(int extruders_count) if (m_objects && extruders_count > 1) update_extruder_values_for_items(extruders_count); - // delete old 2nd column - DeleteColumn(GetColumn(1)); - // insert new created 3rd column - InsertColumn(1, create_objects_list_extruder_column(extruders_count)); + // delete old extruder column + DeleteColumn(GetColumn(colExtruder)); + // insert new created extruder column + InsertColumn(colExtruder, create_objects_list_extruder_column(extruders_count)); // set show/hide for this column set_extruder_column_hidden(extruders_count <= 1); //a workaround for a wrong last column width updating under OSX - GetColumn(2)->SetWidth(25); + GetColumn(colEditing)->SetWidth(25); m_prevent_update_extruder_in_config = false; } void ObjectList::set_extruder_column_hidden(const bool hide) const { - GetColumn(1)->SetHidden(hide); + GetColumn(colExtruder)->SetHidden(hide); } void ObjectList::update_extruder_in_config(const wxDataViewItem& item) @@ -471,7 +475,7 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item) } wxVariant variant; - m_objects_model->GetValue(variant, item, 1); + m_objects_model->GetValue(variant, item, colExtruder); const wxString selection = variant.GetString(); if (!m_config || selection.empty()) @@ -748,7 +752,9 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) #endif // __WXOSX__ const wxString title = col->GetTitle(); - if (title == " ") + if (title == " "); + // show_context_menu(); + else if (title == _("Editing")) show_context_menu(); else if (title == _("Name")) { @@ -2260,7 +2266,13 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) // add instances to the object, if it has those if (model_object->instances.size()>1) - increase_object_instances(obj_idx, model_object->instances.size()); + { + std::vector print_idicator(model_object->instances.size()); + for (int i = 0; i < model_object->instances.size(); ++i) + print_idicator[i] = model_object->instances[i]->is_printable(); + + select_item(m_objects_model->AddInstanceChild(m_objects_model->GetItemById(obj_idx), print_idicator)); + } // add settings to the object, if it has those add_settings_item(item, &model_object->config); @@ -2342,7 +2354,7 @@ void ObjectList::delete_from_model_and_list(const std::vector& it (*m_objects)[item->obj_idx]->config.has("extruder")) { const wxString extruder = wxString::Format("%d", (*m_objects)[item->obj_idx]->config.option("extruder")->value); - m_objects_model->SetValue(extruder, m_objects_model->GetItemById(item->obj_idx), 1); + m_objects_model->SetValue(extruder, m_objects_model->GetItemById(item->obj_idx), colExtruder); } wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx); } @@ -3415,7 +3427,7 @@ void ObjectList::rename_item() // The icon can't be edited so get its old value and reuse it. wxVariant valueOld; - m_objects_model->GetValue(valueOld, item, 0); + m_objects_model->GetValue(valueOld, item, colName); DataViewBitmapText bmpText; bmpText << valueOld; @@ -3425,7 +3437,7 @@ void ObjectList::rename_item() wxVariant value; value << bmpText; - m_objects_model->SetValue(value, item, 0); + m_objects_model->SetValue(value, item, colName); m_objects_model->ItemChanged(item); update_name_in_model(item); @@ -3466,9 +3478,10 @@ void ObjectList::msw_rescale() // update min size !!! A width of control shouldn't be a wxDefaultCoord SetMinSize(wxSize(1, 15 * em)); - GetColumn(0)->SetWidth(19 * em); - GetColumn(1)->SetWidth( 8 * em); - GetColumn(2)->SetWidth( 2 * em); + GetColumn(colName)->SetWidth(19 * em); + GetColumn(colPrint)->SetWidth( 2 * em); + GetColumn(colExtruder)->SetWidth( 8 * em); + GetColumn(colEditing)->SetWidth( 2 * em); // rescale all icons, used by ObjectList msw_rescale_icons(); @@ -3489,18 +3502,18 @@ void ObjectList::msw_rescale() void ObjectList::ItemValueChanged(wxDataViewEvent &event) { - if (event.GetColumn() == 0) + if (event.GetColumn() == colName) update_name_in_model(event.GetItem()); - else if (event.GetColumn() == 1) + else if (event.GetColumn() == colExtruder) update_extruder_in_config(event.GetItem()); } void ObjectList::OnEditingDone(wxDataViewEvent &event) { - if (event.GetColumn() != 0) + if (event.GetColumn() != colName) return; - const auto renderer = dynamic_cast(GetColumn(0)->GetRenderer()); + const auto renderer = dynamic_cast(GetColumn(colName)->GetRenderer()); if (renderer->WasCanceled()) wxTheApp->CallAfter([this]{ @@ -3582,7 +3595,7 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const /* We can change extruder for Object/Volume only. * So, if Instance is selected, get its Object item and change it */ - m_objects_model->SetValue(extruder_str, type & itInstance ? m_objects_model->GetTopParent(item) : item, 1); + m_objects_model->SetValue(extruder_str, type & itInstance ? m_objects_model->GetTopParent(item) : item, colExtruder); const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 3886a35aa3..5bb85e86d5 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -450,6 +450,16 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, // ObjectDataViewModelNode // ---------------------------------------------------------------------------- +void ObjectDataViewModelNode::init_container() +{ +#ifdef __WXGTK__ + // it's necessary on GTK because of control have to know if this item will be container + // in another case you couldn't to add subitem for this item + // it will be produce "segmentation fault" + m_container = true; +#endif //__WXGTK__ +} + ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type) : m_parent(parent), m_type(type), @@ -472,13 +482,8 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent m_name = _(L("Layers")); } -#ifdef __WXGTK__ - // it's necessary on GTK because of control have to know if this item will be container - // in another case you couldn't to add subitem for this item - // it will be produce "segmentation fault" if (type & (itInstanceRoot | itLayerRoot)) - m_container = true; -#endif //__WXGTK__ + init_container(); } ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, @@ -504,14 +509,8 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; m_bmp = create_scaled_bitmap(nullptr, "edit_layers_some"); // FIXME: pass window ptr -#ifdef __WXGTK__ - // it's necessary on GTK because of control have to know if this item will be container - // in another case you couldn't to add subitem for this item - // it will be produce "segmentation fault" - m_container = true; -#endif //__WXGTK__ - set_action_icon(); + init_container(); } void ObjectDataViewModelNode::set_action_icon() @@ -521,6 +520,13 @@ void ObjectDataViewModelNode::set_action_icon() m_action_icon = create_scaled_bitmap(nullptr, m_action_icon_name); // FIXME: pass window ptr } +void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable) +{ + m_printable = printable; + m_printable_icon = m_printable == piUndef ? m_empty_bmp : + create_scaled_bitmap(nullptr, m_printable == piPrintable ? "eye_open.png" : "eye_closed.png"); +} + Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr; void ObjectDataViewModelNode::update_settings_digest_bitmaps() { @@ -574,17 +580,20 @@ bool ObjectDataViewModelNode::SetValue(const wxVariant& variant, unsigned col) { switch (col) { - case 0: { + case colPrint: + m_printable_icon << variant; + return true; + case colName: { DataViewBitmapText data; data << variant; m_bmp = data.GetBitmap(); m_name = data.GetText(); return true; } - case 1: { + case colExtruder: { const wxString & val = variant.GetString(); m_extruder = val == "0" ? _(L("default")) : val; return true; } - case 2: + case colEditing: m_action_icon << variant; return true; default: @@ -744,26 +753,49 @@ static bool append_root_node(ObjectDataViewModelNode *parent_node, return false; } -wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &parent_item, size_t num) +wxDataViewItem ObjectDataViewModel::AddRoot(const wxDataViewItem &parent_item, ItemType root_type) { ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); if (!parent_node) return wxDataViewItem(0); // get InstanceRoot node - ObjectDataViewModelNode *inst_root_node { nullptr }; + ObjectDataViewModelNode *root_node { nullptr }; + const bool appended = append_root_node(parent_node, &root_node, root_type); + if (!root_node) return wxDataViewItem(0); - const bool appended = append_root_node(parent_node, &inst_root_node, itInstanceRoot); - const wxDataViewItem inst_root_item((void*)inst_root_node); - if (!inst_root_node) return wxDataViewItem(0); + const wxDataViewItem root_item((void*)root_node); if (appended) - ItemAdded(parent_item, inst_root_item);// notify control + ItemAdded(parent_item, root_item);// notify control + return root_item; +} + +wxDataViewItem ObjectDataViewModel::AddInstanceRoot(const wxDataViewItem &parent_item) +{ + return AddRoot(parent_item, itInstanceRoot); +} + +wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &parent_item, size_t num) +{ + const std::vector print_indicator(num, true); + + return wxDataViewItem((void*)AddInstanceChild(parent_item, print_indicator)); +} + +wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem& parent_item, + const std::vector& print_indicator) +{ + const wxDataViewItem inst_root_item = AddInstanceRoot(parent_item); + if (!inst_root_item) return wxDataViewItem(0); + + ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); // Add instance nodes ObjectDataViewModelNode *instance_node = nullptr; size_t counter = 0; - while (counter < num) { + while (counter < print_indicator.size()) { instance_node = new ObjectDataViewModelNode(inst_root_node, itInstance); + instance_node->set_printable_icon(print_indicator[counter] ? piPrintable : piUnprintable); inst_root_node->Append(instance_node); // notify control const wxDataViewItem instance_item((void*)instance_node); @@ -776,20 +808,7 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem &paren wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_item) { - ObjectDataViewModelNode *parent_node = (ObjectDataViewModelNode*)parent_item.GetID(); - if (!parent_node) return wxDataViewItem(0); - - // get LayerRoot node - ObjectDataViewModelNode *layer_root_node{ nullptr }; - const bool appended = append_root_node(parent_node, &layer_root_node, itLayerRoot); - if (!layer_root_node) return wxDataViewItem(0); - - const wxDataViewItem layer_root_item((void*)layer_root_node); - - if (appended) - ItemAdded(parent_item, layer_root_item);// notify control - - return layer_root_item; + return AddRoot(parent_item, itLayerRoot); } wxDataViewItem ObjectDataViewModel::AddLayersChild(const wxDataViewItem &parent_item, @@ -1356,13 +1375,16 @@ void ObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &ite ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); switch (col) { - case 0: + case colPrint: + variant << node->m_printable_icon; + break; + case colName: variant << DataViewBitmapText(node->m_name, node->m_bmp); break; - case 1: + case colExtruder: variant = node->m_extruder; break; - case 2: + case colEditing: variant << node->m_action_icon; break; default: @@ -1425,7 +1447,7 @@ bool ObjectDataViewModel::IsEnabled(const wxDataViewItem &item, unsigned int col ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); // disable extruder selection for the non "itObject|itVolume" item - return !(col == 1 && node->m_extruder.IsEmpty()); + return !(col == colExtruder && node->m_extruder.IsEmpty()); } wxDataViewItem ObjectDataViewModel::GetParent(const wxDataViewItem &item) const @@ -1773,7 +1795,7 @@ bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value // The icon can't be edited so get its old value and reuse it. wxVariant valueOld; - GetView()->GetModel()->GetValue(valueOld, m_item, 0); + GetView()->GetModel()->GetValue(valueOld, m_item, colName); DataViewBitmapText bmpText; bmpText << valueOld; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 785634b301..679d9346da 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -175,6 +175,21 @@ enum ItemType { itLayer = 64, }; +enum ColumnNumber +{ + colName = 0, // item name + colPrint , // printable property + colExtruder , // extruder selection + colEditing , // item editing +}; + +enum PrintIndicator +{ + piUndef = 0, // no print indicator + piPrintable , // printable + piUnprintable , // unprintable +}; + class ObjectDataViewModelNode; WX_DEFINE_ARRAY_PTR(ObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray); @@ -194,6 +209,8 @@ class ObjectDataViewModelNode bool m_container = false; wxString m_extruder = "default"; wxBitmap m_action_icon; + PrintIndicator m_printable {piUndef}; + wxBitmap m_printable_icon; std::string m_action_icon_name = ""; Slic3r::ModelVolumeType m_volume_type; @@ -206,14 +223,8 @@ public: m_type(itObject), m_extruder(extruder) { -#ifdef __WXGTK__ - // it's necessary on GTK because of control have to know if this item will be container - // in another case you couldn't to add subitem for this item - // it will be produce "segmentation fault" - m_container = true; -#endif //__WXGTK__ - set_action_icon(); + init_container(); } ObjectDataViewModelNode(ObjectDataViewModelNode* parent, @@ -227,15 +238,9 @@ public: m_idx (idx), m_extruder (extruder) { - m_bmp = bmp; -#ifdef __WXGTK__ - // it's necessary on GTK because of control have to know if this item will be container - // in another case you couldn't to add subitem for this item - // it will be produce "segmentation fault" - m_container = true; -#endif //__WXGTK__ - + m_bmp = bmp; set_action_icon(); + init_container(); } ObjectDataViewModelNode(ObjectDataViewModelNode* parent, @@ -256,6 +261,7 @@ public: } } + void init_container(); bool IsContainer() const { return m_container; @@ -344,6 +350,8 @@ public: // Set action icons for node void set_action_icon(); + // Set printable icon for node + void set_printable_icon(PrintIndicator printable); void update_settings_digest_bitmaps(); bool update_settings_digest(const std::vector& categories); @@ -383,6 +391,7 @@ public: const bool create_frst_child = true); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); + wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, const std::vector& print_indicator); wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item, const t_layer_height_range& layer_range, @@ -472,6 +481,10 @@ public: const bool is_marked = false); void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; + +private: + wxDataViewItem AddRoot(const wxDataViewItem& parent_item, const ItemType root_type); + wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); }; // ---------------------------------------------------------------------------- From 9f393e6b9ba1463467e242daebda6ff0124cd1e5 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 1 Aug 2019 15:25:35 +0200 Subject: [PATCH 474/627] Not taking the snapshot with non-empty Redo stack will likely be more confusing than losing the Redo stack. Let's wait for user feedback. --- src/slic3r/GUI/Selection.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 211863627f..2488947bc3 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -402,8 +402,10 @@ void Selection::remove_all() if (is_empty()) return; - - if (!wxGetApp().plater()->can_redo()) + +// Not taking the snapshot with non-empty Redo stack will likely be more confusing than losing the Redo stack. +// Let's wait for user feedback. +// if (!wxGetApp().plater()->can_redo()) wxGetApp().plater()->take_snapshot(_(L("Selection-Remove All"))); m_mode = Instance; From 170bd8b064d55cd580b19c597acb08d45ac35164 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 1 Aug 2019 15:28:21 +0200 Subject: [PATCH 475/627] Wipetower fix: temperature-changing command was sometimes missing after the toolchange Cause: variable holding last issued temperature was not reset where it should have been This should fix issue #2685 --- src/libslic3r/GCode/WipeTower.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 55a6c4437e..e1b34fdeae 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1242,6 +1242,8 @@ void WipeTower::generate(std::vector> & for (auto& used : m_used_filament_length) // reset used filament stats used = 0.f; + m_old_temperature = -1; // reset last temperature written in the gcode + std::vector layer_result; for (auto layer : m_plan) { From ec9117cc06e5b268d5c5b1170312c922fe3e892b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 1 Aug 2019 15:38:00 +0200 Subject: [PATCH 476/627] Split snapshot text for separated fazes (in ObjectList) --- src/slic3r/GUI/GUI_ObjectList.cpp | 44 ++++++++++++------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 70632a3377..fede48166c 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -490,7 +490,7 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const if (obj_idx < 0) return; const int volume_id = m_objects_model->GetVolumeIdByItem(item); - take_snapshot(wxString::Format(_(L("Rename %s")), volume_id < 0 ? _(L("Object")) : _(L("Sub-object")))); + take_snapshot(volume_id < 0 ? _(L("Rename Object")) : _(L("Rename Sub-object"))); if (m_objects_model->GetItemType(item) & itObject) { (*m_objects)[obj_idx]->name = m_objects_model->GetName(item).ToUTF8().data(); @@ -1043,8 +1043,10 @@ void ObjectList::get_settings_choice(const wxString& category_name) wxArrayInt selections; wxDataViewItem item = GetSelection(); + const ItemType item_type = m_objects_model->GetItemType(item); + settings_menu_hierarchy settings_menu; - const bool is_part = m_objects_model->GetItemType(item) & (itVolume | itLayer); + const bool is_part = item_type & (itVolume | itLayer); get_options_menu(settings_menu, is_part); std::vector< std::pair > *settings_list = nullptr; @@ -1121,7 +1123,10 @@ void ObjectList::get_settings_choice(const wxString& category_name) } #endif - take_snapshot(wxString::Format(_(L("Add Settings for %s")), is_part ? _(L("Sub-object")) : _(L("Object")))); + const wxString snapshot_text = item_type & itLayer ? _(L("Add Settings for Layers")) : + item_type & itVolume ? _(L("Add Settings for Sub-object")) : + _(L("Add Settings for Object")); + take_snapshot(snapshot_text); std::vector selected_options; selected_options.reserve(selection_cnt); @@ -1153,7 +1158,7 @@ void ObjectList::get_settings_choice(const wxString& category_name) // Add settings item for object/sub-object and show them - if (!(m_objects_model->GetItemType(item) & (itObject | itVolume | itLayer))) + if (!(item_type & (itObject | itVolume | itLayer))) item = m_objects_model->GetTopParent(item); show_settings(add_settings_item(item, m_config)); } @@ -1163,11 +1168,13 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) std::vector options = get_options_for_bundle(bundle_name); wxDataViewItem item = GetSelection(); + ItemType item_type = m_objects_model->GetItemType(item); + /* Because of we couldn't edited layer_height for ItVolume from settings list, * correct options according to the selected item type : * remove "layer_height" option */ - if ((m_objects_model->GetItemType(item) & itVolume) && bundle_name == _("Layers and Perimeters")) { + if ((item_type & itVolume) && bundle_name == _("Layers and Perimeters")) { const auto layer_height_it = std::find(options.begin(), options.end(), "layer_height"); if (layer_height_it != options.end()) options.erase(layer_height_it); @@ -1179,7 +1186,10 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) assert(m_config); auto opt_keys = m_config->keys(); - take_snapshot(wxString::Format(_(L("Add Settings Bundle for %s")), m_objects_model->GetItemType(item) & (itVolume|itLayer) ? _(L("Sub-object")) : _(L("Object")))); + const wxString snapshot_text = item_type & itLayer ? _(L("Add Settings Bundle for Layers")) : + item_type & itVolume ? _(L("Add Settings Bundle for Sub-object")) : + _(L("Add Settings Bundle for Object")); + take_snapshot(snapshot_text); const DynamicPrintConfig& from_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; for (auto& opt_key : options) @@ -1196,7 +1206,7 @@ void ObjectList::get_freq_settings_choice(const wxString& bundle_name) } // Add settings item for object/sub-object and show them - if (!(m_objects_model->GetItemType(item) & (itObject | itVolume | itLayer))) + if (!(item_type & (itObject | itVolume | itLayer))) item = m_objects_model->GetTopParent(item); show_settings(add_settings_item(item, m_config)); } @@ -1211,26 +1221,6 @@ void ObjectList::show_settings(const wxDataViewItem settings_item) // update object selection on Plater if (!m_prevent_canvas_selection_update) update_selections_on_canvas(); -/* auto item = GetSelection(); - if (item) { - if (m_objects_model->GetItemType(item) == itInstance) - item = m_objects_model->GetTopParent(item); - const auto settings_item = m_objects_model->IsSettingsItem(item) ? item : m_objects_model->GetSettingsItem(item); - select_item(settings_item ? settings_item : - m_objects_model->AddSettingsChild(item)); - - // update object selection on Plater - if (!m_prevent_canvas_selection_update) - update_selections_on_canvas(); - } - else { - //# ys_FIXME ??? use case ??? - auto panel = wxGetApp().sidebar().scrolled_panel(); - panel->Freeze(); - wxGetApp().obj_settings()->UpdateAndShow(true); - panel->Thaw(); - } - */ } wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) { From 7d25d8c677cc0edbba469e2a54658ed9468efa60 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 1 Aug 2019 16:03:52 +0200 Subject: [PATCH 477/627] Can build with (original llvm) clang-cl on windows --- CMakeLists.txt | 11 ++++++++--- cmake/modules/PrecompiledHeader.cmake | 9 ++++++--- deps/deps-windows.cmake | 4 ++++ src/avrdude/windows/unistd.h | 8 +++++++- src/libnest2d/tests/test.cpp | 4 ++++ src/libslic3r/Arrange.cpp | 5 +++++ src/libslic3r/MinAreaBoundingBox.cpp | 5 +++++ src/slic3r/GUI/GUI_Utils.cpp | 2 +- 8 files changed, 40 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cbb0e2ec49..a29a144fe3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,8 +52,14 @@ if (SLIC3R_GUI) add_definitions(-DSLIC3R_GUI) endif () +if (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) + set(IS_CLANG_CL TRUE) +else () + set(IS_CLANG_CL FALSE) +endif () + if (MSVC) - if (SLIC3R_MSVC_COMPILE_PARALLEL) + if (SLIC3R_MSVC_COMPILE_PARALLEL AND NOT IS_CLANG_CL) add_compile_options(/MP) endif () # /bigobj (Increase Number of Sections in .Obj file) @@ -148,7 +154,7 @@ if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals" ) endif() -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") +if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall" ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder" ) @@ -168,7 +174,6 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATC add_compile_options(-Wno-unknown-pragmas) endif() - if (SLIC3R_ASAN) add_compile_options(-fsanitize=address -fno-omit-frame-pointer) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") diff --git a/cmake/modules/PrecompiledHeader.cmake b/cmake/modules/PrecompiledHeader.cmake index e463021441..2f62a7dbeb 100644 --- a/cmake/modules/PrecompiledHeader.cmake +++ b/cmake/modules/PrecompiledHeader.cmake @@ -105,6 +105,9 @@ function(add_precompiled_header _target _input) cmake_parse_arguments(_PCH "FORCEINCLUDE" "SOURCE_CXX;SOURCE_C" "" ${ARGN}) get_filename_component(_input_we ${_input} NAME_WE) + get_filename_component(_input_full ${_input} ABSOLUTE) + file(TO_NATIVE_PATH "${_input_full}" _input_fullpath) + if(NOT _PCH_SOURCE_CXX) set(_PCH_SOURCE_CXX "${_input_we}.cpp") endif() @@ -138,16 +141,16 @@ function(add_precompiled_header _target _input) set_source_files_properties("${_source}" PROPERTIES OBJECT_OUTPUTS "${_pch_c_pch}") else() if(_source MATCHES \\.\(cpp|cxx|cc\)$) - set(_pch_compile_flags "${_pch_compile_flags} \"/Fp${_pch_cxx_pch}\" \"/Yu${_input}\"") + set(_pch_compile_flags "${_pch_compile_flags} \"/Fp${_pch_cxx_pch}\" \"/Yu${_input_fullpath}\"") set(_pch_source_cxx_needed TRUE) set_source_files_properties("${_source}" PROPERTIES OBJECT_DEPENDS "${_pch_cxx_pch}") else() - set(_pch_compile_flags "${_pch_compile_flags} \"/Fp${_pch_c_pch}\" \"/Yu${_input}\"") + set(_pch_compile_flags "${_pch_compile_flags} \"/Fp${_pch_c_pch}\" \"/Yu${_input_fullpath}\"") set(_pch_source_c_needed TRUE) set_source_files_properties("${_source}" PROPERTIES OBJECT_DEPENDS "${_pch_c_pch}") endif() if(_PCH_FORCEINCLUDE) - set(_pch_compile_flags "${_pch_compile_flags} /FI${_input}") + set(_pch_compile_flags "${_pch_compile_flags} /FI${_input_fullpath}") endif(_PCH_FORCEINCLUDE) endif() diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index 9092f330bc..85013fbddd 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -19,6 +19,10 @@ else () message(FATAL_ERROR "Unsupported MSVC version") endif () +if (CMAKE_CXX_COMPILER_ID STREQUAL Clang) + set(DEP_BOOST_TOOLSET "clang-win") +endif () + if (${DEPS_BITS} EQUAL 32) set(DEP_MSVC_GEN "Visual Studio ${DEP_VS_VER}") set(DEP_PLATFORM "Win32") diff --git a/src/avrdude/windows/unistd.h b/src/avrdude/windows/unistd.h index 95ba79a346..fe6a8fb871 100644 --- a/src/avrdude/windows/unistd.h +++ b/src/avrdude/windows/unistd.h @@ -63,10 +63,15 @@ extern "C" { #define STDOUT_FILENO 1 #define STDERR_FILENO 2 +#ifdef _MSC_VER +#include +struct timezone; +struct timeval; +#else #ifndef __cplusplus /* should be in some equivalent to */ typedef __int8 int8_t; -typedef __int16 int16_t; +typedef __int16 int16_t; typedef __int32 int32_t; typedef __int64 int64_t; typedef unsigned __int8 uint8_t; @@ -74,6 +79,7 @@ typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; typedef unsigned __int64 uint64_t; #endif +#endif int usleep(unsigned usec); diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 29577344d1..4a6691415f 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -7,6 +7,10 @@ #include "../tools/svgtools.hpp" #include +#if defined(_MSC_VER) && defined(__clang__) +#define BOOST_NO_CXX17_HDR_STRING_VIEW +#endif + #include "boost/multiprecision/integer.hpp" #include "boost/rational.hpp" diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index b4cfac9546..ed599d11da 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -12,6 +12,11 @@ #include #include + +#if defined(_MSC_VER) && defined(__clang__) +#define BOOST_NO_CXX17_HDR_STRING_VIEW +#endif + #include #include diff --git a/src/libslic3r/MinAreaBoundingBox.cpp b/src/libslic3r/MinAreaBoundingBox.cpp index fafb54a585..15c04517d0 100644 --- a/src/libslic3r/MinAreaBoundingBox.cpp +++ b/src/libslic3r/MinAreaBoundingBox.cpp @@ -1,6 +1,11 @@ #include "MinAreaBoundingBox.hpp" #include + +#if defined(_MSC_VER) && defined(__clang__) +#define BOOST_NO_CXX17_HDR_STRING_VIEW +#endif + #include #include diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp index 74e70c5546..d5753f2ccf 100644 --- a/src/slic3r/GUI/GUI_Utils.cpp +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -62,7 +62,7 @@ template typename F::FN winapi_get_function(const wchar_t *dll, const c static HINSTANCE dll_handle = LoadLibraryExW(dll, nullptr, 0); if (dll_handle == nullptr) { return nullptr; } - return (F::FN)GetProcAddress(dll_handle, fn_name); + return (typename F::FN)GetProcAddress(dll_handle, fn_name); } #endif From 9471c9cd17b19e3d96155a4413dd7a7a84009960 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 1 Aug 2019 16:20:46 +0200 Subject: [PATCH 478/627] Implemented FR #2633 --- src/slic3r/GUI/MainFrame.cpp | 19 ++++++++++++++++++- src/slic3r/GUI/MainFrame.hpp | 2 ++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d3da148a05..f5da43aee9 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -279,6 +279,18 @@ bool MainFrame::can_export_gcode() const return true; } +bool MainFrame::can_send_gcode() const +{ + if (m_plater == nullptr) + return false; + + if (m_plater->model().objects.empty()) + return false; + + const auto prin_host_opt =wxGetApp().preset_bundle->printers.get_edited_preset().config.option("print_host"); + return prin_host_opt != nullptr && !prin_host_opt->value.empty(); +} + bool MainFrame::can_slice() const { bool bg_proc = wxGetApp().app_config->get("background_processing") == "1"; @@ -451,6 +463,10 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, menu_icon("export_gcode"), nullptr, [this](){return can_export_gcode(); }, this); m_changeable_menu_items.push_back(item_export_gcode); + wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _(L("S&end G-code")) + dots +"\tCtrl+Shift+G", _(L("Send to print current plate as G-code")), + [this](wxCommandEvent&) { if (m_plater) m_plater->send_gcode(); }, menu_icon("export_gcode"), nullptr, + [this](){return can_send_gcode(); }, this); + m_changeable_menu_items.push_back(item_send_gcode); export_menu->AppendSeparator(); append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &STL")) + dots, _(L("Export current plate as STL")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, menu_icon("export_plater"), nullptr, @@ -689,7 +705,8 @@ void MainFrame::update_menubar() { const bool is_fff = plater()->printer_technology() == ptFFF; - m_changeable_menu_items[miExport] ->SetItemLabel((is_fff ? _(L("Export &G-code")) : _(L("Export")) ) + dots + "\tCtrl+G"); + m_changeable_menu_items[miExport] ->SetItemLabel((is_fff ? _(L("Export &G-code")) : _(L("E&xport")) ) + dots + "\tCtrl+G"); + m_changeable_menu_items[miSend] ->SetItemLabel((is_fff ? _(L("S&end G-code")) : _(L("S&end to print"))) + dots + "\tCtrl+Shift+G"); m_changeable_menu_items[miMaterialTab] ->SetItemLabel((is_fff ? _(L("&Filament Settings Tab")) : _(L("Mate&rial Settings Tab"))) + "\tCtrl+3"); m_changeable_menu_items[miMaterialTab] ->SetBitmap(create_scaled_bitmap(this, menu_icon(is_fff ? "spool": "resin"))); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index a41f33824d..5d34be48ef 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -67,6 +67,7 @@ class MainFrame : public DPIFrame bool can_export_model() const; bool can_export_supports() const; bool can_export_gcode() const; + bool can_send_gcode() const; bool can_slice() const; bool can_change_view() const; bool can_select() const; @@ -79,6 +80,7 @@ class MainFrame : public DPIFrame enum MenuItems { // FFF SLA miExport = 0, // Export G-code Export + miSend, // Send G-code Send to print miMaterialTab, // Filament Settings Material Settings }; From 28cc5953506b8dab6b3ee2e386148cd1d2d1239b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 2 Aug 2019 09:43:41 +0200 Subject: [PATCH 479/627] #2593 - Fixed loading of .zip.amf files when running from command line --- src/libslic3r/Model.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 858ae52b29..76ef9eccab 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -99,8 +99,7 @@ Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *c result = load_stl(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".obj")) result = load_obj(input_file.c_str(), &model); - else if (!boost::algorithm::iends_with(input_file, ".zip.amf") && (boost::algorithm::iends_with(input_file, ".amf") || - boost::algorithm::iends_with(input_file, ".amf.xml"))) + else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) result = load_amf(input_file.c_str(), config, &model); else if (boost::algorithm::iends_with(input_file, ".3mf")) result = load_3mf(input_file.c_str(), config, &model); From c791ba776f96c8fa18c435c12dad2c726af46c3b Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 2 Aug 2019 12:05:02 +0200 Subject: [PATCH 480/627] Added absolute time to estimated time for color print and fixed a bug in showing estimated times for print color for silent mode --- src/libslic3r/GCode.cpp | 4 ++-- src/libslic3r/GCodeTimeEstimator.cpp | 24 ++++++++++++++++++++---- src/libslic3r/GCodeTimeEstimator.hpp | 8 +++++--- src/slic3r/GUI/Plater.cpp | 4 ++-- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index bc37300269..011bf3fc42 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1138,9 +1138,9 @@ void GCode::_do_export(Print &print, FILE *file) print.m_print_statistics.clear(); print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A"; - print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(); + print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(true); if (m_silent_time_estimator_enabled) - print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(); + print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(true); std::vector extruders = m_writer.extruders(); if (! extruders.empty()) { diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index 33dc9f4b7d..87f8bca65d 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -694,22 +694,38 @@ namespace Slic3r { return m_color_times; } - std::vector GCodeTimeEstimator::get_color_times_dhms() const + std::vector GCodeTimeEstimator::get_color_times_dhms(bool include_absolute) const { std::vector ret; + float total_time = 0.0f; for (float t : m_color_times) { - ret.push_back(_get_time_dhms(t)); + total_time += t; + std::string time = ""; + if (include_absolute) + time += _get_time_dhms(total_time) + " ("; + time += _get_time_dhms(t); + if (include_absolute) + time += ")"; + ret.push_back(time); } return ret; } - std::vector GCodeTimeEstimator::get_color_times_minutes() const + std::vector GCodeTimeEstimator::get_color_times_minutes(bool include_absolute) const { std::vector ret; + float total_time = 0.0f; for (float t : m_color_times) { - ret.push_back(_get_time_minutes(t)); + total_time += t; + std::string time = ""; + if (include_absolute) + time += _get_time_minutes(total_time) + " ("; + time += _get_time_minutes(t); + if (include_absolute) + time += ")"; + ret.push_back(time); } return ret; } diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index 840d587784..792fb72a51 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -346,14 +346,16 @@ namespace Slic3r { // Returns the estimated time, in minutes (integer) std::string get_time_minutes() const; - // Returns the estimated time, in seconds, for each color + // Returns the estimated time, in seconds, for each color std::vector get_color_times() const; // Returns the estimated time, in format DDd HHh MMm SSs, for each color - std::vector get_color_times_dhms() const; + // If include_absolute==true the strings will be formatted as: "absolute time (relative time)" + std::vector get_color_times_dhms(bool include_absolute) const; // Returns the estimated time, in minutes (integer), for each color - std::vector get_color_times_minutes() const; + // If include_absolute==true the strings will be formatted as: "absolute time (relative time)" + std::vector get_color_times_minutes(bool include_absolute) const; // Return an estimate of the memory consumed by the time estimator. size_t memory_used() const; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7c494b4dcd..23beb09627 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1175,10 +1175,10 @@ void Sidebar::show_sliced_info_sizer(const bool show) if (ps.estimated_silent_print_time != "N/A") { new_label += wxString::Format("\n - %s", _(L("stealth mode"))); info_text += wxString::Format("\n%s", ps.estimated_silent_print_time); - for (int i = (int)ps.estimated_normal_color_print_times.size() - 1; i >= 0; --i) + for (int i = (int)ps.estimated_silent_color_print_times.size() - 1; i >= 0; --i) { new_label += wxString::Format("\n - %s%d", _(L("Color ")), i + 1); - info_text += wxString::Format("\n%s", ps.estimated_normal_color_print_times[i]); + info_text += wxString::Format("\n%s", ps.estimated_silent_color_print_times[i]); } } p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); From bfb135bcc3ac3f7d5cd65365a2f5944a78c7a250 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 2 Aug 2019 15:11:50 +0200 Subject: [PATCH 481/627] Comment out stale implementation in Serial, fix #2150 --- src/slic3r/GUI/FirmwareDialog.cpp | 2 +- src/slic3r/Utils/Serial.cpp | 3 +++ src/slic3r/Utils/Serial.hpp | 14 +++++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index 7865aecf2e..d1f2da040c 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -354,7 +354,7 @@ bool FirmwareDialog::priv::check_model_id() // Therefore, regretably, so far the check cannot be used and we just return true here. // TODO: Rewrite Serial using more platform-native code. return true; - + // if (hex_file.model_id.empty()) { // // No data to check against, assume it's ok // return true; diff --git a/src/slic3r/Utils/Serial.cpp b/src/slic3r/Utils/Serial.cpp index cd2a01cbfd..acfd5fafd0 100644 --- a/src/slic3r/Utils/Serial.cpp +++ b/src/slic3r/Utils/Serial.cpp @@ -353,6 +353,8 @@ void Serial::set_baud_rate(unsigned baud_rate) } } + +/* void Serial::set_DTR(bool on) { auto handle = native_handle(); @@ -495,6 +497,7 @@ std::string Serial::printer_format_line(const std::string &line, unsigned line_n return (boost::format("N%1% %2%*%3%\n") % line_num_str % line % checksum).str(); } +*/ } // namespace Utils diff --git a/src/slic3r/Utils/Serial.hpp b/src/slic3r/Utils/Serial.hpp index 67d64b4ec1..8bad75b315 100644 --- a/src/slic3r/Utils/Serial.hpp +++ b/src/slic3r/Utils/Serial.hpp @@ -46,6 +46,17 @@ public: ~Serial(); void set_baud_rate(unsigned baud_rate); + + // The Serial implementation is currently in disarray and therefore commented out. + // The boost implementation seems to have several problems, such as lack of support + // for custom baud rates, few weird implementation bugs and a history of API breakages. + // It's questionable whether it solves more problems than causes. Probably not. + // TODO: Custom implementation not based on asio. + // + // As of now, this class is only kept for the purpose of rebooting AVR109, + // see FirmwareDialog::priv::avr109_reboot() + +/* void set_DTR(bool on); // Resets the line number both internally as well as with the firmware using M110 @@ -68,7 +79,7 @@ public: // Same as above, but with internally-managed line number size_t printer_write_line(const std::string &line); - + // Toggles DTR to reset the printer void printer_reset(); @@ -76,6 +87,7 @@ public: static std::string printer_format_line(const std::string &line, unsigned line_num); private: unsigned m_line_num = 0; +*/ }; From 1cdc3e0493099b93cc41c11be68fa27e640c94df Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 2 Aug 2019 15:30:37 +0200 Subject: [PATCH 482/627] Workaround for gizmos being clipped by the perspective camera --- src/slic3r/GUI/GLCanvas3D.cpp | 17 ++++++++++++++--- src/slic3r/GUI/GLCanvas3D.hpp | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5e505bb416..c74207123d 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1529,7 +1529,7 @@ void GLCanvas3D::render() } m_camera.apply_view_matrix(); - m_camera.apply_projection(_max_bounding_box(true)); + m_camera.apply_projection(_max_bounding_box(true, true)); GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f }; glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam)); @@ -3273,7 +3273,7 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) void GLCanvas3D::set_camera_zoom(double zoom) { const Size& cnv_size = get_canvas_size(); - m_camera.set_zoom(zoom, _max_bounding_box(false), cnv_size.get_width(), cnv_size.get_height()); + m_camera.set_zoom(zoom, _max_bounding_box(false, false), cnv_size.get_width(), cnv_size.get_height()); m_dirty = true; } @@ -3699,9 +3699,20 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) m_dirty = false; } -BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_bed_model) const +BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model) const { BoundingBoxf3 bb = volumes_bounding_box(); + + // The following is a workaround for gizmos not being taken in account when calculating the tight camera frustrum + // A better solution would ask the gizmo manager for the bounding box of the current active gizmo, if any + if (include_gizmos && m_gizmos.is_running()) + { + BoundingBoxf3 sel_bb = m_selection.get_bounding_box(); + Vec3d sel_bb_center = sel_bb.center(); + Vec3d extend_by = sel_bb.max_size() * Vec3d::Ones(); + bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by)); + } + bb.merge(m_bed.get_bounding_box(include_bed_model)); return bb; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index c9803f9db8..bd33dbef78 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -652,7 +652,7 @@ private: bool _set_current(); void _resize(unsigned int w, unsigned int h); - BoundingBoxf3 _max_bounding_box(bool include_bed_model) const; + BoundingBoxf3 _max_bounding_box(bool include_gizmos, bool include_bed_model) const; void _zoom_to_box(const BoundingBoxf3& box); From 3b24565411d3b4e477a6393e27196707be9eb2df Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 2 Aug 2019 16:15:49 +0200 Subject: [PATCH 483/627] Fixed wrong naming of bottom infill pattern --- src/libslic3r/PrintConfig.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 304f6f749f..e49c81f460 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -422,6 +422,7 @@ void PrintConfigDef::init_fff_params() def->cli = "bottom-fill-pattern|external-fill-pattern|solid-fill-pattern"; def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values = def_top_fill_pattern->enum_values; + def->enum_labels = def_top_fill_pattern->enum_labels; def->aliases = def_top_fill_pattern->aliases; def->set_default_value(new ConfigOptionEnum(ipRectilinear)); From 6ab1cec48c037a4f4a132a06e83f2a12fcacd9ff Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 2 Aug 2019 16:49:22 +0200 Subject: [PATCH 484/627] Empty layers detection added to GCode.cpp Added detection of empty layers so the wipe tower doesn't trip on them (it is not printable anyway). This should improve wipe tower reliability with supports, objects standing on edges, etc. I also turned an assert into exception throw to prevent hard crashes and nonsense output. --- src/libslic3r/GCode.cpp | 18 +++++++++++++++++- src/libslic3r/GCode/ToolOrdering.cpp | 10 ++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 011bf3fc42..6ae703a20f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1,4 +1,5 @@ #include "libslic3r.h" +#include "I18N.hpp" #include "GCode.hpp" #include "ExtrusionEntity.hpp" #include "EdgeGrid.hpp" @@ -36,6 +37,11 @@ namespace Slic3r { +//! macro used to mark string used at localization, +//! return same string +#define L(s) (s) +#define _(s) Slic3r::I18N::translate(s) + // Only add a newline in case the current G-code does not end with a newline. static inline void check_add_eol(std::string &gcode) { @@ -405,7 +411,8 @@ std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, assert(m_layer_idx >= 0 && size_t(m_layer_idx) <= m_tool_changes.size()); if (! m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { if (m_layer_idx < (int)m_tool_changes.size()) { - assert(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()); + if (! (size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) + throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer."); gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id); } m_brim_done = true; @@ -436,6 +443,14 @@ std::vector GCode::collect_layers_to_print(const PrintObjec size_t idx_object_layer = 0; size_t idx_support_layer = 0; while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { + // Let's make sure that the last layer is not empty, so we don't build on top of it. + if (! layers_to_print.empty() + && (! layers_to_print.back().object_layer || ! layers_to_print.back().object_layer->has_extrusions()) + && (! layers_to_print.back().support_layer || ! layers_to_print.back().support_layer->has_extrusions())) + throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + + _(L("Object name: ")) + object.model_object()->name + "\n" + _(L("Print z: ")) + + std::to_string(layers_to_print.back().print_z())); + LayerToPrint layer_to_print; layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer ++] : nullptr; layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer ++] : nullptr; @@ -448,6 +463,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec -- idx_object_layer; } } + layers_to_print.emplace_back(layer_to_print); } diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index f10b45723a..18437c8e16 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -78,8 +78,13 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool zs.emplace_back(layer->print_z); for (auto layer : object->support_layers()) zs.emplace_back(layer->print_z); - if (! object->layers().empty()) - object_bottom_z = object->layers().front()->print_z - object->layers().front()->height; + + // Find first object layer that is not empty and save its print_z + for (const Layer* layer : object->layers()) + if (layer->has_extrusions()) { + object_bottom_z = layer->print_z - layer->height; + break; + } } this->initialize_layers(zs); } @@ -324,6 +329,7 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ m_layer_tools[j].has_wipe_tower = true; } else { LayerTools <_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new); + //LayerTools <_prev = m_layer_tools[j]; LayerTools <_next = m_layer_tools[j + 1]; assert(! m_layer_tools[j - 1].extruders.empty() && ! lt_next.extruders.empty()); // FIXME: Following assert tripped when running combine_infill.t. I decided to comment it out for now. From eaccd737567f239ee2fdea0384b2b0050ec60e2e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 2 Aug 2019 17:53:12 +0200 Subject: [PATCH 485/627] Added InvalidItem() to ObjectDataViewModel to controling if item till exist during multiple deleting + some code cleaning --- src/slic3r/GUI/GUI_ObjectList.cpp | 45 +++++++++++-------------------- src/slic3r/GUI/wxExtensions.cpp | 12 +++++++++ src/slic3r/GUI/wxExtensions.hpp | 2 ++ 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index fede48166c..279a439eda 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1755,7 +1755,19 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) if (type & itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) m_objects_model->DeleteWarningIcon(m_objects_model->GetParent(item)); + // If last two Instances of object is selected, show the message about impossible action + bool show_msg = false; + if (type & itInstance) { + wxDataViewItemArray instances; + m_objects_model->GetChildren(m_objects_model->GetParent(item), instances); + if (instances.Count() == 2 && IsSelected(instances[0]) && IsSelected(instances[1])) + show_msg = true; + } + m_objects_model->Delete(item); + + if (show_msg) + Slic3r::GUI::show_error(nullptr, _(L("From Object List You can't delete the last intance from object."))); } void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item) @@ -1838,7 +1850,7 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con if (vol->is_model_part()) ++solid_cnt; if (volume->is_model_part() && solid_cnt == 1) { - Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last solid part from object."))); + Slic3r::GUI::show_error(nullptr, _(L("From Object List You can't delete the last solid part from object."))); return false; } @@ -1857,7 +1869,7 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con } else if (type == itInstance) { if (object->instances.size() == 1) { - Slic3r::GUI::show_error(nullptr, _(L("You can't delete the last intance from object."))); + Slic3r::GUI::show_error(nullptr, _(L("From Object List You can't delete the last intance from object."))); return false; } @@ -2404,6 +2416,8 @@ void ObjectList::remove() for (auto& item : sels) { + if (m_objects_model->InvalidItem(item)) // item can be deleted for this moment (like last 2 Instances or Volumes) + continue; if (m_objects_model->GetParent(item) == wxDataViewItem(0)) delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); else { @@ -3150,33 +3164,6 @@ void ObjectList::last_volume_is_deleted(const int obj_idx) volume->config.set_key_value("extruder", new ConfigOptionInt(0)); } -/* #lm_FIXME_delete_after_testing -void ObjectList::update_settings_items() -{ - m_prevent_canvas_selection_update = true; - wxDataViewItemArray sel; - GetSelections(sel); // stash selection - - wxDataViewItemArray items; - m_objects_model->GetChildren(wxDataViewItem(0), items); - - for (auto& item : items) { - const wxDataViewItem& settings_item = m_objects_model->GetSettingsItem(item); - select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); - - // If settings item was deleted from the list, - // it's need to be deleted from selection array, if it was there - if (settings_item != m_objects_model->GetSettingsItem(item) && - sel.Index(settings_item) != wxNOT_FOUND) { - sel.Remove(settings_item); - } - } - - // restore selection: - SetSelections(sel); - m_prevent_canvas_selection_update = false; -} -*/ void ObjectList::update_and_show_object_settings_item() { const wxDataViewItem item = GetSelection(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index a33b7248c3..01acb78534 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1325,6 +1325,18 @@ int ObjectDataViewModel::GetRowByItem(const wxDataViewItem& item) const return -1; } +bool ObjectDataViewModel::InvalidItem(const wxDataViewItem& item) +{ + if (!item) + return true; + + ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); + if (!node || node->invalid()) + return true; + + return false; +} + wxString ObjectDataViewModel::GetName(const wxDataViewItem &item) const { ObjectDataViewModelNode *node = (ObjectDataViewModelNode*)item.GetID(); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 2a8d8fccf5..5bc81b82d4 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -355,6 +355,7 @@ public: #ifndef NDEBUG bool valid(); #endif /* NDEBUG */ + bool invalid() const { return m_idx < -1; } private: friend class ObjectDataViewModel; @@ -417,6 +418,7 @@ public: void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx); int GetRowByItem(const wxDataViewItem& item) const; bool IsEmpty() { return m_objects.empty(); } + bool InvalidItem(const wxDataViewItem& item); // helper method for wxLog From 77df54947bb4a7eed2055f15faf08f075cc1330c Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Sat, 3 Aug 2019 08:51:03 +0200 Subject: [PATCH 486/627] Follow-up of c791ba776f96c8fa18c435c12dad2c726af46c3b -> Estimated times for color print layed-out as 'time for color (remaining time at color start)' --- src/libslic3r/GCodeTimeEstimator.cpp | 31 ++++++++++++++-------------- src/libslic3r/GCodeTimeEstimator.hpp | 8 +++---- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index 87f8bca65d..8a2e1266a8 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -694,38 +694,39 @@ namespace Slic3r { return m_color_times; } - std::vector GCodeTimeEstimator::get_color_times_dhms(bool include_absolute) const + std::vector GCodeTimeEstimator::get_color_times_dhms(bool include_remaining) const { std::vector ret; float total_time = 0.0f; for (float t : m_color_times) { - total_time += t; - std::string time = ""; - if (include_absolute) - time += _get_time_dhms(total_time) + " ("; - time += _get_time_dhms(t); - if (include_absolute) + std::string time = _get_time_dhms(t); + if (include_remaining) + { + time += " ("; + time += _get_time_dhms(m_time - total_time); time += ")"; + } + total_time += t; ret.push_back(time); } return ret; } - std::vector GCodeTimeEstimator::get_color_times_minutes(bool include_absolute) const + std::vector GCodeTimeEstimator::get_color_times_minutes(bool include_remaining) const { std::vector ret; float total_time = 0.0f; for (float t : m_color_times) { - total_time += t; - std::string time = ""; - if (include_absolute) - time += _get_time_minutes(total_time) + " ("; - time += _get_time_minutes(t); - if (include_absolute) + std::string time = _get_time_minutes(t); + if (include_remaining) + { + time += " ("; + time += _get_time_minutes(m_time - total_time); time += ")"; - ret.push_back(time); + } + total_time += t; } return ret; } diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index 792fb72a51..8d794af1e4 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -350,12 +350,12 @@ namespace Slic3r { std::vector get_color_times() const; // Returns the estimated time, in format DDd HHh MMm SSs, for each color - // If include_absolute==true the strings will be formatted as: "absolute time (relative time)" - std::vector get_color_times_dhms(bool include_absolute) const; + // If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)" + std::vector get_color_times_dhms(bool include_remaining) const; // Returns the estimated time, in minutes (integer), for each color - // If include_absolute==true the strings will be formatted as: "absolute time (relative time)" - std::vector get_color_times_minutes(bool include_absolute) const; + // If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)" + std::vector get_color_times_minutes(bool include_remaining) const; // Return an estimate of the memory consumed by the time estimator. size_t memory_used() const; From 8078e00c1315a120860a359a6e5c0b415699e485 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Sat, 3 Aug 2019 09:07:38 +0200 Subject: [PATCH 487/627] Fixed automatic update of perspective camera --- src/slic3r/GUI/Camera.cpp | 137 ++++++++++++++++++++------------------ src/slic3r/GUI/Camera.hpp | 7 +- 2 files changed, 76 insertions(+), 68 deletions(-) diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 242d00a071..8e3a6d1f12 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -22,10 +22,10 @@ namespace Slic3r { namespace GUI { const double Camera::DefaultDistance = 1000.0; -double Camera::FrustrumMinZSize = 50.0; +double Camera::FrustrumMinZRange = 50.0; +double Camera::FrustrumMinNearZ = 100.0; double Camera::FrustrumZMargin = 10.0; -double Camera::FovMinDeg = 0.5; -double Camera::FovMaxDeg = 75.0; +double Camera::MaxFovDeg = 60.0; Camera::Camera() : phi(45.0f) @@ -186,7 +186,8 @@ void Camera::apply_view_matrix() const void Camera::apply_projection(const BoundingBoxf3& box) const { - m_distance = DefaultDistance; + set_distance(DefaultDistance); + double w = 0.0; double h = 0.0; @@ -194,15 +195,14 @@ void Camera::apply_projection(const BoundingBoxf3& box) const { m_frustrum_zs = calc_tight_frustrum_zs_around(box); - w = (double)m_viewport[2]; - h = (double)m_viewport[3]; + w = 0.5 * (double)m_viewport[2]; + h = 0.5 * (double)m_viewport[3]; - double two_zoom = 2.0 * m_zoom; - if (two_zoom != 0.0) + if (m_zoom != 0.0) { - double inv_two_zoom = 1.0 / two_zoom; - w *= inv_two_zoom; - h *= inv_two_zoom; + double inv_zoom = 1.0 / m_zoom; + w *= inv_zoom; + h *= inv_zoom; } switch (m_type) @@ -226,21 +226,16 @@ void Camera::apply_projection(const BoundingBoxf3& box) const if (m_type == Perspective) { - double fov_rad = 2.0 * std::atan(h / m_frustrum_zs.first); - double fov_deg = Geometry::rad2deg(fov_rad); + double fov_deg = Geometry::rad2deg(2.0 * std::atan(h / m_frustrum_zs.first)); // adjust camera distance to keep fov in a limited range - if (fov_deg > FovMaxDeg + 0.001) + if (fov_deg > MaxFovDeg) { - double new_near_z = h / ::tan(0.5 * Geometry::deg2rad(FovMaxDeg)); - m_distance += (new_near_z - m_frustrum_zs.first); - apply_view_matrix(); - } - else if (fov_deg < FovMinDeg - 0.001) - { - double new_near_z = h / ::tan(0.5 * Geometry::deg2rad(FovMinDeg)); - m_distance += (new_near_z - m_frustrum_zs.first); - apply_view_matrix(); + double delta_z = h / ::tan(0.5 * Geometry::deg2rad(MaxFovDeg)) - m_frustrum_zs.first; + if (delta_z > 0.001) + set_distance(m_distance + delta_z); + else + break; } else break; @@ -328,42 +323,50 @@ void Camera::debug_render() const std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const { - std::pair ret = std::make_pair(DBL_MAX, -DBL_MAX); + std::pair ret; - Vec3d bb_min = box.min; - Vec3d bb_max = box.max; - - // box vertices in world space - std::vector vertices; - vertices.reserve(8); - vertices.push_back(bb_min); - vertices.emplace_back(bb_max(0), bb_min(1), bb_min(2)); - vertices.emplace_back(bb_max(0), bb_max(1), bb_min(2)); - vertices.emplace_back(bb_min(0), bb_max(1), bb_min(2)); - vertices.emplace_back(bb_min(0), bb_min(1), bb_max(2)); - vertices.emplace_back(bb_max(0), bb_min(1), bb_max(2)); - vertices.push_back(bb_max); - vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2)); - - // set the Z range in eye coordinates (negative Zs are in front of the camera) - for (const Vec3d& v : vertices) + while (true) { - double z = -(m_view_matrix * v)(2); - ret.first = std::min(ret.first, z); - ret.second = std::max(ret.second, z); - } + ret = std::make_pair(DBL_MAX, -DBL_MAX); - // apply margin - ret.first -= FrustrumZMargin; - ret.second += FrustrumZMargin; + // box vertices in world space + std::vector vertices; + vertices.reserve(8); + vertices.push_back(box.min); + vertices.emplace_back(box.max(0), box.min(1), box.min(2)); + vertices.emplace_back(box.max(0), box.max(1), box.min(2)); + vertices.emplace_back(box.min(0), box.max(1), box.min(2)); + vertices.emplace_back(box.min(0), box.min(1), box.max(2)); + vertices.emplace_back(box.max(0), box.min(1), box.max(2)); + vertices.push_back(box.max); + vertices.emplace_back(box.min(0), box.max(1), box.max(2)); - // ensure min size - if (ret.second - ret.first < FrustrumMinZSize) - { - double mid_z = 0.5 * (ret.first + ret.second); - double half_size = 0.5 * FrustrumMinZSize; - ret.first = mid_z - half_size; - ret.second = mid_z + half_size; + // set the Z range in eye coordinates (negative Zs are in front of the camera) + for (const Vec3d& v : vertices) + { + double z = -(m_view_matrix * v)(2); + ret.first = std::min(ret.first, z); + ret.second = std::max(ret.second, z); + } + + // apply margin + ret.first -= FrustrumZMargin; + ret.second += FrustrumZMargin; + + // ensure min size + if (ret.second - ret.first < FrustrumMinZRange) + { + double mid_z = 0.5 * (ret.first + ret.second); + double half_size = 0.5 * FrustrumMinZRange; + ret.first = mid_z - half_size; + ret.second = mid_z + half_size; + } + + if (ret.first >= FrustrumMinNearZ) + break; + + // ensure min Near Z + set_distance(m_distance + FrustrumMinNearZ - ret.first); } return ret; @@ -385,21 +388,19 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca Vec3d up = get_dir_up(); Vec3d forward = get_dir_forward(); - Vec3d bb_min = box.min; - Vec3d bb_max = box.max; Vec3d bb_center = box.center(); // box vertices in world space std::vector vertices; vertices.reserve(8); - vertices.push_back(bb_min); - vertices.emplace_back(bb_max(0), bb_min(1), bb_min(2)); - vertices.emplace_back(bb_max(0), bb_max(1), bb_min(2)); - vertices.emplace_back(bb_min(0), bb_max(1), bb_min(2)); - vertices.emplace_back(bb_min(0), bb_min(1), bb_max(2)); - vertices.emplace_back(bb_max(0), bb_min(1), bb_max(2)); - vertices.push_back(bb_max); - vertices.emplace_back(bb_min(0), bb_max(1), bb_max(2)); + vertices.push_back(box.min); + vertices.emplace_back(box.max(0), box.min(1), box.min(2)); + vertices.emplace_back(box.max(0), box.max(1), box.min(2)); + vertices.emplace_back(box.min(0), box.max(1), box.min(2)); + vertices.emplace_back(box.min(0), box.min(1), box.max(2)); + vertices.emplace_back(box.max(0), box.min(1), box.max(2)); + vertices.push_back(box.max); + vertices.emplace_back(box.min(0), box.max(1), box.max(2)); double max_x = 0.0; double max_y = 0.0; @@ -430,6 +431,12 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca return std::min((double)canvas_w / (2.0 * max_x), (double)canvas_h / (2.0 * max_y)); } +void Camera::set_distance(double distance) const +{ + m_distance = distance; + apply_view_matrix(); +} + } // GUI } // Slic3r diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 79e87c7264..839d0d6cf1 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -10,10 +10,10 @@ namespace GUI { struct Camera { static const double DefaultDistance; - static double FrustrumMinZSize; + static double FrustrumMinZRange; + static double FrustrumMinNearZ; static double FrustrumZMargin; - static double FovMinDeg; - static double FovMaxDeg; + static double MaxFovDeg; enum EType : unsigned char { @@ -101,6 +101,7 @@ private: // the camera MUST be outside of the bounding box in eye coordinate of the given box std::pair calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const; double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const; + void set_distance(double distance) const; }; } // GUI From 0de6e532197bbfda4665e4b5441651e5e740afd1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 2 Aug 2019 19:45:13 +0200 Subject: [PATCH 488/627] Followup of 6ab1cec - empty layers are ok if there are only other empty layers on top of them Also fixed a possible crash in Print.cpp when preparing the wipe tower layers --- src/libslic3r/GCode.cpp | 18 ++++++++++-------- src/libslic3r/Print.cpp | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 6ae703a20f..9e5d5c4fab 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -443,14 +443,6 @@ std::vector GCode::collect_layers_to_print(const PrintObjec size_t idx_object_layer = 0; size_t idx_support_layer = 0; while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { - // Let's make sure that the last layer is not empty, so we don't build on top of it. - if (! layers_to_print.empty() - && (! layers_to_print.back().object_layer || ! layers_to_print.back().object_layer->has_extrusions()) - && (! layers_to_print.back().support_layer || ! layers_to_print.back().support_layer->has_extrusions())) - throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + - _(L("Object name: ")) + object.model_object()->name + "\n" + _(L("Print z: ")) + - std::to_string(layers_to_print.back().print_z())); - LayerToPrint layer_to_print; layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer ++] : nullptr; layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer ++] : nullptr; @@ -464,6 +456,16 @@ std::vector GCode::collect_layers_to_print(const PrintObjec } } + // Let's make sure that the last layer is not empty, so we don't build on top of it. + if (! layers_to_print.empty() + && ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) + || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions())) + && (! layers_to_print.back().object_layer || ! layers_to_print.back().object_layer->has_extrusions()) + && (! layers_to_print.back().support_layer || ! layers_to_print.back().support_layer->has_extrusions())) + throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + + _(L("Object name: ")) + object.model_object()->name + "\n" + _(L("Print z: ")) + + std::to_string(layers_to_print.back().print_z())); + layers_to_print.emplace_back(layer_to_print); } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index c423afeb94..e53f498106 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1723,7 +1723,7 @@ void Print::_make_wipe_tower() break; lt.has_support = true; // Insert the new support layer. - double height = lt.print_z - m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z; + double height = lt.print_z - (i == 0 ? 0. : m_wipe_tower_data.tool_ordering.layer_tools()[i-1].print_z); //FIXME the support layer ID is set to -1, as Vojtech hopes it is not being used anyway. it_layer = m_objects.front()->insert_support_layer(it_layer, -1, height, lt.print_z, lt.print_z - 0.5 * height); ++ it_layer; From f712e5fcf4cb44d61fd9f095058b2609e9f169be Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 5 Aug 2019 08:44:55 +0200 Subject: [PATCH 489/627] Implemented set printable state for ObjectList --- src/slic3r/GUI/GUI_ObjectList.cpp | 21 ++++++++++++++ src/slic3r/GUI/GUI_ObjectList.hpp | 1 + src/slic3r/GUI/Selection.cpp | 2 ++ src/slic3r/GUI/wxExtensions.cpp | 46 ++++++++++++++++++++++++++++++- src/slic3r/GUI/wxExtensions.hpp | 5 ++++ 5 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 9602bf4f76..c0af7a41e7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2273,6 +2273,8 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) select_item(m_objects_model->AddInstanceChild(m_objects_model->GetItemById(obj_idx), print_idicator)); } + else + m_objects_model->SetPrintableState(obj_idx, model_object->instances[0]->is_printable() ? piPrintable : piUnprintable); // add settings to the object, if it has those add_settings_item(item, &model_object->config); @@ -3632,6 +3634,25 @@ void ObjectList::update_after_undo_redo() m_prevent_canvas_selection_update = false; } +void ObjectList::update_printable_state(int obj_idx, int instance_idx) +{ + ModelObject* object = (*m_objects)[obj_idx]; + PrintIndicator printable = piUndef; + + if (object->instances.size() == 1) + { + printable = object->instances[0]->printable ? piPrintable : piUnprintable; + instance_idx = -1; + } + else + { + m_objects_model->SetPrintableState(obj_idx, piUndef); + printable = object->instances[instance_idx]->printable ? piPrintable : piUnprintable; + } + + select_item(m_objects_model->SetPrintableState(obj_idx, printable, instance_idx)); +} + ModelObject* ObjectList::object(const int obj_idx) const { if (obj_idx < 0) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index cbffaaa0cf..1cfca1a42a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -349,6 +349,7 @@ public: void msw_rescale(); void update_after_undo_redo(); + void update_printable_state(int obj_idx, int instance_idx); private: #ifdef __WXOSX__ diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 4e7b4e4ad9..630ecd1d4a 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1479,6 +1479,8 @@ void Selection::toggle_instance_printable_state() if ((volume->object_idx() == obj_idx) && (volume->instance_idx() == instance_idx)) volume->printable = instance->printable; } + + wxGetApp().obj_list()->update_printable_state(obj_idx, instance_idx); } } } diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index f6dc9082ee..7ddf1c2732 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -799,12 +799,28 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem& paren ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); + const bool just_created = inst_root_node->GetChildren().Count() == 0; + // Add instance nodes ObjectDataViewModelNode *instance_node = nullptr; size_t counter = 0; while (counter < print_indicator.size()) { instance_node = new ObjectDataViewModelNode(inst_root_node, itInstance); - instance_node->set_printable_icon(print_indicator[counter] ? piPrintable : piUnprintable); + + // if InstanceRoot item is just created and start to adding Instances + if (just_created && counter == 0) + { + ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); + + // use object's printable state to first instance + instance_node->set_printable_icon(obj_node->IsPrintable()); + + // and set printable state for object_node to piUndef + obj_node->set_printable_icon(piUndef); + ItemChanged(parent_item); + } + else + instance_node->set_printable_icon(print_indicator[counter] ? piPrintable : piUnprintable); inst_root_node->Append(instance_node); // notify control const wxDataViewItem instance_item((void*)instance_node); @@ -915,11 +931,13 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) ItemDeleted(parent, item); ObjectDataViewModelNode *last_instance_node = node_parent->GetNthChild(0); + PrintIndicator last_instance_printable = last_instance_node->IsPrintable(); node_parent->GetChildren().Remove(last_instance_node); delete last_instance_node; ItemDeleted(parent, wxDataViewItem(last_instance_node)); ObjectDataViewModelNode *obj_node = node_parent->GetParent(); + obj_node->set_printable_icon(last_instance_printable); obj_node->GetChildren().Remove(node_parent); delete node_parent; ret_item = wxDataViewItem(obj_node); @@ -1041,9 +1059,12 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par const int inst_cnt = inst_root_node->GetChildCount(); const bool delete_inst_root_item = inst_cnt - num < 2 ? true : false; + PrintIndicator last_inst_printable = piUndef; + int stop = delete_inst_root_item ? 0 : inst_cnt - num; for (int i = inst_cnt - 1; i >= stop;--i) { ObjectDataViewModelNode *last_instance_node = inst_root_node->GetNthChild(i); + if (i==0) last_inst_printable = last_instance_node->IsPrintable(); inst_root_node->GetChildren().Remove(last_instance_node); delete last_instance_node; ItemDeleted(inst_root_item, wxDataViewItem(last_instance_node)); @@ -1052,7 +1073,9 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par if (delete_inst_root_item) { ret_item = parent_item; parent_node->GetChildren().Remove(inst_root_node); + parent_node->set_printable_icon(last_inst_printable); ItemDeleted(parent_item, inst_root_item); + ItemChanged(parent_item); #ifndef __WXGTK__ if (parent_node->GetChildCount() == 0) parent_node->m_container = false; @@ -1621,6 +1644,27 @@ void ObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const Slic3r ItemChanged(item); } +wxDataViewItem ObjectDataViewModel::SetPrintableState( + int obj_idx, + PrintIndicator printable/* = piUndef*/, + int subobj_idx /* = -1*/, + ItemType subobj_type/* = itInstance*/) +{ + wxDataViewItem item = wxDataViewItem(0); + if (subobj_idx < 0) + item = GetItemById(obj_idx); + else + item = subobj_type&itInstance ? GetItemByInstanceId(obj_idx, subobj_idx) : + GetItemByVolumeId(obj_idx, subobj_idx); + + ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); + if (!node) + return wxDataViewItem(0); + node->set_printable_icon(printable); + + return item; +} + void ObjectDataViewModel::Rescale() { wxDataViewItemArray all_items; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index c0df69c5cf..b04c6c9c06 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -324,6 +324,7 @@ public: void SetIdx(const int& idx); int GetIdx() const { return m_idx; } t_layer_height_range GetLayerRange() const { return m_layer_range; } + PrintIndicator IsPrintable() const { return m_printable; } // use this function only for childrens void AssignAllVal(ObjectDataViewModelNode& from_node) @@ -482,6 +483,10 @@ public: void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } void SetWarningBitmap(wxBitmap* bitmap) { m_warning_bmp = bitmap; } void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); + wxDataViewItem SetPrintableState( int obj_idx, + PrintIndicator printable = piUndef, + int subobj_idx = -1, + ItemType subobj_type = itInstance); void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } // Rescale bitmaps for existing Items From e8f27c6407c4339ea73d5561095cfc338bb3e207 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 5 Aug 2019 10:05:28 +0200 Subject: [PATCH 490/627] Added call a toggle printable property for instance from ObjectList --- src/slic3r/GUI/GUI_ObjectList.cpp | 22 +++++++++++++++++----- src/slic3r/GUI/GUI_ObjectList.hpp | 2 ++ src/slic3r/GUI/wxExtensions.cpp | 3 ++- src/slic3r/GUI/wxExtensions.hpp | 3 +-- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c714b6797c..6ab87bd66f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -752,8 +752,8 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) #endif // __WXOSX__ const wxString title = col->GetTitle(); - if (title == " "); - // show_context_menu(); + if (title == " ") + toggle_printable_state(item); else if (title == _("Editing")) show_context_menu(); else if (title == _("Name")) @@ -2276,7 +2276,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) select_item(m_objects_model->AddInstanceChild(m_objects_model->GetItemById(obj_idx), print_idicator)); } else - m_objects_model->SetPrintableState(obj_idx, model_object->instances[0]->is_printable() ? piPrintable : piUnprintable); + m_objects_model->SetPrintableState(model_object->instances[0]->is_printable() ? piPrintable : piUnprintable, obj_idx); // add settings to the object, if it has those add_settings_item(item, &model_object->config); @@ -3623,11 +3623,23 @@ void ObjectList::update_printable_state(int obj_idx, int instance_idx) } else { - m_objects_model->SetPrintableState(obj_idx, piUndef); + m_objects_model->SetPrintableState(piUndef, obj_idx); printable = object->instances[instance_idx]->printable ? piPrintable : piUnprintable; } - select_item(m_objects_model->SetPrintableState(obj_idx, printable, instance_idx)); + m_objects_model->SetPrintableState(printable, obj_idx, instance_idx); +} + +void ObjectList::toggle_printable_state(wxDataViewItem item) +{ + const ItemType type = m_objects_model->GetItemType(item); + if (!(type&(itObject|itInstance/*|itVolume*/))) + return; + + wxGetApp().plater()->canvas3D()->get_selection().toggle_instance_printable_state(); + + // update scene + wxGetApp().plater()->update(); } ModelObject* ObjectList::object(const int obj_idx) const diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 1cfca1a42a..bdec060818 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -349,7 +349,9 @@ public: void msw_rescale(); void update_after_undo_redo(); + //update printable state for item from objects model void update_printable_state(int obj_idx, int instance_idx); + void toggle_printable_state(wxDataViewItem item); private: #ifdef __WXOSX__ diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 3da6997a79..524861133e 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1657,8 +1657,8 @@ void ObjectDataViewModel::SetVolumeType(const wxDataViewItem &item, const Slic3r } wxDataViewItem ObjectDataViewModel::SetPrintableState( + PrintIndicator printable, int obj_idx, - PrintIndicator printable/* = piUndef*/, int subobj_idx /* = -1*/, ItemType subobj_type/* = itInstance*/) { @@ -1673,6 +1673,7 @@ wxDataViewItem ObjectDataViewModel::SetPrintableState( if (!node) return wxDataViewItem(0); node->set_printable_icon(printable); + ItemChanged(item); return item; } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 5b9bdee023..242a487d1c 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -485,8 +485,7 @@ public: void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } void SetWarningBitmap(wxBitmap* bitmap) { m_warning_bmp = bitmap; } void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); - wxDataViewItem SetPrintableState( int obj_idx, - PrintIndicator printable = piUndef, + wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx, int subobj_idx = -1, ItemType subobj_type = itInstance); From 9b5a577c073e1d5ee48741b9441af0016695d2ed Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 5 Aug 2019 11:02:56 +0200 Subject: [PATCH 491/627] Fixed OnContextMenu() under OSX --- src/slic3r/GUI/GUI_ObjectList.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 6ab87bd66f..9de9323f14 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -738,18 +738,16 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) wxDataViewColumn* col; const wxPoint pt = get_mouse_position_in_control(); HitTest(pt, item, col); - if (!item) #ifdef __WXOSX__ // temporary workaround for OSX - // after Yosemite OS X version, HitTest return undefined item - item = GetSelection(); - if (item) - show_context_menu(); - else - printf("undefined item\n"); - return; -#else - return; + // after Yosemite OS X version, HitTest return undefined item + if (!item) item = GetSelection(); #endif // __WXOSX__ + + if (!item) { + printf("undefined item\n"); + return; + } + const wxString title = col->GetTitle(); if (title == " ") From 3efae8a03e54612b1af6372755f7bbc90dc479d5 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 5 Aug 2019 13:01:23 +0200 Subject: [PATCH 492/627] Added a memory logging function for Mac and Linux --- src/libslic3r/utils.cpp | 44 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index f9a0443380..0718ebf0e2 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -13,9 +13,13 @@ #include #include #include + #include #ifdef BSD #include #endif + #ifdef __APPLE__ + #include + #endif #endif #include @@ -432,7 +436,6 @@ std::string format_memsize_MB(size_t n) } #ifdef WIN32 - #ifndef PROCESS_MEMORY_COUNTERS_EX // MingW32 doesn't have this struct in psapi.h typedef struct _PROCESS_MEMORY_COUNTERS_EX { @@ -464,12 +467,51 @@ std::string log_memory_info() } return out; } +#elif defined(__linux__) or defined(__APPLE__) +std::string log_memory_info() +{ + std::string out = " Unable to get current memory usage."; + // Get current memory usage. +#ifdef __APPLE__ + struct mach_task_basic_info info; + mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; + if ( task_info( mach_task_self( ), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount ) == KERN_SUCCESS ) + out = " Memory usage: resident: " + format_memsize_MB((size_t)info.resident_size); +#else // i.e. __linux__ + + size_t tSize = 0, resident = 0, share = 0; + std::ifstream buffer("/proc/self/statm"); + if (buffer) { + if ((buffer >> tSize >> resident >> share)) { + size_t page_size = (size_t)sysconf(_SC_PAGE_SIZE); // in case x86-64 is configured to use 2MB pages + size_t rss = resident * page_size; + out = " Memory usage: resident: " + format_memsize_MB(rss); + out += " shared: " + format_memsize_MB(share * page_size); + out += " private: " + format_memsize_MB(rss - share * page_size); + } + } +#endif + // Now get peak memory usage. + rusage memory_info; + if (getrusage(RUSAGE_SELF, &memory_info) != 0) + out += " Could not get peak memory usage."; + else { + size_t peak_mem_usage = (size_t)memory_info.ru_maxrss; + #ifdef __linux + peak_mem_usage *= 1024L;// getrusage returns the value in kB on linux + #endif + out += " Peak Memory Usage: " + format_memsize_MB(peak_mem_usage); + } + + return out; +} #else std::string log_memory_info() { return std::string(); } + #endif // Returns the size of physical memory (RAM) in bytes. From 731e5abd887b9f6c421d4a6c2e6ea0b27b4d9431 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 5 Aug 2019 14:30:32 +0200 Subject: [PATCH 493/627] Fixed a regression issue where excessive memory was allocated for the GLVolumes before sending to the GPU driver. The following commits were partially reverted: 4269c8b23cb6878a20f468a916d0079ecaf647a0 Removed GLVolume non-VBO rendering d15698e21e86a4e896bbb5f3c59440ec2dc721e9 GLVolume and GLIndexedVertexArray refactored to send data to gpu at the first render call Namely, the GLVolume buffers are "shrink to size"'d before sending their content to the OpenGL driver, and the vertex buffers are populated as quickly as possible from the GLVolume, so that the same buffer is not kept twice in RAM on systems, where the RAM is shared with the graphics card. Also the memory allocation reporting was improved for the GLVolumes. --- src/libslic3r/GCode.cpp | 9 +- src/slic3r/GUI/3DScene.cpp | 93 ++++++++++++-------- src/slic3r/GUI/3DScene.hpp | 155 ++++++++++++++++++++++------------ src/slic3r/GUI/GLCanvas3D.cpp | 60 +++++++++---- src/slic3r/GUI/Selection.cpp | 1 + 5 files changed, 208 insertions(+), 110 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index bc37300269..21faffe537 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -561,11 +561,11 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ } if (print->config().remaining_times.value) { - BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for normal mode"; + BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for normal mode" << log_memory_info(); m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f); m_normal_time_estimator.reset(); if (m_silent_time_estimator_enabled) { - BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for silent mode"; + BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for silent mode" << log_memory_info(); m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f); m_silent_time_estimator.reset(); } @@ -573,7 +573,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ // starts analyzer calculations if (m_enable_analyzer) { - BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data"; + BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data" << log_memory_info(); m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); }); m_analyzer.reset(); } @@ -1820,7 +1820,8 @@ void GCode::process_layer( ", time estimator memory: " << format_memsize_MB(m_normal_time_estimator.memory_used() + m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0) << ", analyzer memory: " << - format_memsize_MB(m_analyzer.memory_used()); + format_memsize_MB(m_analyzer.memory_used()) << + log_memory_info(); } void GCode::apply_print_config(const PrintConfig &print_config) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index dba5958466..b9a79f3a91 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -12,6 +12,7 @@ #include "libslic3r/GCode/Analyzer.hpp" #include "slic3r/GUI/PresetBundle.hpp" #include "libslic3r/Format/STL.hpp" +#include "libslic3r/Utils.hpp" #include #include @@ -74,15 +75,19 @@ void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh) } } -void GLIndexedVertexArray::finalize_geometry() const +void GLIndexedVertexArray::finalize_geometry(bool opengl_initialized) { assert(this->vertices_and_normals_interleaved_VBO_id == 0); assert(this->triangle_indices_VBO_id == 0); assert(this->quad_indices_VBO_id == 0); - this->shrink_to_fit(); + if (! opengl_initialized) { + // Shrink the data vectors to conserve memory in case the data cannot be transfered to the OpenGL driver yet. + this->shrink_to_fit(); + return; + } - if (! empty()) { + if (! this->vertices_and_normals_interleaved.empty()) { glsafe(::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved.size() * 4, this->vertices_and_normals_interleaved.data(), GL_STATIC_DRAW)); @@ -124,13 +129,8 @@ void GLIndexedVertexArray::release_geometry() void GLIndexedVertexArray::render() const { - if (this->vertices_and_normals_interleaved_VBO_id == 0) - { - // sends data to gpu, if not done yet - finalize_geometry(); - if (this->vertices_and_normals_interleaved_VBO_id == 0) - return; - } + assert(this->vertices_and_normals_interleaved_VBO_id != 0); + assert(this->triangle_indices_VBO_id != 0 || this->quad_indices_VBO_id != 0); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); @@ -161,13 +161,8 @@ void GLIndexedVertexArray::render( const std::pair& tverts_range, const std::pair& qverts_range) const { - if (this->vertices_and_normals_interleaved_VBO_id == 0) - { - // sends data to gpu, if not done yet - finalize_geometry(); - if (this->vertices_and_normals_interleaved_VBO_id == 0) - return; - } + assert(this->vertices_and_normals_interleaved_VBO_id != 0); + assert(this->triangle_indices_VBO_id != 0 || this->quad_indices_VBO_id != 0); // Render using the Vertex Buffer Objects. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_and_normals_interleaved_VBO_id)); @@ -415,30 +410,32 @@ bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == - bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposBasePool); } std::vector GLVolumeCollection::load_object( - const ModelObject* model_object, + const ModelObject *model_object, int obj_idx, - const std::vector& instance_idxs, - const std::string& color_by) + const std::vector &instance_idxs, + const std::string &color_by, + bool opengl_initialized) { std::vector volumes_idx; for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++volume_idx) for (int instance_idx : instance_idxs) - volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by)); + volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by, opengl_initialized)); return volumes_idx; } int GLVolumeCollection::load_object_volume( - const ModelObject* model_object, - int obj_idx, - int volume_idx, - int instance_idx, - const std::string& color_by) + const ModelObject *model_object, + int obj_idx, + int volume_idx, + int instance_idx, + const std::string &color_by, + bool opengl_initialized) { - const ModelVolume* model_volume = model_object->volumes[volume_idx]; - const int extruder_id = model_volume->extruder_id(); - const ModelInstance* instance = model_object->instances[instance_idx]; - const TriangleMesh& mesh = model_volume->mesh(); - float color[4]; + const ModelVolume *model_volume = model_object->volumes[volume_idx]; + const int extruder_id = model_volume->extruder_id(); + const ModelInstance *instance = model_object->instances[instance_idx]; + const TriangleMesh &mesh = model_volume->mesh(); + float color[4]; memcpy(color, GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3); /* if (model_volume->is_support_blocker()) { color[0] = 1.0f; @@ -455,6 +452,7 @@ int GLVolumeCollection::load_object_volume( GLVolume& v = *this->volumes.back(); v.set_color_from_model_volume(model_volume); v.indexed_vertex_array.load_mesh(mesh); + v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); if (model_volume->is_model_part()) { @@ -475,13 +473,14 @@ int GLVolumeCollection::load_object_volume( // This function produces volumes for multiple instances in a single shot, // as some object specific mesh conversions may be expensive. void GLVolumeCollection::load_object_auxiliary( - const SLAPrintObject* print_object, + const SLAPrintObject *print_object, int obj_idx, // pairs of const std::vector>& instances, SLAPrintObjectStep milestone, // Timestamp of the last change of the milestone - size_t timestamp) + size_t timestamp, + bool opengl_initialized) { assert(print_object->is_step_done(milestone)); Transform3d mesh_trafo_inv = print_object->trafo().inverse(); @@ -495,6 +494,7 @@ void GLVolumeCollection::load_object_auxiliary( this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); GLVolume& v = *this->volumes.back(); v.indexed_vertex_array.load_mesh(mesh); + v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); v.geometry_id = std::pair(timestamp, model_instance.id().id); // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. @@ -511,7 +511,7 @@ void GLVolumeCollection::load_object_auxiliary( } int GLVolumeCollection::load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width) + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized) { if (depth < 0.01f) return int(this->volumes.size() - 1); @@ -564,6 +564,7 @@ int GLVolumeCollection::load_wipe_tower_preview( this->volumes.emplace_back(new GLVolume(color)); GLVolume& v = *this->volumes.back(); v.indexed_vertex_array.load_mesh(mesh); + v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); @@ -823,6 +824,27 @@ std::vector GLVolumeCollection::get_current_print_zs(bool active_only) c return print_zs; } +size_t GLVolumeCollection::cpu_memory_used() const +{ + size_t memsize = sizeof(*this) + this->volumes.capacity() * sizeof(GLVolume); + for (const GLVolume *volume : this->volumes) + memsize += volume->cpu_memory_used(); + return memsize; +} + +size_t GLVolumeCollection::gpu_memory_used() const +{ + size_t memsize = 0; + for (const GLVolume *volume : this->volumes) + memsize += volume->gpu_memory_used(); + return memsize; +} + +std::string GLVolumeCollection::log_memory_info() const +{ + return " (GLVolumeCollection RAM: " + format_memsize_MB(this->cpu_memory_used()) + " GPU: " + format_memsize_MB(this->gpu_memory_used()) + " Both: " + format_memsize_MB(this->gpu_memory_used()) + ")"; +} + // caller is responsible for supplying NO lines with zero length static void thick_lines_to_indexed_vertex_array( const Lines &lines, @@ -1598,6 +1620,7 @@ bool GLArrow::on_init() triangles.emplace_back(7, 13, 6); m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles)); + m_volume.indexed_vertex_array.finalize_geometry(true); return true; } @@ -1711,6 +1734,7 @@ bool GLCurvedArrow::on_init() triangles.emplace_back(vertices_per_level, 2 * vertices_per_level + 1, vertices_per_level + 1); m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles)); + m_volume.indexed_vertex_array.finalize_geometry(true); return true; } @@ -1737,6 +1761,7 @@ bool GLBed::on_init_from_file(const std::string& filename) m_filename = filename; m_volume.indexed_vertex_array.load_mesh(model.mesh()); + m_volume.indexed_vertex_array.finalize_geometry(true); float color[4] = { 0.235f, 0.235f, 0.235f, 1.0f }; set_color(color, 4); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 8ae57eeaea..3d89a3a5f1 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -64,7 +64,7 @@ public: vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) - {} + { assert(! rhs.has_VBOs()); } GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), triangle_indices(std::move(rhs.triangle_indices)), @@ -72,7 +72,7 @@ public: vertices_and_normals_interleaved_VBO_id(0), triangle_indices_VBO_id(0), quad_indices_VBO_id(0) - {} + { assert(! rhs.has_VBOs()); } ~GLIndexedVertexArray() { release_geometry(); } @@ -80,14 +80,17 @@ public: { assert(vertices_and_normals_interleaved_VBO_id == 0); assert(triangle_indices_VBO_id == 0); - assert(triangle_indices_VBO_id == 0); - this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; - this->triangle_indices = rhs.triangle_indices; - this->quad_indices = rhs.quad_indices; - this->m_bounding_box = rhs.m_bounding_box; - vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; - triangle_indices_size = rhs.triangle_indices_size; - quad_indices_size = rhs.quad_indices_size; + assert(quad_indices_VBO_id == 0); + assert(rhs.vertices_and_normals_interleaved == 0); + assert(rhs.triangle_indices_VBO_id == 0); + assert(rhs.quad_indices_VBO_id == 0); + this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; + this->triangle_indices = rhs.triangle_indices; + this->quad_indices = rhs.quad_indices; + this->m_bounding_box = rhs.m_bounding_box; + this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; + this->triangle_indices_size = rhs.triangle_indices_size; + this->quad_indices_size = rhs.quad_indices_size; return *this; } @@ -95,21 +98,24 @@ public: { assert(vertices_and_normals_interleaved_VBO_id == 0); assert(triangle_indices_VBO_id == 0); - assert(triangle_indices_VBO_id == 0); - this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); - this->triangle_indices = std::move(rhs.triangle_indices); - this->quad_indices = std::move(rhs.quad_indices); - this->m_bounding_box = std::move(rhs.m_bounding_box); - vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; - triangle_indices_size = rhs.triangle_indices_size; - quad_indices_size = rhs.quad_indices_size; + assert(quad_indices_VBO_id == 0); + assert(rhs.vertices_and_normals_interleaved == 0); + assert(rhs.triangle_indices_VBO_id == 0); + assert(rhs.quad_indices_VBO_id == 0); + this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); + this->triangle_indices = std::move(rhs.triangle_indices); + this->quad_indices = std::move(rhs.quad_indices); + this->m_bounding_box = std::move(rhs.m_bounding_box); + this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; + this->triangle_indices_size = rhs.triangle_indices_size; + this->quad_indices_size = rhs.quad_indices_size; return *this; } // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x) - mutable std::vector vertices_and_normals_interleaved; - mutable std::vector triangle_indices; - mutable std::vector quad_indices; + std::vector vertices_and_normals_interleaved; + std::vector triangle_indices; + std::vector quad_indices; // When the geometry data is loaded into the graphics card as Vertex Buffer Objects, // the above mentioned std::vectors are cleared and the following variables keep their original length. @@ -119,9 +125,9 @@ public: // IDs of the Vertex Array Objects, into which the geometry has been loaded. // Zero if the VBOs are not sent to GPU yet. - mutable unsigned int vertices_and_normals_interleaved_VBO_id{ 0 }; - mutable unsigned int triangle_indices_VBO_id{ 0 }; - mutable unsigned int quad_indices_VBO_id{ 0 }; + unsigned int vertices_and_normals_interleaved_VBO_id{ 0 }; + unsigned int triangle_indices_VBO_id{ 0 }; + unsigned int quad_indices_VBO_id{ 0 }; void load_mesh_full_shading(const TriangleMesh &mesh); void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } @@ -141,12 +147,12 @@ public: if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity()) this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6)); - this->vertices_and_normals_interleaved.push_back(nx); - this->vertices_and_normals_interleaved.push_back(ny); - this->vertices_and_normals_interleaved.push_back(nz); - this->vertices_and_normals_interleaved.push_back(x); - this->vertices_and_normals_interleaved.push_back(y); - this->vertices_and_normals_interleaved.push_back(z); + this->vertices_and_normals_interleaved.emplace_back(nx); + this->vertices_and_normals_interleaved.emplace_back(ny); + this->vertices_and_normals_interleaved.emplace_back(nz); + this->vertices_and_normals_interleaved.emplace_back(x); + this->vertices_and_normals_interleaved.emplace_back(y); + this->vertices_and_normals_interleaved.emplace_back(z); this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); m_bounding_box.merge(Vec3f(x, y, z).cast()); @@ -167,9 +173,9 @@ public: if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity()) this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3)); - this->triangle_indices.push_back(idx1); - this->triangle_indices.push_back(idx2); - this->triangle_indices.push_back(idx3); + this->triangle_indices.emplace_back(idx1); + this->triangle_indices.emplace_back(idx2); + this->triangle_indices.emplace_back(idx3); this->triangle_indices_size = this->triangle_indices.size(); }; @@ -180,17 +186,17 @@ public: if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity()) this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4)); - this->quad_indices.push_back(idx1); - this->quad_indices.push_back(idx2); - this->quad_indices.push_back(idx3); - this->quad_indices.push_back(idx4); + this->quad_indices.emplace_back(idx1); + this->quad_indices.emplace_back(idx2); + this->quad_indices.emplace_back(idx3); + this->quad_indices.emplace_back(idx4); this->quad_indices_size = this->quad_indices.size(); }; // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry() const; + void finalize_geometry(bool opengl_initialized); // Release the geometry data, release OpenGL VBOs. void release_geometry(); @@ -211,7 +217,7 @@ public: } // Shrink the internal storage to tighly fit the data stored. - void shrink_to_fit() const { + void shrink_to_fit() { this->vertices_and_normals_interleaved.shrink_to_fit(); this->triangle_indices.shrink_to_fit(); this->quad_indices.shrink_to_fit(); @@ -219,6 +225,22 @@ public: const BoundingBoxf3& bounding_box() const { return m_bounding_box; } + // Return an estimate of the memory consumed by this class. + size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); } + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const + { + size_t memsize = 0; + if (this->vertices_and_normals_interleaved_VBO_id != 0) + memsize += this->vertices_and_normals_interleaved_size * 4; + if (this->triangle_indices_VBO_id != 0) + memsize += this->triangle_indices_size * 4; + if (this->quad_indices_VBO_id != 0) + memsize += this->quad_indices_size * 4; + return memsize; + } + size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } + private: BoundingBoxf3 m_bounding_box; }; @@ -250,7 +272,7 @@ private: Geometry::Transformation m_volume_transformation; // Shift in z required by sla supports+pad - double m_sla_shift_z; + double m_sla_shift_z; // Bounding box of this volume, in unscaled coordinates. mutable BoundingBoxf3 m_transformed_bounding_box; // Whether or not is needed to recalculate the transformed bounding box. @@ -420,13 +442,22 @@ public: void render() const; void render(int color_id, int detection_id, int worldmatrix_id) const; - void finalize_geometry() { this->indexed_vertex_array.finalize_geometry(); } + void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); } void release_geometry() { this->indexed_vertex_array.release_geometry(); } void set_bounding_boxes_as_dirty() { m_transformed_bounding_box_dirty = true; m_transformed_convex_hull_bounding_box_dirty = true; } bool is_sla_support() const; bool is_sla_pad() const; + + // Return an estimate of the memory consumed by this class. + size_t cpu_memory_used() const { + //FIXME what to do wih m_convex_hull? + return sizeof(*this) - sizeof(this->indexed_vertex_array) + this->indexed_vertex_array.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + this->offsets.capacity() * sizeof(size_t); + } + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); } + size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } }; typedef std::vector GLVolumePtrs; @@ -461,30 +492,33 @@ public: ~GLVolumeCollection() { clear(); }; std::vector load_object( - const ModelObject* model_object, + const ModelObject *model_object, int obj_idx, - const std::vector& instance_idxs, - const std::string& color_by); + const std::vector &instance_idxs, + const std::string &color_by, + bool opengl_initialized); int load_object_volume( - const ModelObject* model_object, - int obj_idx, - int volume_idx, - int instance_idx, - const std::string& color_by); + const ModelObject *model_object, + int obj_idx, + int volume_idx, + int instance_idx, + const std::string &color_by, + bool opengl_initialized); // Load SLA auxiliary GLVolumes (for support trees or pad). void load_object_auxiliary( - const SLAPrintObject* print_object, + const SLAPrintObject *print_object, int obj_idx, // pairs of const std::vector>& instances, SLAPrintObjectStep milestone, // Timestamp of the last change of the milestone - size_t timestamp); + size_t timestamp, + bool opengl_initialized); int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); // Render the volumes by OpenGL. void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; @@ -492,7 +526,7 @@ public: // Finalize the initialization of the geometry & indices, // upload the geometry and indices to OpenGL VBO objects // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry() { for (auto* v : volumes) v->finalize_geometry(); } + void finalize_geometry(bool opengl_initialized) { for (auto* v : volumes) v->finalize_geometry(opengl_initialized); } // Release the geometry data assigned to the volumes. // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them. void release_geometry() { for (auto *v : volumes) v->release_geometry(); } @@ -520,6 +554,14 @@ public: // Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection std::vector get_current_print_zs(bool active_only) const; + // Return an estimate of the memory consumed by this class. + size_t cpu_memory_used() const; + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const; + size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } + // Return CPU, GPU and total memory log line. + std::string log_memory_info() const; + private: GLVolumeCollection(const GLVolumeCollection &other); GLVolumeCollection& operator=(const GLVolumeCollection &); @@ -537,6 +579,7 @@ public: GLModel(); virtual ~GLModel(); + // init() / init_from_file() shall be called with the OpenGL context active! bool init() { return on_init(); } bool init_from_file(const std::string& filename) { return on_init_from_file(filename); } @@ -566,7 +609,7 @@ protected: class GLArrow : public GLModel { protected: - virtual bool on_init(); + bool on_init() override; }; class GLCurvedArrow : public GLModel @@ -577,13 +620,13 @@ public: explicit GLCurvedArrow(unsigned int resolution); protected: - virtual bool on_init(); + bool on_init() override; }; class GLBed : public GLModel { protected: - virtual bool on_init_from_file(const std::string& filename); + bool on_init_from_file(const std::string& filename) override; }; class _3DScene diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5e505bb416..ac3ac5d263 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1234,10 +1234,9 @@ bool GLCanvas3D::init() return false; } -// // on linux the gl context is not valid until the canvas is not shown on screen -// // we defer the geometry finalization of volumes until the first call to render() -// if (!m_volumes.empty()) -// m_volumes.finalize_geometry(); + // on linux the gl context is not valid until the canvas is not shown on screen + // we defer the geometry finalization of volumes until the first call to render() + m_volumes.finalize_geometry(true); if (m_gizmos.is_enabled() && !m_gizmos.init()) std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl; @@ -1691,7 +1690,7 @@ std::vector GLCanvas3D::load_object(const ModelObject& model_object, int ob instance_idxs.push_back(i); } } - return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by); + return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_initialized); } std::vector GLCanvas3D::load_object(const Model& model, int obj_idx) @@ -1879,7 +1878,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id); if (it->new_geometry()) { // New volume. - m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by); + m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_initialized); m_volumes.volumes.back()->geometry_id = key.geometry_id; update_object_list = true; } else { @@ -1952,7 +1951,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re for (size_t istep = 0; istep < sla_steps.size(); ++istep) if (!instances[istep].empty()) - m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp); + m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized); } // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed @@ -1992,7 +1991,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re depth = (900.f/w) * (float)(extruders_count - 1); int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( 1000, x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), - brim_spacing * 4.5f); + brim_spacing * 4.5f, m_initialized); if (volume_idx_wipe_tower_old != -1) map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; } @@ -4511,6 +4510,7 @@ void GLCanvas3D::_load_print_toolpaths() _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), volume); } + volume.indexed_vertex_array.finalize_geometry(m_initialized); } void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const std::vector& str_tool_colors, const std::vector& color_print_values) @@ -4576,7 +4576,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; }); // Maximum size of an allocation block: 32MB / sizeof(float) - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start"; + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); @@ -4682,16 +4682,22 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c } } } + for (GLVolume *vol : vols) + // Ideally one would call vol->indexed_vertex_array.finalize() here to move the buffers to the OpenGL driver, + // but this code runs in parallel and the OpenGL driver is not thread safe. + vol->indexed_vertex_array.shrink_to_fit(); }); - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results"; + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); // Remove empty volumes from the newly added volumes. m_volumes.volumes.erase( std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); - BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_tool_colors) @@ -4748,7 +4754,7 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ ctxt.wipe_tower_angle = ctxt.print->config().wipe_tower_rotation_angle.value/180.f * PI; ctxt.wipe_tower_pos = Vec2f(ctxt.print->config().wipe_tower_x.value, ctxt.print->config().wipe_tower_y.value); - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start"; + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info(); //FIXME Improve the heuristics for a grain size. size_t n_items = print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1); @@ -4846,16 +4852,20 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); } } + for (GLVolume *vol : vols) + vol->indexed_vertex_array.shrink_to_fit(); }); - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results"; + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info(); // Remove empty volumes from the newly added volumes. m_volumes.volumes.erase( std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); - BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end"; + BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } static inline int hex_digit_to_int(const char c) @@ -4868,6 +4878,8 @@ static inline int hex_digit_to_int(const char c) void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector& tool_colors) { + BOOST_LOG_TRIVIAL(debug) << "Loading G-code extrusion paths - start" << m_volumes.log_memory_info() << log_memory_info(); + // helper functions to select data in dependence of the extrusion view type struct Helper { @@ -4983,6 +4995,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat if (filters.empty()) return; + BOOST_LOG_TRIVIAL(debug) << "Loading G-code extrusion paths - create volumes" << m_volumes.log_memory_info() << log_memory_info(); + // creates a new volume for each filter for (Filter& filter : filters) { @@ -5013,6 +5027,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat } } + BOOST_LOG_TRIVIAL(debug) << "Loading G-code extrusion paths - populate volumes" << m_volumes.log_memory_info() << log_memory_info(); + // populates volumes for (const GCodePreviewData::Extrusion::Layer& layer : preview_data.extrusion.layers) { @@ -5030,6 +5046,12 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat } } } + + // finalize volumes and sends geometry to gpu + for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); + + BOOST_LOG_TRIVIAL(debug) << "Loading G-code extrusion paths - end" << m_volumes.log_memory_info() << log_memory_info(); } void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector& tool_colors) @@ -5074,6 +5096,10 @@ void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, return; } + + // finalize volumes and sends geometry to gpu + for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) + m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); } bool GLCanvas3D::_travel_paths_by_type(const GCodePreviewData& preview_data) @@ -5302,6 +5328,7 @@ void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data) _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); } + volume->indexed_vertex_array.finalize_geometry(m_initialized); } } @@ -5329,6 +5356,7 @@ void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data) _3DScene::point3_to_verts(position.position, position.width, position.height, *volume); } + volume->indexed_vertex_array.finalize_geometry(m_initialized); } } @@ -5354,7 +5382,7 @@ void GLCanvas3D::_load_fff_shells() instance_ids[i] = i; } - m_volumes.load_object(model_obj, object_id, instance_ids, "object"); + m_volumes.load_object(model_obj, object_id, instance_ids, "object", m_initialized); ++object_id; } @@ -5376,7 +5404,7 @@ void GLCanvas3D::_load_fff_shells() if (!print->is_step_done(psWipeTower)) depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1); m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - !print->is_step_done(psWipeTower), brim_spacing * 4.5f); + !print->is_step_done(psWipeTower), brim_spacing * 4.5f, m_initialized); } } } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 2488947bc3..11d038b9d7 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -100,6 +100,7 @@ void Selection::set_volumes(GLVolumePtrs* volumes) update_valid(); } +// Init shall be called from the OpenGL render function, so that the OpenGL context is initialized! bool Selection::init() { if (!m_arrow.init()) From c91df2c769f4b1f9b3eeac760615c54f9bfe043d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 5 Aug 2019 14:36:50 +0200 Subject: [PATCH 494/627] Fixed a typo in print_host variable name --- src/slic3r/GUI/MainFrame.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f5da43aee9..b0945aea83 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -287,8 +287,8 @@ bool MainFrame::can_send_gcode() const if (m_plater->model().objects.empty()) return false; - const auto prin_host_opt =wxGetApp().preset_bundle->printers.get_edited_preset().config.option("print_host"); - return prin_host_opt != nullptr && !prin_host_opt->value.empty(); + const auto print_host_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("print_host"); + return print_host_opt != nullptr && !print_host_opt->value.empty(); } bool MainFrame::can_slice() const From 219521f6ad66c229cbd234125394e93474097f5a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 5 Aug 2019 14:54:29 +0200 Subject: [PATCH 495/627] Wording improvmenet of some error message. --- src/slic3r/GUI/GUI_ObjectList.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 279a439eda..e20abcd111 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1767,7 +1767,7 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) m_objects_model->Delete(item); if (show_msg) - Slic3r::GUI::show_error(nullptr, _(L("From Object List You can't delete the last intance from object."))); + Slic3r::GUI::show_error(nullptr, _(L("Last instance of an object cannot be deleted."))); } void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item) @@ -1869,7 +1869,7 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con } else if (type == itInstance) { if (object->instances.size() == 1) { - Slic3r::GUI::show_error(nullptr, _(L("From Object List You can't delete the last intance from object."))); + Slic3r::GUI::show_error(nullptr, _(L("Last instance of an object cannot be deleted."))); return false; } From 4152a5df43298629ba2856f1813ab235191a88df Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 5 Aug 2019 14:57:30 +0200 Subject: [PATCH 496/627] Implemented update on canvas of a printable state for new volumes added from ObjectList --- src/slic3r/GUI/GLCanvas3D.cpp | 17 +++++++++++++++++ src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/GUI_ObjectList.cpp | 20 +++++++++++++++++--- src/slic3r/GUI/wxExtensions.cpp | 5 +++-- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c74207123d..36d9fffec5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1320,6 +1320,23 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject _set_warning_texture(WarningTexture::SomethingNotShown, false); } +void GLCanvas3D::update_instance_printable_state_for_objects(std::vector& object_idxs) +{ + for (size_t obj_idx : object_idxs) + { + ModelObject* model_object = m_model->objects[obj_idx]; + for (int inst_idx = 0; inst_idx < model_object->instances.size(); inst_idx++) + { + ModelInstance* instance = model_object->instances[inst_idx]; + + for (GLVolume* volume : m_volumes.volumes) + { + if ((volume->object_idx() == obj_idx) && (volume->instance_idx() == inst_idx)) + volume->printable = instance->printable; + } + } + } +} void GLCanvas3D::set_config(const DynamicPrintConfig* config) { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index bd33dbef78..1738d77426 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -482,6 +482,7 @@ public: void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); + void update_instance_printable_state_for_objects(std::vector& object_idxs); void set_config(const DynamicPrintConfig* config); void set_process(BackgroundSlicingProcess* process); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 9de9323f14..882ac873c1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2271,7 +2271,9 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) for (int i = 0; i < model_object->instances.size(); ++i) print_idicator[i] = model_object->instances[i]->is_printable(); - select_item(m_objects_model->AddInstanceChild(m_objects_model->GetItemById(obj_idx), print_idicator)); + const wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx); + m_objects_model->AddInstanceChild(object_item, print_idicator); + Expand(m_objects_model->GetInstanceRootItem(object_item)); } else m_objects_model->SetPrintableState(model_object->instances[0]->is_printable() ? piPrintable : piUnprintable, obj_idx); @@ -3317,7 +3319,8 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set } // Add new object to the object_list - add_object_to_list(m_objects->size() - 1); + const size_t new_obj_indx = static_cast(m_objects->size() - 1); + add_object_to_list(new_obj_indx); for (std::set::const_reverse_iterator it = inst_idxs.rbegin(); it != inst_idxs.rend(); ++it) { @@ -3325,12 +3328,18 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set del_subobject_from_object(obj_idx, *it, itInstance); delete_instance_from_list(obj_idx, *it); } + + std::vector object_idxs = { new_obj_indx }; + // update printable state for new volumes on canvas3D + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(object_idxs); } void ObjectList::instances_to_separated_objects(const int obj_idx) { const int inst_cnt = (*m_objects)[obj_idx]->instances.size(); + std::vector object_idxs; + for (int i = inst_cnt-1; i > 0 ; i--) { // create new object from initial @@ -3344,12 +3353,17 @@ void ObjectList::instances_to_separated_objects(const int obj_idx) } // Add new object to the object_list - add_object_to_list(m_objects->size() - 1); + const size_t new_obj_indx = static_cast(m_objects->size() - 1); + add_object_to_list(new_obj_indx); + object_idxs.push_back(new_obj_indx); // delete current instance from the initial object del_subobject_from_object(obj_idx, i, itInstance); delete_instance_from_list(obj_idx, i); } + + // update printable state for new volumes on canvas3D + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(object_idxs); } void ObjectList::split_instances() diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 524861133e..5928a4c5dc 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -812,8 +812,9 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem& paren { ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); - // use object's printable state to first instance - instance_node->set_printable_icon(obj_node->IsPrintable()); + // use object's printable state to first instance, if it was defined + instance_node->set_printable_icon(obj_node->IsPrintable() != piUndef ? obj_node->IsPrintable() : + print_indicator[counter] ? piPrintable : piUnprintable ); // and set printable state for object_node to piUndef obj_node->set_printable_icon(piUndef); From 6da196b419183ed47ba9d10e5de8f52823d9e40f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 5 Aug 2019 15:12:35 +0200 Subject: [PATCH 497/627] Corrected return value of ConfigOptionVector::apply_override to what was intended (this didn't cause any bug though, the return value is currently not used) --- src/libslic3r/Config.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 2850f1cb90..ff55632262 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -375,7 +375,7 @@ public: this->values[i] = rhs_vec->values[i]; modified = true; } - return false; + return modified; } private: From cf2f16d864e1493ee57b803af6576f13dac6f29e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 5 Aug 2019 17:49:21 +0200 Subject: [PATCH 498/627] Fixed Model::convert_multipart_object() for STLs (regression from 8e2af5151dcf6f102b65981ff5aa56c2dfda5a2a). Removed Model::s_auto_extruder_id and related, as it is a Perl interfacing legacy. Fixed a typo in asserts introduced in the preceding commit. --- src/libslic3r/Model.cpp | 119 ++++++++++++------------------------- src/libslic3r/Model.hpp | 10 +--- src/slic3r/GUI/3DScene.hpp | 4 +- src/slic3r/GUI/Plater.cpp | 2 +- 4 files changed, 43 insertions(+), 92 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 76ef9eccab..479a8f9940 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -21,8 +21,6 @@ namespace Slic3r { -unsigned int Model::s_auto_extruder_id = 1; - Model& Model::assign_copy(const Model &rhs) { this->copy_id(rhs); @@ -485,9 +483,20 @@ bool Model::looks_like_multipart_object() const return false; } +// Generate next extruder ID string, in the range of (1, max_extruders). +static inline std::string auto_extruder_id(unsigned int max_extruders, unsigned int &cntr) +{ + char str_extruder[64]; + sprintf(str_extruder, "%ud", cntr + 1); + if (++ cntr == max_extruders) + cntr = 0; + return str_extruder; +} + void Model::convert_multipart_object(unsigned int max_extruders) { - if (this->objects.empty()) + assert(this->objects.size() >= 2); + if (this->objects.size() < 2) return; ModelObject* object = new ModelObject(this); @@ -495,58 +504,32 @@ void Model::convert_multipart_object(unsigned int max_extruders) object->name = this->objects.front()->name; //FIXME copy the config etc? - reset_auto_extruder_id(); - - bool is_single_object = (this->objects.size() == 1); - - for (const ModelObject* o : this->objects) - { - for (const ModelVolume* v : o->volumes) - { - if (is_single_object) - { - // If there is only one object, just copy the volumes - ModelVolume* new_v = object->add_volume(*v); - if (new_v != nullptr) - { - new_v->name = o->name; - new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string(max_extruders)); - new_v->translate(-o->origin_translation); - } - } - else - { - // If there are more than one object, put all volumes together - // Each object may contain any number of volumes and instances - // The volumes transformations are relative to the object containing them... - int counter = 1; - for (const ModelInstance* i : o->instances) - { - ModelVolume* new_v = object->add_volume(*v); - if (new_v != nullptr) - { - new_v->name = o->name + "_" + std::to_string(counter++); - new_v->config.set_deserialize("extruder", get_auto_extruder_id_as_string(max_extruders)); - new_v->translate(-o->origin_translation); - // ...so, transform everything to a common reference system (world) - new_v->set_transformation(i->get_transformation() * v->get_transformation()); - } - } + unsigned int extruder_counter = 0; + for (const ModelObject* o : this->objects) + for (const ModelVolume* v : o->volumes) { + // If there are more than one object, put all volumes together + // Each object may contain any number of volumes and instances + // The volumes transformations are relative to the object containing them... + Geometry::Transformation trafo_volume = v->get_transformation(); + // Revert the centering operation. + trafo_volume.set_offset(trafo_volume.get_offset() - o->origin_translation); + int counter = 1; + auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) { + assert(new_v != nullptr); + new_v->name = o->name + "_" + std::to_string(counter++); + new_v->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter)); + return new_v; + }; + if (o->instances.empty()) { + copy_volume(object->add_volume(*v))->set_transformation(trafo_volume); + } else { + for (const ModelInstance* i : o->instances) + // ...so, transform everything to a common reference system (world) + copy_volume(object->add_volume(*v))->set_transformation(i->get_transformation() * trafo_volume); } } - } - - if (is_single_object) - { - // If there is only one object, keep its instances - for (const ModelInstance* i : this->objects.front()->instances) - { - object->add_instance(*i); - } - } - else - // If there are more than one object, create a single instance - object->add_instance(); + // If there are more than one object, create a single instance + object->add_instance(); this->clear_objects(); this->objects.push_back(object); @@ -571,32 +554,6 @@ void Model::adjust_min_z() } } -unsigned int Model::get_auto_extruder_id(unsigned int max_extruders) -{ - unsigned int id = s_auto_extruder_id; - if (id > max_extruders) { - // The current counter is invalid, likely due to switching the printer profiles - // to a profile with a lower number of extruders. - reset_auto_extruder_id(); - id = s_auto_extruder_id; - } else if (++ s_auto_extruder_id > max_extruders) { - reset_auto_extruder_id(); - } - return id; -} - -std::string Model::get_auto_extruder_id_as_string(unsigned int max_extruders) -{ - char str_extruder[64]; - sprintf(str_extruder, "%ud", get_auto_extruder_id(max_extruders)); - return str_extruder; -} - -void Model::reset_auto_extruder_id() -{ - s_auto_extruder_id = 1; -} - // Propose a filename including path derived from the ModelObject's input path. // If object's name is filled in, use the object name, otherwise use the input name. std::string Model::propose_export_file_name_and_path() const @@ -1661,7 +1618,7 @@ size_t ModelVolume::split(unsigned int max_extruders) size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin(); std::string name = this->name; - Model::reset_auto_extruder_id(); + unsigned int extruder_counter = 0; Vec3d offset = this->get_offset(); for (TriangleMesh *mesh : meshptrs) { @@ -1680,7 +1637,7 @@ size_t ModelVolume::split(unsigned int max_extruders) this->object->volumes[ivolume]->center_geometry_after_creation(); this->object->volumes[ivolume]->translate(offset); this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); - this->object->volumes[ivolume]->config.set_deserialize("extruder", Model::get_auto_extruder_id_as_string(max_extruders)); + this->object->volumes[ivolume]->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter)); delete mesh; ++ idx; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a3281e5222..2513bdd053 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -721,8 +721,6 @@ private: // all objects may share mutliple materials. class Model final : public ObjectBase { - static unsigned int s_auto_extruder_id; - public: // Materials are owned by a model and referenced by objects through t_model_material_id. // Single material may be shared by multiple models. @@ -791,14 +789,10 @@ public: void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } - static unsigned int get_auto_extruder_id(unsigned int max_extruders); - static std::string get_auto_extruder_id_as_string(unsigned int max_extruders); - static void reset_auto_extruder_id(); - // Propose an output file name & path based on the first printable object's name and source input file's path. - std::string propose_export_file_name_and_path() const; + std::string propose_export_file_name_and_path() const; // Propose an output path, replace extension. The new_extension shall contain the initial dot. - std::string propose_export_file_name_and_path(const std::string &new_extension) const; + std::string propose_export_file_name_and_path(const std::string &new_extension) const; private: explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }; diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 3d89a3a5f1..06797ea95e 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -81,7 +81,7 @@ public: assert(vertices_and_normals_interleaved_VBO_id == 0); assert(triangle_indices_VBO_id == 0); assert(quad_indices_VBO_id == 0); - assert(rhs.vertices_and_normals_interleaved == 0); + assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); assert(rhs.triangle_indices_VBO_id == 0); assert(rhs.quad_indices_VBO_id == 0); this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; @@ -99,7 +99,7 @@ public: assert(vertices_and_normals_interleaved_VBO_id == 0); assert(triangle_indices_VBO_id == 0); assert(quad_indices_VBO_id == 0); - assert(rhs.vertices_and_normals_interleaved == 0); + assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); assert(rhs.triangle_indices_VBO_id == 0); assert(rhs.quad_indices_VBO_id == 0); this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 23beb09627..dddf479eb7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2302,7 +2302,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } - if (new_model != nullptr) { + if (new_model != nullptr && new_model->objects.size() > 1) { wxMessageDialog dlg(q, _(L( "Multiple objects were loaded for a multi-material printer.\n" "Instead of considering them as multiple objects, should I consider\n" From b0d4cb6e06672d79d7c251b3904e0095e62e63b3 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 5 Aug 2019 19:57:57 +0200 Subject: [PATCH 499/627] Memory usage is now shown in SysInfoDialog on all three platforms --- src/libslic3r/Utils.hpp | 5 +- src/libslic3r/utils.cpp | 121 +++++++++++++++---------------- src/slic3r/GUI/SysInfoDialog.cpp | 26 +++---- 3 files changed, 74 insertions(+), 78 deletions(-) diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 8a4f1424b2..b19027826b 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -18,8 +18,9 @@ extern void trace(unsigned int level, const char *message); // Format memory allocated, separate thousands by comma. extern std::string format_memsize_MB(size_t n); // Return string to be added to the boost::log output to inform about the current process memory allocation. -// The string is non-empty only if the loglevel >= info (3). -extern std::string log_memory_info(); +// The string is non-empty if the loglevel >= info (3) or ignore_loglevel==true. +// Latter is used to get the memory info from SysInfoDialog. +extern std::string log_memory_info(bool ignore_loglevel = false); extern void disable_multi_threading(); // Returns the size of physical memory (RAM) in bytes. extern size_t total_physical_memory(); diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 0718ebf0e2..ea5e3edec9 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -435,84 +435,81 @@ std::string format_memsize_MB(size_t n) return out + "MB"; } -#ifdef WIN32 -#ifndef PROCESS_MEMORY_COUNTERS_EX - // MingW32 doesn't have this struct in psapi.h - typedef struct _PROCESS_MEMORY_COUNTERS_EX { - DWORD cb; - DWORD PageFaultCount; - SIZE_T PeakWorkingSetSize; - SIZE_T WorkingSetSize; - SIZE_T QuotaPeakPagedPoolUsage; - SIZE_T QuotaPagedPoolUsage; - SIZE_T QuotaPeakNonPagedPoolUsage; - SIZE_T QuotaNonPagedPoolUsage; - SIZE_T PagefileUsage; - SIZE_T PeakPagefileUsage; - SIZE_T PrivateUsage; - } PROCESS_MEMORY_COUNTERS_EX, *PPROCESS_MEMORY_COUNTERS_EX; -#endif /* PROCESS_MEMORY_COUNTERS_EX */ - -std::string log_memory_info() +// Returns platform-specific string to be used as log output or parsed in SysInfoDialog. +// The latter parses the string with (semi)colons as separators, it should look about as +// "desc1: value1; desc2: value2" or similar (spaces should not matter). +std::string log_memory_info(bool ignore_loglevel) { std::string out; - if (logSeverity <= boost::log::trivial::info) { + if (ignore_loglevel || logSeverity <= boost::log::trivial::info) { +#ifdef WIN32 + #ifndef PROCESS_MEMORY_COUNTERS_EX + // MingW32 doesn't have this struct in psapi.h + typedef struct _PROCESS_MEMORY_COUNTERS_EX { + DWORD cb; + DWORD PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivateUsage; + } PROCESS_MEMORY_COUNTERS_EX, *PPROCESS_MEMORY_COUNTERS_EX; + #endif /* PROCESS_MEMORY_COUNTERS_EX */ + + HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ::GetCurrentProcessId()); if (hProcess != nullptr) { PROCESS_MEMORY_COUNTERS_EX pmc; if (GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) - out = " WorkingSet: " + format_memsize_MB(pmc.WorkingSetSize) + " PrivateBytes: " + format_memsize_MB(pmc.PrivateUsage) + " Pagefile(peak): " + format_memsize_MB(pmc.PagefileUsage) + "(" + format_memsize_MB(pmc.PeakPagefileUsage) + ")"; + out = " WorkingSet: " + format_memsize_MB(pmc.WorkingSetSize) + "; PrivateBytes: " + format_memsize_MB(pmc.PrivateUsage) + "; Pagefile(peak): " + format_memsize_MB(pmc.PagefileUsage) + "(" + format_memsize_MB(pmc.PeakPagefileUsage) + ")"; + else + out += " Used memory: N/A"; CloseHandle(hProcess); } - } - return out; -} #elif defined(__linux__) or defined(__APPLE__) -std::string log_memory_info() -{ - std::string out = " Unable to get current memory usage."; - - // Get current memory usage. -#ifdef __APPLE__ - struct mach_task_basic_info info; - mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; - if ( task_info( mach_task_self( ), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount ) == KERN_SUCCESS ) - out = " Memory usage: resident: " + format_memsize_MB((size_t)info.resident_size); -#else // i.e. __linux__ - - size_t tSize = 0, resident = 0, share = 0; - std::ifstream buffer("/proc/self/statm"); - if (buffer) { - if ((buffer >> tSize >> resident >> share)) { + // Get current memory usage. + #ifdef __APPLE__ + struct mach_task_basic_info info; + mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; + out += " Resident memory: "; + if ( task_info( mach_task_self( ), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount ) == KERN_SUCCESS ) + out += format_memsize_MB((size_t)info.resident_size); + else + out += "N/A"; + #else // i.e. __linux__ + size_t tSize = 0, resident = 0, share = 0; + std::ifstream buffer("/proc/self/statm"); + if (buffer && (buffer >> tSize >> resident >> share)) { size_t page_size = (size_t)sysconf(_SC_PAGE_SIZE); // in case x86-64 is configured to use 2MB pages size_t rss = resident * page_size; - out = " Memory usage: resident: " + format_memsize_MB(rss); - out += " shared: " + format_memsize_MB(share * page_size); - out += " private: " + format_memsize_MB(rss - share * page_size); + out += " Resident memory: " + format_memsize_MB(rss); + out += "; Shared memory: " + format_memsize_MB(share * page_size); + out += "; Private memory: " + format_memsize_MB(rss - share * page_size); } - } + else + out += " Used memory: N/A"; + #endif + // Now get peak memory usage. + out += "; Peak memory usage: "; + rusage memory_info; + if (getrusage(RUSAGE_SELF, &memory_info) == 0) + { + size_t peak_mem_usage = (size_t)memory_info.ru_maxrss; + #ifdef __linux__ + peak_mem_usage *= 1024;// getrusage returns the value in kB on linux + #endif + out += format_memsize_MB(peak_mem_usage); + } + else + out += "N/A"; #endif - // Now get peak memory usage. - rusage memory_info; - if (getrusage(RUSAGE_SELF, &memory_info) != 0) - out += " Could not get peak memory usage."; - else { - size_t peak_mem_usage = (size_t)memory_info.ru_maxrss; - #ifdef __linux - peak_mem_usage *= 1024L;// getrusage returns the value in kB on linux - #endif - out += " Peak Memory Usage: " + format_memsize_MB(peak_mem_usage); } - return out; } -#else -std::string log_memory_info() -{ - return std::string(); -} - -#endif // Returns the size of physical memory (RAM) in bytes. // http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index e9487ee155..a1bae8742e 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -58,21 +58,19 @@ std::string get_mem_info(bool format_as_html) std::string b_end = format_as_html ? "" : ""; std::string line_end = format_as_html ? "
" : "\n"; - const Slic3r::UndoRedo::Stack &stack = wxGetApp().plater()->undo_redo_stack_main(); - out << b_start << "RAM size reserved for the Undo / Redo stack [MB]: " << b_end << Slic3r::format_memsize_MB(stack.get_memory_limit()) << line_end; - out << b_start << "RAM size occupied by the Undo / Redo stack [MB]: " << b_end << Slic3r::format_memsize_MB(stack.memsize()) << line_end << line_end; - -#ifdef _WIN32 - HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ::GetCurrentProcessId()); - if (hProcess != nullptr) { - PROCESS_MEMORY_COUNTERS_EX pmc; - if (GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) - out << b_start << "WorkingSet [MB]: " << b_end << format_memsize_MB(pmc.WorkingSetSize) << line_end - << b_start << "PrivateBytes [MB]: " << b_end << format_memsize_MB(pmc.PrivateUsage) << line_end - << b_start << "Pagefile(peak) [MB]: " << b_end << format_memsize_MB(pmc.PagefileUsage) << "(" << format_memsize_MB(pmc.PeakPagefileUsage) << ")" << line_end; - CloseHandle(hProcess); + std::string mem_info_str = log_memory_info(true); + std::istringstream mem_info(mem_info_str); + std::string value; + while (std::getline(mem_info, value, ':')) { + out << b_start << (value+": ") << b_end; + std::getline(mem_info, value, ';'); + out << value << line_end; } -#endif + + const Slic3r::UndoRedo::Stack &stack = wxGetApp().plater()->undo_redo_stack_main(); + out << b_start << "RAM size reserved for the Undo / Redo stack: " << b_end << Slic3r::format_memsize_MB(stack.get_memory_limit()) << line_end; + out << b_start << "RAM size occupied by the Undo / Redo stack: " << b_end << Slic3r::format_memsize_MB(stack.memsize()) << line_end << line_end; + return out.str(); } From 13ee32538a09ed8f57990982766090ebd08bfd6d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 6 Aug 2019 09:41:09 +0200 Subject: [PATCH 500/627] Fixed selection after multiple selected Copy/Paste --- src/slic3r/GUI/GUI_ObjectList.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index e20abcd111..346d4494b0 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2849,6 +2849,9 @@ void ObjectList::update_selections_on_canvas() wxDataViewItemArray sels; GetSelections(sels); + // clear selection before adding new elements + selection.clear(); //OR remove_all()? + for (auto item : sels) { add_to_selection(item, selection, instance_idx, mode); From b43f7c3880ba1c0bbb6cda679d9e06444c749a88 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 6 Aug 2019 10:01:10 +0200 Subject: [PATCH 501/627] Generated new POT-file Fixed lines with translation of the empty strings --- resources/localization/PrusaSlicer.pot | 4140 ++++++++++++++---------- src/slic3r/GUI/BedShapeDialog.cpp | 4 +- src/slic3r/GUI/GLCanvas3D.cpp | 12 +- src/slic3r/GUI/Plater.cpp | 2 +- 4 files changed, 2369 insertions(+), 1789 deletions(-) diff --git a/resources/localization/PrusaSlicer.pot b/resources/localization/PrusaSlicer.pot index 30c41434f8..bdad4a76d8 100644 --- a/resources/localization/PrusaSlicer.pot +++ b/resources/localization/PrusaSlicer.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-05-20 15:59+0200\n" +"POT-Creation-Date: 2019-08-06 09:54+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,46 +17,46 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: src/slic3r/GUI/AboutDialog.cpp:39 src/slic3r/GUI/AboutDialog.cpp:286 +#: src/slic3r/GUI/AboutDialog.cpp:39 src/slic3r/GUI/AboutDialog.cpp:289 msgid "Portions copyright" msgstr "" -#: src/slic3r/GUI/AboutDialog.cpp:122 src/slic3r/GUI/AboutDialog.cpp:251 +#: src/slic3r/GUI/AboutDialog.cpp:125 src/slic3r/GUI/AboutDialog.cpp:254 msgid "Copyright" msgstr "" #. TRN "Slic3r _is licensed under the_ License" -#: src/slic3r/GUI/AboutDialog.cpp:124 +#: src/slic3r/GUI/AboutDialog.cpp:127 msgid "" "License agreements of all following programs (libraries) are part of " "application license agreement" msgstr "" -#: src/slic3r/GUI/AboutDialog.cpp:194 +#: src/slic3r/GUI/AboutDialog.cpp:197 #, possible-c-format msgid "About %s" msgstr "" -#: src/slic3r/GUI/AboutDialog.cpp:226 src/slic3r/GUI/MainFrame.cpp:59 +#: src/slic3r/GUI/AboutDialog.cpp:229 src/slic3r/GUI/MainFrame.cpp:60 msgid "Version" msgstr "" #. TRN "Slic3r _is licensed under the_ License" -#: src/slic3r/GUI/AboutDialog.cpp:253 +#: src/slic3r/GUI/AboutDialog.cpp:256 msgid "is licensed under the" msgstr "" -#: src/slic3r/GUI/AboutDialog.cpp:254 +#: src/slic3r/GUI/AboutDialog.cpp:257 msgid "GNU Affero General Public License, version 3" msgstr "" -#: src/slic3r/GUI/AboutDialog.cpp:255 +#: src/slic3r/GUI/AboutDialog.cpp:258 msgid "" "PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap " "community." msgstr "" -#: src/slic3r/GUI/AboutDialog.cpp:256 +#: src/slic3r/GUI/AboutDialog.cpp:259 msgid "" "Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, " "Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and " @@ -64,8 +64,9 @@ msgid "" msgstr "" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:92 -#: src/slic3r/GUI/BackgroundSlicingProcess.cpp:408 -msgid "Copying of the temporary G-code to the output G-code failed" +msgid "" +"Copying of the temporary G-code to the output G-code failed. Maybe the SD " +"card is write locked?" msgstr "" #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:93 @@ -86,115 +87,152 @@ msgstr "" msgid "Masked SLA file exported to %1%" msgstr "" +#: src/slic3r/GUI/BackgroundSlicingProcess.cpp:408 +msgid "Copying of the temporary G-code to the output G-code failed" +msgstr "" + #: src/slic3r/GUI/BackgroundSlicingProcess.cpp:417 msgid "Scheduling upload to `%1%`. See Window -> Print Host Upload Queue" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:60 +#: src/slic3r/GUI/BedShapeDialog.cpp:65 msgid "Shape" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:68 +#: src/slic3r/GUI/BedShapeDialog.cpp:72 msgid "Rectangular" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:72 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:232 src/slic3r/GUI/Plater.cpp:136 -#: src/slic3r/GUI/Tab.cpp:2294 +#: src/slic3r/GUI/BedShapeDialog.cpp:76 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:391 src/slic3r/GUI/Plater.cpp:145 +#: src/slic3r/GUI/Tab.cpp:2469 msgid "Size" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:73 +#: src/slic3r/GUI/BedShapeDialog.cpp:77 msgid "Size in X and Y of the rectangular plate." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:79 +#: src/slic3r/GUI/BedShapeDialog.cpp:83 msgid "Origin" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:80 +#: src/slic3r/GUI/BedShapeDialog.cpp:84 msgid "" "Distance of the 0,0 G-code coordinate from the front left corner of the " "rectangle." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:84 +#: src/slic3r/GUI/BedShapeDialog.cpp:88 msgid "Circular" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:87 src/slic3r/GUI/ConfigWizard.cpp:118 -#: src/slic3r/GUI/ConfigWizard.cpp:565 src/slic3r/GUI/ConfigWizard.cpp:579 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:229 -#: src/slic3r/GUI/WipeTowerDialog.cpp:84 src/libslic3r/PrintConfig.cpp:60 -#: src/libslic3r/PrintConfig.cpp:67 src/libslic3r/PrintConfig.cpp:76 -#: src/libslic3r/PrintConfig.cpp:211 src/libslic3r/PrintConfig.cpp:286 -#: src/libslic3r/PrintConfig.cpp:294 src/libslic3r/PrintConfig.cpp:344 -#: src/libslic3r/PrintConfig.cpp:354 src/libslic3r/PrintConfig.cpp:474 -#: src/libslic3r/PrintConfig.cpp:485 src/libslic3r/PrintConfig.cpp:503 -#: src/libslic3r/PrintConfig.cpp:681 src/libslic3r/PrintConfig.cpp:1201 -#: src/libslic3r/PrintConfig.cpp:1262 src/libslic3r/PrintConfig.cpp:1280 -#: src/libslic3r/PrintConfig.cpp:1298 src/libslic3r/PrintConfig.cpp:1350 -#: src/libslic3r/PrintConfig.cpp:1360 src/libslic3r/PrintConfig.cpp:1481 -#: src/libslic3r/PrintConfig.cpp:1489 src/libslic3r/PrintConfig.cpp:1530 -#: src/libslic3r/PrintConfig.cpp:1538 src/libslic3r/PrintConfig.cpp:1548 -#: src/libslic3r/PrintConfig.cpp:1556 src/libslic3r/PrintConfig.cpp:1564 -#: src/libslic3r/PrintConfig.cpp:1647 src/libslic3r/PrintConfig.cpp:1863 -#: src/libslic3r/PrintConfig.cpp:1933 src/libslic3r/PrintConfig.cpp:1967 -#: src/libslic3r/PrintConfig.cpp:2160 src/libslic3r/PrintConfig.cpp:2167 -#: src/libslic3r/PrintConfig.cpp:2174 src/libslic3r/PrintConfig.cpp:2204 -#: src/libslic3r/PrintConfig.cpp:2214 src/libslic3r/PrintConfig.cpp:2224 -#: src/libslic3r/PrintConfig.cpp:2332 src/libslic3r/PrintConfig.cpp:2407 -#: src/libslic3r/PrintConfig.cpp:2416 src/libslic3r/PrintConfig.cpp:2425 -#: src/libslic3r/PrintConfig.cpp:2435 src/libslic3r/PrintConfig.cpp:2479 -#: src/libslic3r/PrintConfig.cpp:2489 src/libslic3r/PrintConfig.cpp:2508 -#: src/libslic3r/PrintConfig.cpp:2518 src/libslic3r/PrintConfig.cpp:2527 -#: src/libslic3r/PrintConfig.cpp:2545 src/libslic3r/PrintConfig.cpp:2560 -#: src/libslic3r/PrintConfig.cpp:2574 src/libslic3r/PrintConfig.cpp:2587 -#: src/libslic3r/PrintConfig.cpp:2597 +#: src/slic3r/GUI/BedShapeDialog.cpp:91 src/slic3r/GUI/ConfigWizard.cpp:118 +#: src/slic3r/GUI/ConfigWizard.cpp:571 src/slic3r/GUI/ConfigWizard.cpp:585 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:388 +#: src/slic3r/GUI/WipeTowerDialog.cpp:84 src/slic3r/GUI/wxExtensions.cpp:486 +#: src/libslic3r/PrintConfig.cpp:70 src/libslic3r/PrintConfig.cpp:77 +#: src/libslic3r/PrintConfig.cpp:86 src/libslic3r/PrintConfig.cpp:220 +#: src/libslic3r/PrintConfig.cpp:295 src/libslic3r/PrintConfig.cpp:303 +#: src/libslic3r/PrintConfig.cpp:353 src/libslic3r/PrintConfig.cpp:363 +#: src/libslic3r/PrintConfig.cpp:488 src/libslic3r/PrintConfig.cpp:499 +#: src/libslic3r/PrintConfig.cpp:517 src/libslic3r/PrintConfig.cpp:695 +#: src/libslic3r/PrintConfig.cpp:1215 src/libslic3r/PrintConfig.cpp:1276 +#: src/libslic3r/PrintConfig.cpp:1294 src/libslic3r/PrintConfig.cpp:1312 +#: src/libslic3r/PrintConfig.cpp:1364 src/libslic3r/PrintConfig.cpp:1374 +#: src/libslic3r/PrintConfig.cpp:1495 src/libslic3r/PrintConfig.cpp:1503 +#: src/libslic3r/PrintConfig.cpp:1544 src/libslic3r/PrintConfig.cpp:1552 +#: src/libslic3r/PrintConfig.cpp:1562 src/libslic3r/PrintConfig.cpp:1570 +#: src/libslic3r/PrintConfig.cpp:1578 src/libslic3r/PrintConfig.cpp:1661 +#: src/libslic3r/PrintConfig.cpp:1878 src/libslic3r/PrintConfig.cpp:1948 +#: src/libslic3r/PrintConfig.cpp:1982 src/libslic3r/PrintConfig.cpp:2176 +#: src/libslic3r/PrintConfig.cpp:2183 src/libslic3r/PrintConfig.cpp:2190 +#: src/libslic3r/PrintConfig.cpp:2220 src/libslic3r/PrintConfig.cpp:2230 +#: src/libslic3r/PrintConfig.cpp:2240 src/libslic3r/PrintConfig.cpp:2403 +#: src/libslic3r/PrintConfig.cpp:2478 src/libslic3r/PrintConfig.cpp:2487 +#: src/libslic3r/PrintConfig.cpp:2496 src/libslic3r/PrintConfig.cpp:2506 +#: src/libslic3r/PrintConfig.cpp:2550 src/libslic3r/PrintConfig.cpp:2560 +#: src/libslic3r/PrintConfig.cpp:2572 src/libslic3r/PrintConfig.cpp:2592 +#: src/libslic3r/PrintConfig.cpp:2602 src/libslic3r/PrintConfig.cpp:2613 +#: src/libslic3r/PrintConfig.cpp:2631 src/libslic3r/PrintConfig.cpp:2646 +#: src/libslic3r/PrintConfig.cpp:2660 src/libslic3r/PrintConfig.cpp:2673 +#: src/libslic3r/PrintConfig.cpp:2683 src/libslic3r/PrintConfig.cpp:2704 +#: src/libslic3r/PrintConfig.cpp:2715 src/libslic3r/PrintConfig.cpp:2725 +#: src/libslic3r/PrintConfig.cpp:2735 msgid "mm" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:88 src/libslic3r/PrintConfig.cpp:678 +#: src/slic3r/GUI/BedShapeDialog.cpp:92 src/libslic3r/PrintConfig.cpp:692 msgid "Diameter" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:89 +#: src/slic3r/GUI/BedShapeDialog.cpp:93 msgid "" "Diameter of the print bed. It is assumed that origin (0,0) is located in the " "center." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:93 src/slic3r/GUI/GUI_Preview.cpp:245 +#: src/slic3r/GUI/BedShapeDialog.cpp:97 src/slic3r/GUI/GUI_Preview.cpp:246 #: src/libslic3r/GCode/PreviewData.cpp:175 msgid "Custom" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:97 +#: src/slic3r/GUI/BedShapeDialog.cpp:101 msgid "Load shape from STL..." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:143 +#: src/slic3r/GUI/BedShapeDialog.cpp:154 msgid "Settings" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:316 -msgid "Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):" +#: src/slic3r/GUI/BedShapeDialog.cpp:171 +msgid "Texture" msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:333 src/slic3r/GUI/GUI_ObjectList.cpp:1442 -msgid "Error!" +#: src/slic3r/GUI/BedShapeDialog.cpp:181 src/slic3r/GUI/BedShapeDialog.cpp:249 +msgid "Load..." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:342 +#: src/slic3r/GUI/BedShapeDialog.cpp:189 src/slic3r/GUI/BedShapeDialog.cpp:257 +#: src/slic3r/GUI/Tab.cpp:3204 +msgid "Remove" +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.cpp:239 +msgid "Model" +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.cpp:464 +msgid "Choose an STL file to import bed shape from:" +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.cpp:471 src/slic3r/GUI/BedShapeDialog.cpp:520 +#: src/slic3r/GUI/BedShapeDialog.cpp:543 +msgid "Invalid file format." +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.cpp:482 +msgid "Error! Invalid model" +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.cpp:490 msgid "The selected file contains no geometry." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.cpp:346 +#: src/slic3r/GUI/BedShapeDialog.cpp:494 msgid "" "The selected file contains several disjoint areas. This is not supported." msgstr "" -#: src/slic3r/GUI/BedShapeDialog.hpp:45 src/slic3r/GUI/ConfigWizard.cpp:530 +#: src/slic3r/GUI/BedShapeDialog.cpp:509 +msgid "Choose a file to import bed texture from (PNG/SVG):" +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.cpp:532 +msgid "Choose an STL file to import bed model from:" +msgstr "" + +#: src/slic3r/GUI/BedShapeDialog.hpp:59 src/slic3r/GUI/ConfigWizard.cpp:530 msgid "Bed Shape" msgstr "" @@ -268,7 +306,7 @@ msgstr "" msgid "slic3r version" msgstr "" -#: src/slic3r/GUI/ConfigSnapshotDialog.cpp:46 src/slic3r/GUI/Preset.cpp:1282 +#: src/slic3r/GUI/ConfigSnapshotDialog.cpp:46 src/slic3r/GUI/Preset.cpp:1307 msgid "print" msgstr "" @@ -276,11 +314,11 @@ msgstr "" msgid "filaments" msgstr "" -#: src/slic3r/GUI/ConfigSnapshotDialog.cpp:48 src/slic3r/GUI/Preset.cpp:1286 +#: src/slic3r/GUI/ConfigSnapshotDialog.cpp:48 src/slic3r/GUI/Preset.cpp:1311 msgid "printer" msgstr "" -#: src/slic3r/GUI/ConfigSnapshotDialog.cpp:52 src/slic3r/GUI/Tab.cpp:934 +#: src/slic3r/GUI/ConfigSnapshotDialog.cpp:52 src/slic3r/GUI/Tab.cpp:939 msgid "vendor" msgstr "" @@ -329,11 +367,11 @@ msgstr "" msgid "All standard" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:189 src/slic3r/GUI/Tab.cpp:3038 +#: src/slic3r/GUI/ConfigWizard.cpp:189 src/slic3r/GUI/Tab.cpp:3254 msgid "All" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:190 src/slic3r/GUI/Plater.cpp:432 +#: src/slic3r/GUI/ConfigWizard.cpp:190 src/slic3r/GUI/Plater.cpp:470 #: src/libslic3r/GCode/PreviewData.cpp:162 msgid "None" msgstr "" @@ -352,7 +390,7 @@ msgstr "" msgid "Welcome" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:304 src/slic3r/GUI/GUI_App.cpp:713 +#: src/slic3r/GUI/ConfigWizard.cpp:304 src/slic3r/GUI/GUI_App.cpp:747 #, possible-c-format msgid "Run %s" msgstr "" @@ -399,7 +437,7 @@ msgstr "" msgid "Updates" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:415 src/slic3r/GUI/Preferences.cpp:61 +#: src/slic3r/GUI/ConfigWizard.cpp:415 src/slic3r/GUI/Preferences.cpp:69 msgid "Check for application updates" msgstr "" @@ -412,7 +450,7 @@ msgid "" "notification mechanisms, no automatic installation is done." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:425 src/slic3r/GUI/Preferences.cpp:69 +#: src/slic3r/GUI/ConfigWizard.cpp:425 src/slic3r/GUI/Preferences.cpp:77 msgid "Update built-in Presets automatically" msgstr "" @@ -450,7 +488,7 @@ msgstr "" msgid "Firmware Type" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:492 src/slic3r/GUI/Tab.cpp:1957 +#: src/slic3r/GUI/ConfigWizard.cpp:492 src/slic3r/GUI/Tab.cpp:2100 msgid "Firmware" msgstr "" @@ -466,180 +504,183 @@ msgstr "" msgid "Set the shape of your printer's bed." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:547 +#: src/slic3r/GUI/ConfigWizard.cpp:553 msgid "Filament and Nozzle Diameters" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:547 +#: src/slic3r/GUI/ConfigWizard.cpp:553 msgid "Print Diameters" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:561 +#: src/slic3r/GUI/ConfigWizard.cpp:567 msgid "Enter the diameter of your printer's hot end nozzle." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:564 +#: src/slic3r/GUI/ConfigWizard.cpp:570 msgid "Nozzle Diameter:" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:574 +#: src/slic3r/GUI/ConfigWizard.cpp:580 msgid "Enter the diameter of your filament." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:575 +#: src/slic3r/GUI/ConfigWizard.cpp:581 msgid "" "Good precision is required, so use a caliper and do multiple measurements " "along the filament, then compute the average." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:578 +#: src/slic3r/GUI/ConfigWizard.cpp:584 msgid "Filament Diameter:" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:612 +#: src/slic3r/GUI/ConfigWizard.cpp:618 msgid "Extruder and Bed Temperatures" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:612 +#: src/slic3r/GUI/ConfigWizard.cpp:618 msgid "Temperatures" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:628 +#: src/slic3r/GUI/ConfigWizard.cpp:634 msgid "Enter the temperature needed for extruding your filament." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:629 +#: src/slic3r/GUI/ConfigWizard.cpp:635 msgid "A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:632 +#: src/slic3r/GUI/ConfigWizard.cpp:638 msgid "Extrusion Temperature:" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:633 src/slic3r/GUI/ConfigWizard.cpp:647 +#: src/slic3r/GUI/ConfigWizard.cpp:639 src/slic3r/GUI/ConfigWizard.cpp:653 msgid "°C" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:642 +#: src/slic3r/GUI/ConfigWizard.cpp:648 msgid "" "Enter the bed temperature needed for getting your filament to stick to your " "heated bed." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:643 +#: src/slic3r/GUI/ConfigWizard.cpp:649 msgid "" "A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have " "no heated bed." msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:646 +#: src/slic3r/GUI/ConfigWizard.cpp:652 msgid "Bed Temperature:" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1109 +#: src/slic3r/GUI/ConfigWizard.cpp:1115 msgid "Select all standard printers" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1112 +#: src/slic3r/GUI/ConfigWizard.cpp:1118 msgid "< &Back" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1113 +#: src/slic3r/GUI/ConfigWizard.cpp:1119 msgid "&Next >" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1114 +#: src/slic3r/GUI/ConfigWizard.cpp:1120 msgid "&Finish" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1115 src/slic3r/GUI/FirmwareDialog.cpp:147 -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:37 -#: src/slic3r/GUI/ProgressStatusBar.cpp:28 +#: src/slic3r/GUI/ConfigWizard.cpp:1121 src/slic3r/GUI/FirmwareDialog.cpp:151 +#: src/slic3r/GUI/ProgressStatusBar.cpp:27 msgid "Cancel" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1129 +#: src/slic3r/GUI/ConfigWizard.cpp:1135 msgid "Prusa FFF Technology Printers" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1132 +#: src/slic3r/GUI/ConfigWizard.cpp:1138 msgid "Prusa MSLA Technology Printers" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1201 +#: src/slic3r/GUI/ConfigWizard.cpp:1207 msgid "Configuration Assistant" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1202 +#: src/slic3r/GUI/ConfigWizard.cpp:1208 msgid "Configuration &Assistant" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1204 +#: src/slic3r/GUI/ConfigWizard.cpp:1210 msgid "Configuration Wizard" msgstr "" -#: src/slic3r/GUI/ConfigWizard.cpp:1205 +#: src/slic3r/GUI/ConfigWizard.cpp:1211 msgid "Configuration &Wizard" msgstr "" -#: src/slic3r/GUI/Field.cpp:117 +#: src/slic3r/GUI/Field.cpp:125 msgid "default value" msgstr "" -#: src/slic3r/GUI/Field.cpp:120 +#: src/slic3r/GUI/Field.cpp:128 msgid "parameter name" msgstr "" -#: src/slic3r/GUI/Field.cpp:148 +#: src/slic3r/GUI/Field.cpp:139 +msgid "N/A" +msgstr "" + +#: src/slic3r/GUI/Field.cpp:158 #, possible-c-format msgid "%s doesn't support percentage" msgstr "" -#: src/slic3r/GUI/Field.cpp:162 src/slic3r/GUI/Field.cpp:185 +#: src/slic3r/GUI/Field.cpp:174 src/slic3r/GUI/Field.cpp:197 msgid "Invalid numeric input." msgstr "" -#: src/slic3r/GUI/Field.cpp:167 +#: src/slic3r/GUI/Field.cpp:179 msgid "Input value is out of range" msgstr "" -#: src/slic3r/GUI/Field.cpp:193 +#: src/slic3r/GUI/Field.cpp:206 #, possible-c-format msgid "" -"Do you mean %d%% instead of %d %s?\n" -"Select YES if you want to change this value to %d%%, \n" -"or NO if you are sure that %d %s is a correct value." +"Do you mean %s%% instead of %s %s?\n" +"Select YES if you want to change this value to %s%%, \n" +"or NO if you are sure that %s %s is a correct value." msgstr "" -#: src/slic3r/GUI/Field.cpp:196 +#: src/slic3r/GUI/Field.cpp:209 msgid "Parameter validation" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:146 +#: src/slic3r/GUI/FirmwareDialog.cpp:150 msgid "Flash!" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:148 +#: src/slic3r/GUI/FirmwareDialog.cpp:152 msgid "Flashing in progress. Please do not disconnect the printer!" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:192 +#: src/slic3r/GUI/FirmwareDialog.cpp:199 msgid "Flashing failed" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:273 +#: src/slic3r/GUI/FirmwareDialog.cpp:282 msgid "Flashing succeeded!" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:274 +#: src/slic3r/GUI/FirmwareDialog.cpp:283 msgid "Flashing failed. Please see the avrdude log below." msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:275 +#: src/slic3r/GUI/FirmwareDialog.cpp:284 msgid "Flashing cancelled." msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:313 +#: src/slic3r/GUI/FirmwareDialog.cpp:332 #, possible-c-format msgid "" "This firmware hex file does not match the printer model.\n" @@ -650,13 +691,13 @@ msgid "" "Please only continue if you are sure this is the right thing to do." msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:400 src/slic3r/GUI/FirmwareDialog.cpp:436 +#: src/slic3r/GUI/FirmwareDialog.cpp:419 src/slic3r/GUI/FirmwareDialog.cpp:454 #, possible-c-format msgid "" "Multiple %s devices found. Please only connect one at a time for flashing." msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:417 +#: src/slic3r/GUI/FirmwareDialog.cpp:436 #, possible-c-format msgid "" "The %s device was not found.\n" @@ -664,947 +705,1198 @@ msgid "" "connector ..." msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:530 +#: src/slic3r/GUI/FirmwareDialog.cpp:548 #, possible-c-format msgid "The %s device could not have been found" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:608 +#: src/slic3r/GUI/FirmwareDialog.cpp:645 #, possible-c-format msgid "Error accessing port at %s: %s" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:610 +#: src/slic3r/GUI/FirmwareDialog.cpp:647 #, possible-c-format msgid "Error: %s" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:740 +#: src/slic3r/GUI/FirmwareDialog.cpp:777 msgid "Firmware flasher" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:765 +#: src/slic3r/GUI/FirmwareDialog.cpp:802 msgid "Firmware image:" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:768 src/slic3r/GUI/Tab.cpp:1718 -#: src/slic3r/GUI/Tab.cpp:1774 +#: src/slic3r/GUI/FirmwareDialog.cpp:805 src/slic3r/GUI/Tab.cpp:1824 +#: src/slic3r/GUI/Tab.cpp:1880 msgid "Browse" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:770 +#: src/slic3r/GUI/FirmwareDialog.cpp:807 msgid "Serial port:" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:772 +#: src/slic3r/GUI/FirmwareDialog.cpp:809 msgid "Autodetected" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:773 +#: src/slic3r/GUI/FirmwareDialog.cpp:810 msgid "Rescan" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:780 +#: src/slic3r/GUI/FirmwareDialog.cpp:817 msgid "Progress:" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:783 +#: src/slic3r/GUI/FirmwareDialog.cpp:820 msgid "Status:" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:784 +#: src/slic3r/GUI/FirmwareDialog.cpp:821 msgid "Ready" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:804 +#: src/slic3r/GUI/FirmwareDialog.cpp:841 msgid "Advanced: Output log" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:815 +#: src/slic3r/GUI/FirmwareDialog.cpp:852 #: src/slic3r/GUI/PrintHostDialogs.cpp:161 msgid "Close" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:863 +#: src/slic3r/GUI/FirmwareDialog.cpp:903 msgid "" "Are you sure you want to cancel firmware flashing?\n" "This could leave your printer in an unusable state!" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:864 +#: src/slic3r/GUI/FirmwareDialog.cpp:904 msgid "Confirmation" msgstr "" -#: src/slic3r/GUI/FirmwareDialog.cpp:867 +#: src/slic3r/GUI/FirmwareDialog.cpp:907 msgid "Cancelling..." msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:720 +#: src/slic3r/GUI/GLCanvas3D.cpp:526 +msgid "Layers heights" +msgstr "" + +#: src/slic3r/GUI/GLCanvas3D.cpp:623 msgid "An object outside the print area was detected" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:721 +#: src/slic3r/GUI/GLCanvas3D.cpp:624 msgid "A toolpath outside the print area was detected" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:722 +#: src/slic3r/GUI/GLCanvas3D.cpp:625 msgid "SLA supports outside the print area were detected" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:723 +#: src/slic3r/GUI/GLCanvas3D.cpp:626 msgid "Some objects are not visible when editing supports" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:725 +#: src/slic3r/GUI/GLCanvas3D.cpp:628 msgid "" "An object outside the print area was detected\n" "Resolve the current problem to continue slicing" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:1694 -msgid "Last frame" +#: src/slic3r/GUI/GLCanvas3D.cpp:1711 +msgid "Mirror Object" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:1698 -msgid "ms" +#: src/slic3r/GUI/GLCanvas3D.cpp:2872 +msgid "Move Object" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3434 +#: src/slic3r/GUI/GLCanvas3D.cpp:3389 src/slic3r/GUI/GLCanvas3D.cpp:3609 +#: src/slic3r/GUI/MainFrame.cpp:559 +msgid "Undo" +msgstr "" + +#: src/slic3r/GUI/GLCanvas3D.cpp:3389 src/slic3r/GUI/GLCanvas3D.cpp:3639 +#: src/slic3r/GUI/MainFrame.cpp:562 +msgid "Redo" +msgstr "" + +#: src/slic3r/GUI/GLCanvas3D.cpp:3395 +#, possible-c-format +msgid "%s Stack" +msgstr "" + +#: src/slic3r/GUI/GLCanvas3D.cpp:3413 +#, possible-c-format +msgid "%s %d Action" +msgstr "" + +#: src/slic3r/GUI/GLCanvas3D.cpp:3460 msgid "Add..." msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3444 src/slic3r/GUI/GUI_ObjectList.cpp:1277 -#: src/slic3r/GUI/Plater.cpp:2994 src/slic3r/GUI/Plater.cpp:3013 -#: src/slic3r/GUI/Tab.cpp:2988 +#: src/slic3r/GUI/GLCanvas3D.cpp:3468 src/slic3r/GUI/GUI_ObjectList.cpp:1434 +#: src/slic3r/GUI/Plater.cpp:3467 src/slic3r/GUI/Plater.cpp:3486 +#: src/slic3r/GUI/Tab.cpp:3204 msgid "Delete" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3455 src/slic3r/GUI/Plater.cpp:3375 +#: src/slic3r/GUI/GLCanvas3D.cpp:3477 src/slic3r/GUI/Plater.cpp:4075 msgid "Delete all" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3466 src/slic3r/GUI/KBShortcutsDialog.cpp:134 +#: src/slic3r/GUI/GLCanvas3D.cpp:3486 src/slic3r/GUI/KBShortcutsDialog.cpp:134 +#: src/slic3r/GUI/Plater.cpp:2636 msgid "Arrange" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3480 +#: src/slic3r/GUI/GLCanvas3D.cpp:3486 +msgid "Arrange selection" +msgstr "" + +#: src/slic3r/GUI/GLCanvas3D.cpp:3498 msgid "Copy" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3491 +#: src/slic3r/GUI/GLCanvas3D.cpp:3507 msgid "Paste" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3505 +#: src/slic3r/GUI/GLCanvas3D.cpp:3519 msgid "Add instance" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3517 +#: src/slic3r/GUI/GLCanvas3D.cpp:3530 msgid "Remove instance" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3532 +#: src/slic3r/GUI/GLCanvas3D.cpp:3543 msgid "Split to objects" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3544 src/slic3r/GUI/GUI_ObjectList.cpp:1129 +#: src/slic3r/GUI/GLCanvas3D.cpp:3553 src/slic3r/GUI/GUI_ObjectList.cpp:1280 msgid "Split to parts" msgstr "" -#: src/slic3r/GUI/GLCanvas3D.cpp:3559 +#: src/slic3r/GUI/GLCanvas3D.cpp:3566 msgid "Layers editing" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:35 -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:195 -msgid "Rotate lower part upwards" +#: src/slic3r/GUI/GLCanvas3D.cpp:5623 +msgid "Selection-Add from rectangle" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:36 -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:198 -msgid "Perform cut" +#: src/slic3r/GUI/GLCanvas3D.cpp:5642 +msgid "Selection-Remove from rectangle" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:43 -msgid "Cut object:" -msgstr "" - -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:88 -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:188 src/libslic3r/PrintConfig.cpp:3049 +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:40 +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:144 src/libslic3r/PrintConfig.cpp:3176 msgid "Cut" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:193 +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:149 msgid "Keep upper part" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:194 +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:150 msgid "Keep lower part" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp:32 +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:151 +msgid "Rotate lower part upwards" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoCut.cpp:154 +msgid "Perform cut" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp:45 msgid "Place on face" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoMove.cpp:52 +#: src/slic3r/GUI/Gizmos/GLGizmoMove.cpp:48 msgid "Move" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoMove.cpp:178 +#: src/slic3r/GUI/Gizmos/GLGizmoMove.cpp:177 msgid "Position (mm)" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoMove.cpp:178 +#: src/slic3r/GUI/Gizmos/GLGizmoMove.cpp:177 msgid "Displacement (mm)" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp:458 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:305 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:324 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:342 -#: src/libslic3r/PrintConfig.cpp:3098 +#: src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp:449 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:466 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:485 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:503 +#: src/libslic3r/PrintConfig.cpp:3225 msgid "Rotate" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp:491 +#: src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp:482 msgid "Rotation (deg)" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoScale.cpp:53 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:231 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:325 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:343 -#: src/libslic3r/PrintConfig.cpp:3113 +#: src/slic3r/GUI/Gizmos/GLGizmoScale.cpp:47 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:390 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:486 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:504 +#: src/libslic3r/PrintConfig.cpp:3240 msgid "Scale" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoScale.cpp:291 +#: src/slic3r/GUI/Gizmos/GLGizmoScale.cpp:292 msgid "Scale (%)" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:840 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:44 msgid "Head diameter" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:856 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:45 msgid "Lock supports under new islands" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:860 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1249 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:46 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1427 msgid "Remove selected points" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:864 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:921 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:47 msgid "Remove all points" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:869 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1252 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:48 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1430 msgid "Apply changes" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:874 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1253 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:49 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1431 msgid "Discard changes" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:881 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:50 msgid "Minimal points distance" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:882 -#: src/libslic3r/PrintConfig.cpp:2534 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:51 +#: src/libslic3r/PrintConfig.cpp:2620 msgid "Support points density" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:911 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1255 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:52 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1433 msgid "Auto-generate points" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:917 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:53 msgid "Manual editing" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:934 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:54 msgid "Clipping of view" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:935 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:55 msgid "Reset direction" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1007 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:531 +msgid "Add support point" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:720 +msgid "Delete support point" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:925 +msgid "Change point head diameter" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:991 +msgid "Support parameter change" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1099 msgid "SLA Support Points" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1034 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1138 msgid "Do you want to save your manually edited support points?" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1035 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1139 msgid "Save changes?" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1178 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1183 +msgid "Move support point" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1282 +msgid "Support points edit" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1333 msgid "" "Autogeneration will erase all manually edited points.\n" "\n" "Are you sure you want to do it?\n" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1180 src/slic3r/GUI/GUI.cpp:283 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1335 src/slic3r/GUI/GUI.cpp:289 #: src/slic3r/GUI/WipeTowerDialog.cpp:44 src/slic3r/GUI/WipeTowerDialog.cpp:328 msgid "Warning" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1212 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1338 +msgid "Autogenerate support points" +msgstr "" + +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1390 msgid "SLA gizmo keyboard shortcuts" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1223 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1401 msgid "Note: some shortcuts work in (non)editing mode only." msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1241 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1244 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1245 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1419 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1422 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1423 msgid "Left click" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1241 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1419 msgid "Add point" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1242 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1420 msgid "Right click" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1242 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1420 msgid "Remove point" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1243 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1246 -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1247 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1421 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1424 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1425 msgid "Drag" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1243 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1421 msgid "Move point" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1244 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1422 msgid "Add point to selection" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1245 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1423 msgid "Remove point from selection" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1246 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1424 msgid "Select by rectangle" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1247 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1425 msgid "Deselect by rectangle" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1248 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1426 msgid "Select all points" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1250 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1428 msgid "Mouse wheel" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1250 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1428 msgid "Move clipping plane" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1251 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1429 msgid "Reset clipping plane" msgstr "" -#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1254 +#: src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp:1432 msgid "Switch to editing mode" msgstr "" -#: src/slic3r/GUI/GUI.cpp:142 src/slic3r/GUI/Tab.cpp:2847 +#: src/slic3r/GUI/GUI.cpp:141 src/slic3r/GUI/Tab.cpp:3063 msgid "It's impossible to print multi-part object(s) with SLA technology." msgstr "" -#: src/slic3r/GUI/GUI.cpp:143 +#: src/slic3r/GUI/GUI.cpp:142 msgid "Please check and fix your object list." msgstr "" -#: src/slic3r/GUI/GUI.cpp:144 src/slic3r/GUI/Tab.cpp:2849 +#: src/slic3r/GUI/GUI.cpp:143 src/slic3r/GUI/Plater.cpp:2213 +#: src/slic3r/GUI/Tab.cpp:3065 msgid "Attention!" msgstr "" -#: src/slic3r/GUI/GUI.cpp:277 +#: src/slic3r/GUI/GUI.cpp:283 msgid "Notice" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:401 +#: src/slic3r/GUI/GUI_App.cpp:435 msgid "Changing of an application language" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:409 src/slic3r/GUI/GUI_App.cpp:418 +#: src/slic3r/GUI/GUI_App.cpp:443 src/slic3r/GUI/GUI_App.cpp:452 msgid "Recreating" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:422 +#: src/slic3r/GUI/GUI_App.cpp:456 msgid "Loading of current presets" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:430 +#: src/slic3r/GUI/GUI_App.cpp:464 msgid "Loading of a mode view" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:510 +#: src/slic3r/GUI/GUI_App.cpp:544 msgid "Choose one file (3MF/AMF):" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:522 +#: src/slic3r/GUI/GUI_App.cpp:556 msgid "Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:564 +#: src/slic3r/GUI/GUI_App.cpp:598 msgid "Select the language" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:565 +#: src/slic3r/GUI/GUI_App.cpp:599 msgid "Language" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:716 +#: src/slic3r/GUI/GUI_App.cpp:750 msgid "&Configuration Snapshots" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:716 +#: src/slic3r/GUI/GUI_App.cpp:750 msgid "Inspect / activate configuration snapshots" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:717 +#: src/slic3r/GUI/GUI_App.cpp:751 msgid "Take Configuration &Snapshot" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:717 +#: src/slic3r/GUI/GUI_App.cpp:751 msgid "Capture a configuration snapshot" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:720 +#: src/slic3r/GUI/GUI_App.cpp:754 msgid "&Preferences" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:726 +#: src/slic3r/GUI/GUI_App.cpp:760 msgid "Application preferences" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:729 src/slic3r/GUI/wxExtensions.cpp:2555 +#: src/slic3r/GUI/GUI_App.cpp:763 src/slic3r/GUI/wxExtensions.cpp:2882 msgid "Simple" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:729 +#: src/slic3r/GUI/GUI_App.cpp:763 msgid "Simple View Mode" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:730 src/slic3r/GUI/GUI_ObjectList.cpp:85 -#: src/slic3r/GUI/GUI_ObjectList.cpp:541 src/slic3r/GUI/Tab.cpp:1032 -#: src/slic3r/GUI/Tab.cpp:1047 src/slic3r/GUI/Tab.cpp:1145 -#: src/slic3r/GUI/Tab.cpp:1148 src/slic3r/GUI/Tab.cpp:1551 -#: src/slic3r/GUI/Tab.cpp:1977 src/slic3r/GUI/Tab.cpp:3492 -#: src/slic3r/GUI/wxExtensions.cpp:2556 src/libslic3r/PrintConfig.cpp:73 -#: src/libslic3r/PrintConfig.cpp:188 src/libslic3r/PrintConfig.cpp:351 -#: src/libslic3r/PrintConfig.cpp:999 src/libslic3r/PrintConfig.cpp:2210 +#: src/slic3r/GUI/GUI_App.cpp:764 src/slic3r/GUI/GUI_ObjectList.cpp:93 +#: src/slic3r/GUI/GUI_ObjectList.cpp:567 src/slic3r/GUI/Tab.cpp:1037 +#: src/slic3r/GUI/Tab.cpp:1052 src/slic3r/GUI/Tab.cpp:1150 +#: src/slic3r/GUI/Tab.cpp:1153 src/slic3r/GUI/Tab.cpp:1649 +#: src/slic3r/GUI/Tab.cpp:2120 src/slic3r/GUI/Tab.cpp:3699 +#: src/slic3r/GUI/wxExtensions.cpp:2883 src/libslic3r/PrintConfig.cpp:83 +#: src/libslic3r/PrintConfig.cpp:197 src/libslic3r/PrintConfig.cpp:360 +#: src/libslic3r/PrintConfig.cpp:1013 src/libslic3r/PrintConfig.cpp:2226 msgid "Advanced" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:730 +#: src/slic3r/GUI/GUI_App.cpp:764 msgid "Advanced View Mode" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:731 src/slic3r/GUI/wxExtensions.cpp:2557 +#: src/slic3r/GUI/GUI_App.cpp:765 src/slic3r/GUI/wxExtensions.cpp:2884 msgid "Expert" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:731 +#: src/slic3r/GUI/GUI_App.cpp:765 msgid "Expert View Mode" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:736 +#: src/slic3r/GUI/GUI_App.cpp:770 msgid "Mode" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:736 +#: src/slic3r/GUI/GUI_App.cpp:770 #, possible-c-format msgid "%s View Mode" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:738 +#: src/slic3r/GUI/GUI_App.cpp:772 msgid "Change Application &Language" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:740 +#: src/slic3r/GUI/GUI_App.cpp:774 msgid "Flash printer &firmware" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:740 +#: src/slic3r/GUI/GUI_App.cpp:774 msgid "Upload a firmware image into an Arduino based printer" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:752 +#: src/slic3r/GUI/GUI_App.cpp:786 msgid "Taking configuration snapshot" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:752 +#: src/slic3r/GUI/GUI_App.cpp:786 msgid "Snapshot name" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:795 +#: src/slic3r/GUI/GUI_App.cpp:829 msgid "" "Switching the language will trigger application restart.\n" "You will lose content of the plater." msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:797 +#: src/slic3r/GUI/GUI_App.cpp:831 msgid "Do you want to proceed?" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:798 +#: src/slic3r/GUI/GUI_App.cpp:832 msgid "Language selection" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:817 +#: src/slic3r/GUI/GUI_App.cpp:855 msgid "&Configuration" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:837 +#: src/slic3r/GUI/GUI_App.cpp:877 msgid "The presets on the following tabs were modified" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:837 src/slic3r/GUI/Tab.cpp:2835 +#: src/slic3r/GUI/GUI_App.cpp:877 src/slic3r/GUI/Tab.cpp:3051 msgid "Discard changes and continue anyway?" msgstr "" -#: src/slic3r/GUI/GUI_App.cpp:838 +#: src/slic3r/GUI/GUI_App.cpp:880 msgid "Unsaved Presets" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:28 src/slic3r/GUI/GUI_ObjectList.cpp:77 -#: src/slic3r/GUI/GUI_ObjectList.cpp:533 src/libslic3r/PrintConfig.cpp:57 -#: src/libslic3r/PrintConfig.cpp:151 src/libslic3r/PrintConfig.cpp:382 -#: src/libslic3r/PrintConfig.cpp:439 src/libslic3r/PrintConfig.cpp:447 -#: src/libslic3r/PrintConfig.cpp:853 src/libslic3r/PrintConfig.cpp:1037 -#: src/libslic3r/PrintConfig.cpp:1340 src/libslic3r/PrintConfig.cpp:1406 -#: src/libslic3r/PrintConfig.cpp:1587 src/libslic3r/PrintConfig.cpp:2022 -#: src/libslic3r/PrintConfig.cpp:2079 +#: src/slic3r/GUI/GUI_ObjectList.cpp:30 src/slic3r/GUI/GUI_ObjectList.cpp:84 +#: src/slic3r/GUI/GUI_ObjectList.cpp:558 src/libslic3r/PrintConfig.cpp:67 +#: src/libslic3r/PrintConfig.cpp:160 src/libslic3r/PrintConfig.cpp:392 +#: src/libslic3r/PrintConfig.cpp:453 src/libslic3r/PrintConfig.cpp:461 +#: src/libslic3r/PrintConfig.cpp:867 src/libslic3r/PrintConfig.cpp:1051 +#: src/libslic3r/PrintConfig.cpp:1354 src/libslic3r/PrintConfig.cpp:1420 +#: src/libslic3r/PrintConfig.cpp:1601 src/libslic3r/PrintConfig.cpp:2037 +#: src/libslic3r/PrintConfig.cpp:2095 msgid "Layers and Perimeters" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:29 src/slic3r/GUI/GUI_ObjectList.cpp:78 -#: src/slic3r/GUI/GUI_ObjectList.cpp:534 src/slic3r/GUI/Plater.cpp:446 -#: src/slic3r/GUI/Tab.cpp:1036 src/slic3r/GUI/Tab.cpp:1037 -#: src/slic3r/GUI/Tab.cpp:1395 src/libslic3r/PrintConfig.cpp:168 -#: src/libslic3r/PrintConfig.cpp:390 src/libslic3r/PrintConfig.cpp:740 -#: src/libslic3r/PrintConfig.cpp:754 src/libslic3r/PrintConfig.cpp:791 -#: src/libslic3r/PrintConfig.cpp:944 src/libslic3r/PrintConfig.cpp:954 -#: src/libslic3r/PrintConfig.cpp:972 src/libslic3r/PrintConfig.cpp:990 -#: src/libslic3r/PrintConfig.cpp:1009 src/libslic3r/PrintConfig.cpp:1694 -#: src/libslic3r/PrintConfig.cpp:1711 +#: src/slic3r/GUI/GUI_ObjectList.cpp:31 src/slic3r/GUI/GUI_ObjectList.cpp:85 +#: src/slic3r/GUI/GUI_ObjectList.cpp:559 src/slic3r/GUI/Plater.cpp:498 +#: src/slic3r/GUI/Tab.cpp:1041 src/slic3r/GUI/Tab.cpp:1042 +#: src/slic3r/GUI/Tab.cpp:1394 src/libslic3r/PrintConfig.cpp:177 +#: src/libslic3r/PrintConfig.cpp:400 src/libslic3r/PrintConfig.cpp:420 +#: src/libslic3r/PrintConfig.cpp:754 src/libslic3r/PrintConfig.cpp:768 +#: src/libslic3r/PrintConfig.cpp:805 src/libslic3r/PrintConfig.cpp:958 +#: src/libslic3r/PrintConfig.cpp:968 src/libslic3r/PrintConfig.cpp:986 +#: src/libslic3r/PrintConfig.cpp:1004 src/libslic3r/PrintConfig.cpp:1023 +#: src/libslic3r/PrintConfig.cpp:1708 src/libslic3r/PrintConfig.cpp:1725 msgid "Infill" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:30 src/slic3r/GUI/GUI_ObjectList.cpp:79 -#: src/slic3r/GUI/GUI_ObjectList.cpp:535 src/slic3r/GUI/GUI_Preview.cpp:242 -#: src/slic3r/GUI/Tab.cpp:1065 src/slic3r/GUI/Tab.cpp:1066 -#: src/libslic3r/PrintConfig.cpp:335 src/libslic3r/PrintConfig.cpp:1467 -#: src/libslic3r/PrintConfig.cpp:1815 src/libslic3r/PrintConfig.cpp:1821 -#: src/libslic3r/PrintConfig.cpp:1829 src/libslic3r/PrintConfig.cpp:1841 -#: src/libslic3r/PrintConfig.cpp:1851 src/libslic3r/PrintConfig.cpp:1859 -#: src/libslic3r/PrintConfig.cpp:1874 src/libslic3r/PrintConfig.cpp:1895 -#: src/libslic3r/PrintConfig.cpp:1906 src/libslic3r/PrintConfig.cpp:1922 -#: src/libslic3r/PrintConfig.cpp:1931 src/libslic3r/PrintConfig.cpp:1940 -#: src/libslic3r/PrintConfig.cpp:1951 src/libslic3r/PrintConfig.cpp:1965 -#: src/libslic3r/PrintConfig.cpp:1973 src/libslic3r/PrintConfig.cpp:1974 -#: src/libslic3r/PrintConfig.cpp:1983 src/libslic3r/PrintConfig.cpp:1991 -#: src/libslic3r/PrintConfig.cpp:2005 src/libslic3r/GCode/PreviewData.cpp:172 +#: src/slic3r/GUI/GUI_ObjectList.cpp:32 src/slic3r/GUI/GUI_ObjectList.cpp:86 +#: src/slic3r/GUI/GUI_ObjectList.cpp:560 src/slic3r/GUI/GUI_Preview.cpp:243 +#: src/slic3r/GUI/Tab.cpp:1070 src/slic3r/GUI/Tab.cpp:1071 +#: src/libslic3r/PrintConfig.cpp:344 src/libslic3r/PrintConfig.cpp:1481 +#: src/libslic3r/PrintConfig.cpp:1830 src/libslic3r/PrintConfig.cpp:1836 +#: src/libslic3r/PrintConfig.cpp:1844 src/libslic3r/PrintConfig.cpp:1856 +#: src/libslic3r/PrintConfig.cpp:1866 src/libslic3r/PrintConfig.cpp:1874 +#: src/libslic3r/PrintConfig.cpp:1889 src/libslic3r/PrintConfig.cpp:1910 +#: src/libslic3r/PrintConfig.cpp:1921 src/libslic3r/PrintConfig.cpp:1937 +#: src/libslic3r/PrintConfig.cpp:1946 src/libslic3r/PrintConfig.cpp:1955 +#: src/libslic3r/PrintConfig.cpp:1966 src/libslic3r/PrintConfig.cpp:1980 +#: src/libslic3r/PrintConfig.cpp:1988 src/libslic3r/PrintConfig.cpp:1989 +#: src/libslic3r/PrintConfig.cpp:1998 src/libslic3r/PrintConfig.cpp:2006 +#: src/libslic3r/PrintConfig.cpp:2020 src/libslic3r/GCode/PreviewData.cpp:172 msgid "Support material" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:33 src/slic3r/GUI/GUI_ObjectList.cpp:81 -#: src/slic3r/GUI/GUI_ObjectList.cpp:537 src/slic3r/GUI/Tab.cpp:1125 -#: src/slic3r/GUI/Tab.cpp:1881 src/libslic3r/PrintConfig.cpp:457 -#: src/libslic3r/PrintConfig.cpp:965 src/libslic3r/PrintConfig.cpp:1375 -#: src/libslic3r/PrintConfig.cpp:1703 src/libslic3r/PrintConfig.cpp:1887 -#: src/libslic3r/PrintConfig.cpp:1913 src/libslic3r/PrintConfig.cpp:2186 -#: src/libslic3r/PrintConfig.cpp:2194 -msgid "Extruders" +#: src/slic3r/GUI/GUI_ObjectList.cpp:35 src/slic3r/GUI/GUI_ObjectList.cpp:90 +#: src/slic3r/GUI/GUI_ObjectList.cpp:564 src/libslic3r/PrintConfig.cpp:2202 +#: src/libslic3r/PrintConfig.cpp:2210 +msgid "Wipe options" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:39 +#: src/slic3r/GUI/GUI_ObjectList.cpp:41 msgid "Pad and Support" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:45 +#: src/slic3r/GUI/GUI_ObjectList.cpp:47 msgid "Add part" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:46 +#: src/slic3r/GUI/GUI_ObjectList.cpp:48 msgid "Add modifier" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:47 +#: src/slic3r/GUI/GUI_ObjectList.cpp:49 msgid "Add support enforcer" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:48 +#: src/slic3r/GUI/GUI_ObjectList.cpp:50 msgid "Add support blocker" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:80 src/slic3r/GUI/GUI_ObjectList.cpp:536 -#: src/slic3r/GUI/GUI_Preview.cpp:221 src/slic3r/GUI/Tab.cpp:1090 -#: src/libslic3r/PrintConfig.cpp:200 src/libslic3r/PrintConfig.cpp:427 -#: src/libslic3r/PrintConfig.cpp:882 src/libslic3r/PrintConfig.cpp:1010 -#: src/libslic3r/PrintConfig.cpp:1396 src/libslic3r/PrintConfig.cpp:1633 -#: src/libslic3r/PrintConfig.cpp:1682 src/libslic3r/PrintConfig.cpp:1733 -#: src/libslic3r/PrintConfig.cpp:2064 +#: src/slic3r/GUI/GUI_ObjectList.cpp:87 src/slic3r/GUI/GUI_ObjectList.cpp:561 +#: src/slic3r/GUI/GUI_Preview.cpp:222 src/slic3r/GUI/Tab.cpp:1095 +#: src/libslic3r/PrintConfig.cpp:209 src/libslic3r/PrintConfig.cpp:441 +#: src/libslic3r/PrintConfig.cpp:896 src/libslic3r/PrintConfig.cpp:1024 +#: src/libslic3r/PrintConfig.cpp:1410 src/libslic3r/PrintConfig.cpp:1647 +#: src/libslic3r/PrintConfig.cpp:1696 src/libslic3r/PrintConfig.cpp:1747 +#: src/libslic3r/PrintConfig.cpp:2080 msgid "Speed" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:82 src/slic3r/GUI/GUI_ObjectList.cpp:538 -#: src/libslic3r/PrintConfig.cpp:417 src/libslic3r/PrintConfig.cpp:524 -#: src/libslic3r/PrintConfig.cpp:841 src/libslic3r/PrintConfig.cpp:973 -#: src/libslic3r/PrintConfig.cpp:1384 src/libslic3r/PrintConfig.cpp:1723 -#: src/libslic3r/PrintConfig.cpp:1896 src/libslic3r/PrintConfig.cpp:2053 +#: src/slic3r/GUI/GUI_ObjectList.cpp:88 src/slic3r/GUI/GUI_ObjectList.cpp:562 +#: src/slic3r/GUI/Tab.cpp:1130 src/slic3r/GUI/Tab.cpp:1997 +#: src/libslic3r/PrintConfig.cpp:471 src/libslic3r/PrintConfig.cpp:979 +#: src/libslic3r/PrintConfig.cpp:1389 src/libslic3r/PrintConfig.cpp:1717 +#: src/libslic3r/PrintConfig.cpp:1902 src/libslic3r/PrintConfig.cpp:1928 +msgid "Extruders" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:89 src/slic3r/GUI/GUI_ObjectList.cpp:563 +#: src/libslic3r/PrintConfig.cpp:431 src/libslic3r/PrintConfig.cpp:538 +#: src/libslic3r/PrintConfig.cpp:855 src/libslic3r/PrintConfig.cpp:987 +#: src/libslic3r/PrintConfig.cpp:1398 src/libslic3r/PrintConfig.cpp:1737 +#: src/libslic3r/PrintConfig.cpp:1911 src/libslic3r/PrintConfig.cpp:2069 msgid "Extrusion Width" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:87 src/slic3r/GUI/GUI_ObjectList.cpp:543 -#: src/slic3r/GUI/Plater.cpp:428 src/slic3r/GUI/Tab.cpp:3454 -#: src/slic3r/GUI/Tab.cpp:3455 src/libslic3r/PrintConfig.cpp:2398 -#: src/libslic3r/PrintConfig.cpp:2405 src/libslic3r/PrintConfig.cpp:2414 -#: src/libslic3r/PrintConfig.cpp:2423 src/libslic3r/PrintConfig.cpp:2433 -#: src/libslic3r/PrintConfig.cpp:2459 src/libslic3r/PrintConfig.cpp:2466 -#: src/libslic3r/PrintConfig.cpp:2477 src/libslic3r/PrintConfig.cpp:2487 -#: src/libslic3r/PrintConfig.cpp:2496 src/libslic3r/PrintConfig.cpp:2506 -#: src/libslic3r/PrintConfig.cpp:2515 src/libslic3r/PrintConfig.cpp:2525 -#: src/libslic3r/PrintConfig.cpp:2535 src/libslic3r/PrintConfig.cpp:2543 +#: src/slic3r/GUI/GUI_ObjectList.cpp:95 src/slic3r/GUI/GUI_ObjectList.cpp:569 +#: src/slic3r/GUI/Plater.cpp:466 src/slic3r/GUI/Tab.cpp:3655 +#: src/slic3r/GUI/Tab.cpp:3656 src/libslic3r/PrintConfig.cpp:2469 +#: src/libslic3r/PrintConfig.cpp:2476 src/libslic3r/PrintConfig.cpp:2485 +#: src/libslic3r/PrintConfig.cpp:2494 src/libslic3r/PrintConfig.cpp:2504 +#: src/libslic3r/PrintConfig.cpp:2530 src/libslic3r/PrintConfig.cpp:2537 +#: src/libslic3r/PrintConfig.cpp:2548 src/libslic3r/PrintConfig.cpp:2558 +#: src/libslic3r/PrintConfig.cpp:2567 src/libslic3r/PrintConfig.cpp:2580 +#: src/libslic3r/PrintConfig.cpp:2590 src/libslic3r/PrintConfig.cpp:2599 +#: src/libslic3r/PrintConfig.cpp:2609 src/libslic3r/PrintConfig.cpp:2621 +#: src/libslic3r/PrintConfig.cpp:2629 msgid "Supports" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:88 src/slic3r/GUI/GUI_ObjectList.cpp:544 -#: src/slic3r/GUI/Tab.cpp:3482 src/slic3r/GUI/Tab.cpp:3483 -#: src/libslic3r/PrintConfig.cpp:2551 src/libslic3r/PrintConfig.cpp:2558 -#: src/libslic3r/PrintConfig.cpp:2572 src/libslic3r/PrintConfig.cpp:2582 -#: src/libslic3r/PrintConfig.cpp:2595 src/libslic3r/PrintConfig.cpp:2604 +#: src/slic3r/GUI/GUI_ObjectList.cpp:96 src/slic3r/GUI/GUI_ObjectList.cpp:570 +#: src/slic3r/GUI/Tab.cpp:3684 src/slic3r/GUI/Tab.cpp:3685 +#: src/libslic3r/PrintConfig.cpp:2637 src/libslic3r/PrintConfig.cpp:2644 +#: src/libslic3r/PrintConfig.cpp:2658 src/libslic3r/PrintConfig.cpp:2668 +#: src/libslic3r/PrintConfig.cpp:2681 src/libslic3r/PrintConfig.cpp:2690 +#: src/libslic3r/PrintConfig.cpp:2701 src/libslic3r/PrintConfig.cpp:2712 +#: src/libslic3r/PrintConfig.cpp:2722 src/libslic3r/PrintConfig.cpp:2732 msgid "Pad" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:205 +#: src/slic3r/GUI/GUI_ObjectList.cpp:217 msgid "Name" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:259 +#: src/slic3r/GUI/GUI_ObjectList.cpp:271 #, possible-c-format msgid "Auto-repaired (%d errors):\n" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:266 +#: src/slic3r/GUI/GUI_ObjectList.cpp:278 msgid "degenerate facets" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:267 +#: src/slic3r/GUI/GUI_ObjectList.cpp:279 msgid "edges fixed" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:268 +#: src/slic3r/GUI/GUI_ObjectList.cpp:280 msgid "facets removed" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:269 +#: src/slic3r/GUI/GUI_ObjectList.cpp:281 msgid "facets added" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:270 +#: src/slic3r/GUI/GUI_ObjectList.cpp:282 msgid "facets reversed" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:271 +#: src/slic3r/GUI/GUI_ObjectList.cpp:283 msgid "backwards edges" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:279 +#: src/slic3r/GUI/GUI_ObjectList.cpp:291 msgid "Right button click the icon to fix STL through Netfabb" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:308 +#: src/slic3r/GUI/GUI_ObjectList.cpp:325 msgid "Right button click the icon to change the object settings" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:359 src/slic3r/GUI/GUI_ObjectList.cpp:380 -#: src/slic3r/GUI/GUI_ObjectList.cpp:392 src/slic3r/GUI/GUI_ObjectList.cpp:2850 -#: src/slic3r/GUI/GUI_ObjectList.cpp:2860 -#: src/slic3r/GUI/GUI_ObjectList.cpp:2892 src/slic3r/GUI/wxExtensions.cpp:543 -#: src/slic3r/GUI/wxExtensions.cpp:568 +#: src/slic3r/GUI/GUI_ObjectList.cpp:375 src/slic3r/GUI/GUI_ObjectList.cpp:396 +#: src/slic3r/GUI/GUI_ObjectList.cpp:408 src/slic3r/GUI/GUI_ObjectList.cpp:3508 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3518 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3550 src/slic3r/GUI/wxExtensions.cpp:576 +#: src/slic3r/GUI/wxExtensions.cpp:633 src/slic3r/GUI/wxExtensions.cpp:658 +#: src/slic3r/GUI/wxExtensions.cpp:794 msgid "default" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:364 src/slic3r/GUI/Tab.cpp:1515 -#: src/libslic3r/PrintConfig.cpp:456 +#: src/slic3r/GUI/GUI_ObjectList.cpp:380 src/slic3r/GUI/Tab.cpp:1613 +#: src/libslic3r/PrintConfig.cpp:470 msgid "Extruder" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:883 src/slic3r/GUI/GUI_ObjectList.cpp:1159 -#: src/slic3r/GUI/GUI_ObjectList.cpp:1165 -#: src/slic3r/GUI/GUI_ObjectList.cpp:1388 +#: src/slic3r/GUI/GUI_ObjectList.cpp:493 +msgid "Rename Object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:493 +msgid "Rename Sub-object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:934 src/slic3r/GUI/GUI_ObjectList.cpp:3346 +msgid "Instances to Separated Objects" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:952 +msgid "Remove Volume(s)" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1007 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1316 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1322 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1556 #, possible-c-format msgid "Quick Add Settings (%s)" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:946 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1077 msgid "Select showing settings" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1079 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1126 +msgid "Add Settings for Layers" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1127 +msgid "Add Settings for Sub-object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1128 +msgid "Add Settings for Object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1189 +msgid "Add Settings Bundle for Layers" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1190 +msgid "Add Settings Bundle for Sub-object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1191 +msgid "Add Settings Bundle for Object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1230 msgid "Load" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1084 -#: src/slic3r/GUI/GUI_ObjectList.cpp:1109 -#: src/slic3r/GUI/GUI_ObjectList.cpp:1112 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1235 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1260 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1263 msgid "Box" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1084 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1235 msgid "Cylinder" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1084 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1235 msgid "Sphere" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1084 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1235 msgid "Slab" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1138 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1287 +msgid "Edit Layers" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1295 msgid "Add settings" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1205 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1362 msgid "Change type" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1212 -#: src/slic3r/GUI/GUI_ObjectList.cpp:1342 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1369 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1510 msgid "Set as a Separated Object" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1218 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1375 msgid "Rename" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1229 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1386 msgid "Fix through the Netfabb" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1239 src/slic3r/GUI/Plater.cpp:3023 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1396 src/slic3r/GUI/Plater.cpp:3496 msgid "Export as STL" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1246 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1403 msgid "Change extruder" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1265 src/libslic3r/PrintConfig.cpp:300 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1422 src/libslic3r/PrintConfig.cpp:309 msgid "Default" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1271 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1428 msgid "Select new extruder for the object/part" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1342 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1440 +msgid "Scale to print volume" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1440 +msgid "Scale the selected object to fit the print volume" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1510 msgid "Set as a Separated Objects" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1555 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1585 +msgid "Load Part" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1617 +msgid "Error!" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1662 +msgid "Add Generic Subobject" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1669 msgid "Generic" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1698 -msgid "You can't delete the last solid part from object." +#: src/slic3r/GUI/GUI_ObjectList.cpp:1770 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1872 +msgid "Last instance of an object cannot be deleted." msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1715 -msgid "You can't delete the last intance from object." +#: src/slic3r/GUI/GUI_ObjectList.cpp:1782 +msgid "Delete Settings" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1741 src/slic3r/GUI/Plater.cpp:2343 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1806 +msgid "Delete All Instances from Object" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1822 +msgid "Delete Layers Range" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1853 +msgid "From Object List You can't delete the last solid part from object." +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1857 +msgid "Delete Subobject" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1876 +msgid "Delete Instance" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1900 src/slic3r/GUI/Plater.cpp:2793 msgid "" "The selected object couldn't be split because it contains only one part." msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1850 +#: src/slic3r/GUI/GUI_ObjectList.cpp:1904 +msgid "Split to Parts" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:1950 +msgid "Add Layers" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2075 msgid "Group manipulation" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1862 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2087 msgid "Object manipulation" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1872 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2100 msgid "Object Settings to modify" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1876 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2104 msgid "Part Settings to modify" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1885 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2109 +msgid "Layer range Settings to modify" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2115 msgid "Part manipulation" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:1891 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2121 msgid "Instance manipulation" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2416 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2128 +msgid "Layers Editing" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2128 +msgid "Layer Editing" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2303 +msgid "Delete Selected Item" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2415 +msgid "Delete Selected" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2484 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2513 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2531 +msgid "Add New Layers Range" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2590 +msgid "Edit Layers Range" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2867 +msgid "Selection-Remove from list" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2875 +msgid "Selection-Add from list" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2993 msgid "Object or Instance" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2416 -#: src/slic3r/GUI/GUI_ObjectList.cpp:2547 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2994 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3127 msgid "Part" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2418 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2994 +msgid "Layer" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:2996 msgid "Unsupported selection" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2419 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2997 #, possible-c-format msgid "You started your selection with %s Item." msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2420 +#: src/slic3r/GUI/GUI_ObjectList.cpp:2998 #, possible-c-format msgid "In this mode you can select only other %s Items%s" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2423 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3001 msgid "of a current Object" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2428 -#: src/slic3r/GUI/GUI_ObjectList.cpp:2501 src/slic3r/GUI/Plater.cpp:117 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3006 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3081 src/slic3r/GUI/Plater.cpp:126 msgid "Info" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2542 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3122 msgid "You can't change a type of the last solid part of the object." msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2547 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3127 msgid "Modifier" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2547 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3127 msgid "Support Enforcer" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2547 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3127 msgid "Support Blocker" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2549 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3129 msgid "Type:" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2549 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3129 msgid "Select type of part" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2713 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3134 +msgid "Change Part Type" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectList.cpp:3368 msgid "Enter new name" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2713 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3368 msgid "Renaming" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2729 -#: src/slic3r/GUI/GUI_ObjectList.cpp:2823 src/slic3r/GUI/Tab.cpp:3335 -#: src/slic3r/GUI/Tab.cpp:3339 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3384 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3480 src/slic3r/GUI/Tab.cpp:3536 +#: src/slic3r/GUI/Tab.cpp:3540 msgid "The supplied name is not valid;" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2730 -#: src/slic3r/GUI/GUI_ObjectList.cpp:2824 src/slic3r/GUI/Tab.cpp:3336 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3385 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3481 src/slic3r/GUI/Tab.cpp:3537 msgid "the following characters are not allowed:" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2840 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3498 msgid "Set extruder for selected items" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2841 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3499 msgid "Select extruder number for selected objects and/or parts" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2854 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3512 msgid "Select extruder number:" msgstr "" -#: src/slic3r/GUI/GUI_ObjectList.cpp:2855 +#: src/slic3r/GUI/GUI_ObjectList.cpp:3513 msgid "This extruder will be set for selected items" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:40 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:83 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:62 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:105 msgid "World coordinates" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:41 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:84 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:63 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:106 msgid "Local coordinates" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:60 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:82 msgid "Select coordinate space, in which the transformation will be performed." msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:102 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:125 msgid "Object Manipulation" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:153 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:176 msgid "Object name" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:229 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:282 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:212 +#, possible-c-format +msgid "Toggle %c axis mirroring" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:245 +msgid "Set Mirror" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:285 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:290 +msgid "Reset scale" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:303 +msgid "Reset rotation" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:328 +msgid "Reset Rotation" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:340 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:355 +msgid "Drop to bed" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:388 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:443 msgid "Position" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:230 -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:283 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:389 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:444 msgid "Rotation" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:284 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:445 msgid "Scale factors" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:341 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:502 msgid "Translate" msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:640 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:554 +msgid "" +"You cann't use non-uniform scaling mode for multiple objects/parts selection" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:715 +msgid "Set Position" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:746 +msgid "Set Orientation" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:811 +msgid "Set Scale" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:895 msgid "" "The currently manipulated object is tilted (rotation angles are not " "multiples of 90°).\n" @@ -1613,7 +1905,7 @@ msgid "" "once the rotation is embedded into the object coordinates." msgstr "" -#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:643 +#: src/slic3r/GUI/GUI_ObjectManipulation.cpp:898 msgid "" "This operation is irreversible.\n" "Do you want to proceed?" @@ -1623,117 +1915,127 @@ msgstr "" msgid "Additional Settings" msgstr "" -#: src/slic3r/GUI/GUI_ObjectSettings.cpp:83 +#: src/slic3r/GUI/GUI_ObjectSettings.cpp:94 msgid "Remove parameter" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:215 +#: src/slic3r/GUI/GUI_ObjectSettings.cpp:100 +#, possible-c-format +msgid "Delete Option %s" +msgstr "" + +#: src/slic3r/GUI/GUI_ObjectSettings.cpp:144 +#, possible-c-format +msgid "Change Option %s" +msgstr "" + +#: src/slic3r/GUI/GUI_Preview.cpp:216 msgid "View" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:218 src/slic3r/GUI/GUI_Preview.cpp:544 +#: src/slic3r/GUI/GUI_Preview.cpp:219 src/slic3r/GUI/GUI_Preview.cpp:554 #: src/libslic3r/GCode/PreviewData.cpp:394 msgid "Feature type" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:219 src/libslic3r/PrintConfig.cpp:469 +#: src/slic3r/GUI/GUI_Preview.cpp:220 src/libslic3r/PrintConfig.cpp:483 msgid "Height" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:220 src/libslic3r/PrintConfig.cpp:2172 +#: src/slic3r/GUI/GUI_Preview.cpp:221 src/libslic3r/PrintConfig.cpp:2188 msgid "Width" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:222 +#: src/slic3r/GUI/GUI_Preview.cpp:223 msgid "Volumetric flow rate" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:223 src/slic3r/GUI/GUI_Preview.cpp:321 -#: src/slic3r/GUI/GUI_Preview.cpp:487 src/slic3r/GUI/GUI_Preview.cpp:544 -#: src/slic3r/GUI/GUI_Preview.cpp:720 src/libslic3r/GCode/PreviewData.cpp:404 +#: src/slic3r/GUI/GUI_Preview.cpp:224 src/slic3r/GUI/GUI_Preview.cpp:328 +#: src/slic3r/GUI/GUI_Preview.cpp:506 src/slic3r/GUI/GUI_Preview.cpp:553 +#: src/slic3r/GUI/GUI_Preview.cpp:749 src/libslic3r/GCode/PreviewData.cpp:404 msgid "Tool" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:224 src/slic3r/GUI/GUI_Preview.cpp:542 +#: src/slic3r/GUI/GUI_Preview.cpp:225 src/slic3r/GUI/GUI_Preview.cpp:551 #: src/libslic3r/GCode/PreviewData.cpp:406 msgid "Color Print" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:227 +#: src/slic3r/GUI/GUI_Preview.cpp:228 msgid "Show" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:230 src/slic3r/GUI/GUI_Preview.cpp:231 +#: src/slic3r/GUI/GUI_Preview.cpp:231 src/slic3r/GUI/GUI_Preview.cpp:232 msgid "Feature types" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:233 src/libslic3r/GCode/PreviewData.cpp:163 +#: src/slic3r/GUI/GUI_Preview.cpp:234 src/libslic3r/GCode/PreviewData.cpp:163 msgid "Perimeter" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:234 src/libslic3r/GCode/PreviewData.cpp:164 +#: src/slic3r/GUI/GUI_Preview.cpp:235 src/libslic3r/GCode/PreviewData.cpp:164 msgid "External perimeter" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:235 src/libslic3r/GCode/PreviewData.cpp:165 +#: src/slic3r/GUI/GUI_Preview.cpp:236 src/libslic3r/GCode/PreviewData.cpp:165 msgid "Overhang perimeter" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:236 src/libslic3r/GCode/PreviewData.cpp:166 +#: src/slic3r/GUI/GUI_Preview.cpp:237 src/libslic3r/GCode/PreviewData.cpp:166 msgid "Internal infill" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:237 src/libslic3r/PrintConfig.cpp:1722 -#: src/libslic3r/PrintConfig.cpp:1732 src/libslic3r/GCode/PreviewData.cpp:167 +#: src/slic3r/GUI/GUI_Preview.cpp:238 src/libslic3r/PrintConfig.cpp:1736 +#: src/libslic3r/PrintConfig.cpp:1746 src/libslic3r/GCode/PreviewData.cpp:167 msgid "Solid infill" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:238 src/libslic3r/PrintConfig.cpp:2052 -#: src/libslic3r/PrintConfig.cpp:2063 src/libslic3r/GCode/PreviewData.cpp:168 +#: src/slic3r/GUI/GUI_Preview.cpp:239 src/libslic3r/PrintConfig.cpp:2068 +#: src/libslic3r/PrintConfig.cpp:2079 src/libslic3r/GCode/PreviewData.cpp:168 msgid "Top solid infill" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:239 src/libslic3r/GCode/PreviewData.cpp:169 +#: src/slic3r/GUI/GUI_Preview.cpp:240 src/libslic3r/GCode/PreviewData.cpp:169 msgid "Bridge infill" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:240 src/libslic3r/PrintConfig.cpp:881 +#: src/slic3r/GUI/GUI_Preview.cpp:241 src/libslic3r/PrintConfig.cpp:895 #: src/libslic3r/GCode/PreviewData.cpp:170 msgid "Gap fill" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:241 src/slic3r/GUI/Tab.cpp:1056 +#: src/slic3r/GUI/GUI_Preview.cpp:242 src/slic3r/GUI/Tab.cpp:1061 #: src/libslic3r/GCode/PreviewData.cpp:171 msgid "Skirt" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:243 src/libslic3r/PrintConfig.cpp:1939 +#: src/slic3r/GUI/GUI_Preview.cpp:244 src/libslic3r/PrintConfig.cpp:1954 #: src/libslic3r/GCode/PreviewData.cpp:173 msgid "Support material interface" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:244 src/slic3r/GUI/Tab.cpp:1136 +#: src/slic3r/GUI/GUI_Preview.cpp:245 src/slic3r/GUI/Tab.cpp:1141 #: src/libslic3r/GCode/PreviewData.cpp:174 msgid "Wipe tower" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:249 src/libslic3r/PrintConfig.cpp:2086 +#: src/slic3r/GUI/GUI_Preview.cpp:250 src/libslic3r/PrintConfig.cpp:2102 msgid "Travel" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:250 +#: src/slic3r/GUI/GUI_Preview.cpp:251 msgid "Retractions" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:251 +#: src/slic3r/GUI/GUI_Preview.cpp:252 msgid "Unretractions" msgstr "" -#: src/slic3r/GUI/GUI_Preview.cpp:252 +#: src/slic3r/GUI/GUI_Preview.cpp:253 msgid "Shells" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:13 src/slic3r/GUI/MainFrame.cpp:608 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:13 src/slic3r/GUI/MainFrame.cpp:672 msgid "Keyboard Shortcuts" msgstr "" @@ -1749,8 +2051,8 @@ msgstr "" msgid "Load Config from .ini/amf/3mf/gcode" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:107 src/slic3r/GUI/Plater.cpp:740 -#: src/slic3r/GUI/Plater.cpp:3907 src/libslic3r/PrintConfig.cpp:3000 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:107 src/slic3r/GUI/Plater.cpp:822 +#: src/slic3r/GUI/Plater.cpp:4687 src/libslic3r/PrintConfig.cpp:3127 msgid "Export G-code" msgstr "" @@ -1877,642 +2179,675 @@ msgstr "" #: src/slic3r/GUI/KBShortcutsDialog.cpp:147 msgid "" +"Press to scale selection to fit print volume\n" +"in Gizmo scale" +msgstr "" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:148 +msgid "" "Press to activate deselection rectangle\n" "or to scale or rotate selected objects\n" "around their own center" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:148 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:149 msgid "Press to activate one direction scaling in Gizmo scale" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:149 -msgid "Zoom to Bed" -msgstr "" - #: src/slic3r/GUI/KBShortcutsDialog.cpp:150 -msgid "Zoom to all objects in scene, if none selected" +msgid "Change camera type" msgstr "" #: src/slic3r/GUI/KBShortcutsDialog.cpp:151 -msgid "Zoom to selected object" +msgid "Zoom to Bed" msgstr "" #: src/slic3r/GUI/KBShortcutsDialog.cpp:152 -msgid "Zoom in" +msgid "Zoom to all objects in scene, if none selected" msgstr "" #: src/slic3r/GUI/KBShortcutsDialog.cpp:153 -msgid "Zoom out" +msgid "Zoom to selected object" msgstr "" #: src/slic3r/GUI/KBShortcutsDialog.cpp:154 -msgid "Unselect gizmo / Clear selection" +msgid "Zoom in" +msgstr "" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:155 +msgid "Zoom out" msgstr "" #: src/slic3r/GUI/KBShortcutsDialog.cpp:156 +msgid "Unselect gizmo / Clear selection" +msgstr "" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:158 +msgid "Toggle picking pass texture rendering on/off" +msgstr "" + +#: src/slic3r/GUI/KBShortcutsDialog.cpp:161 msgid "Plater Shortcuts" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:171 -#: src/slic3r/GUI/KBShortcutsDialog.cpp:182 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:176 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:187 msgid "Arrow Up" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:171 -#: src/slic3r/GUI/KBShortcutsDialog.cpp:173 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:176 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:178 msgid "Upper Layer" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:172 -#: src/slic3r/GUI/KBShortcutsDialog.cpp:183 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:177 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:188 msgid "Arrow Down" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:172 -#: src/slic3r/GUI/KBShortcutsDialog.cpp:174 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:177 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:179 msgid "Lower Layer" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:176 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:181 msgid "Preview Shortcuts" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:182 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:187 msgid "Move current slider thumb Up" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:183 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:188 msgid "Move current slider thumb Down" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:184 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:189 msgid "Arrow Left" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:184 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:189 msgid "Set upper thumb to current slider thumb" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:185 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:190 msgid "Arrow Right" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:185 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:190 msgid "Set lower thumb to current slider thumb" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:186 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:191 msgid "Add color change marker for current layer" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:187 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:192 msgid "Delete color change marker for current layer" msgstr "" -#: src/slic3r/GUI/KBShortcutsDialog.cpp:189 +#: src/slic3r/GUI/KBShortcutsDialog.cpp:194 msgid "Layers Slider Shortcuts" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:61 +#: src/slic3r/GUI/MainFrame.cpp:62 msgid "" " - Remember to check for updates at http://github.com/prusa3d/PrusaSlicer/" "releases" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:150 +#: src/slic3r/GUI/MainFrame.cpp:157 msgid "based on Slic3r" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:180 +#: src/slic3r/GUI/MainFrame.cpp:187 msgid "Plater" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:374 +#: src/slic3r/GUI/MainFrame.cpp:393 msgid "&New Project" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:374 +#: src/slic3r/GUI/MainFrame.cpp:393 msgid "Start a new project" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:377 +#: src/slic3r/GUI/MainFrame.cpp:396 msgid "&Open Project" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:377 +#: src/slic3r/GUI/MainFrame.cpp:396 msgid "Open a project file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:380 +#: src/slic3r/GUI/MainFrame.cpp:401 +msgid "Recent projects" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:410 +msgid "The selected project is no more available" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:410 src/slic3r/GUI/MainFrame.cpp:747 +#: src/slic3r/GUI/PrintHostDialogs.cpp:231 +msgid "Error" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:434 msgid "&Save Project" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:380 +#: src/slic3r/GUI/MainFrame.cpp:434 msgid "Save current project file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:384 src/slic3r/GUI/MainFrame.cpp:386 +#: src/slic3r/GUI/MainFrame.cpp:438 src/slic3r/GUI/MainFrame.cpp:440 msgid "Save Project &as" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:384 src/slic3r/GUI/MainFrame.cpp:386 +#: src/slic3r/GUI/MainFrame.cpp:438 src/slic3r/GUI/MainFrame.cpp:440 msgid "Save current project file as" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:394 +#: src/slic3r/GUI/MainFrame.cpp:448 msgid "Import STL/OBJ/AM&F/3MF" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:394 +#: src/slic3r/GUI/MainFrame.cpp:448 msgid "Load a model" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:398 +#: src/slic3r/GUI/MainFrame.cpp:452 msgid "Import &Config" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:398 +#: src/slic3r/GUI/MainFrame.cpp:452 msgid "Load exported configuration file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:400 +#: src/slic3r/GUI/MainFrame.cpp:454 msgid "Import Config from &project" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:400 +#: src/slic3r/GUI/MainFrame.cpp:454 msgid "Load configuration from project file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:403 +#: src/slic3r/GUI/MainFrame.cpp:457 msgid "Import Config &Bundle" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:403 +#: src/slic3r/GUI/MainFrame.cpp:457 msgid "Load presets from a bundle" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:405 +#: src/slic3r/GUI/MainFrame.cpp:459 msgid "&Import" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:408 src/slic3r/GUI/MainFrame.cpp:644 +#: src/slic3r/GUI/MainFrame.cpp:462 src/slic3r/GUI/MainFrame.cpp:708 msgid "Export &G-code" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:408 +#: src/slic3r/GUI/MainFrame.cpp:462 msgid "Export current plate as G-code" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:413 +#: src/slic3r/GUI/MainFrame.cpp:466 src/slic3r/GUI/MainFrame.cpp:709 +msgid "S&end G-code" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:466 +msgid "Send to print current plate as G-code" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:471 msgid "Export plate as &STL" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:413 +#: src/slic3r/GUI/MainFrame.cpp:471 msgid "Export current plate as STL" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:416 +#: src/slic3r/GUI/MainFrame.cpp:474 msgid "Export plate as STL including supports" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:416 +#: src/slic3r/GUI/MainFrame.cpp:474 msgid "Export current plate as STL including supports" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:419 +#: src/slic3r/GUI/MainFrame.cpp:477 msgid "Export plate as &AMF" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:419 +#: src/slic3r/GUI/MainFrame.cpp:477 msgid "Export current plate as AMF" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:423 +#: src/slic3r/GUI/MainFrame.cpp:481 msgid "Export &Config" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:423 +#: src/slic3r/GUI/MainFrame.cpp:481 msgid "Export current configuration to file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:425 +#: src/slic3r/GUI/MainFrame.cpp:483 msgid "Export Config &Bundle" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:425 +#: src/slic3r/GUI/MainFrame.cpp:483 msgid "Export all presets to file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:427 +#: src/slic3r/GUI/MainFrame.cpp:485 msgid "&Export" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:433 +#: src/slic3r/GUI/MainFrame.cpp:491 msgid "Quick Slice" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:433 +#: src/slic3r/GUI/MainFrame.cpp:491 msgid "Slice a file into a G-code" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:439 +#: src/slic3r/GUI/MainFrame.cpp:497 msgid "Quick Slice and Save As" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:439 +#: src/slic3r/GUI/MainFrame.cpp:497 msgid "Slice a file into a G-code, save as" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:445 +#: src/slic3r/GUI/MainFrame.cpp:503 msgid "Repeat Last Quick Slice" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:445 +#: src/slic3r/GUI/MainFrame.cpp:503 msgid "Repeat last quick slice" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:453 +#: src/slic3r/GUI/MainFrame.cpp:511 msgid "(Re)Slice No&w" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:453 +#: src/slic3r/GUI/MainFrame.cpp:511 msgid "Start new slicing process" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:457 +#: src/slic3r/GUI/MainFrame.cpp:515 msgid "&Repair STL file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:457 +#: src/slic3r/GUI/MainFrame.cpp:515 msgid "Automatically repair an STL file" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:460 +#: src/slic3r/GUI/MainFrame.cpp:518 msgid "&Quit" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:460 +#: src/slic3r/GUI/MainFrame.cpp:518 #, possible-c-format msgid "Quit %s" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:485 +#: src/slic3r/GUI/MainFrame.cpp:543 msgid "&Select all" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:488 +#: src/slic3r/GUI/MainFrame.cpp:544 msgid "Selects all objects" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:491 +#: src/slic3r/GUI/MainFrame.cpp:546 msgid "D&eselect all" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:492 +#: src/slic3r/GUI/MainFrame.cpp:547 msgid "Deselects all objects" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:496 +#: src/slic3r/GUI/MainFrame.cpp:550 msgid "&Delete selected" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:497 +#: src/slic3r/GUI/MainFrame.cpp:551 msgid "Deletes the current selection" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:499 +#: src/slic3r/GUI/MainFrame.cpp:553 msgid "Delete &all" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:500 +#: src/slic3r/GUI/MainFrame.cpp:554 msgid "Deletes all objects" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:504 +#: src/slic3r/GUI/MainFrame.cpp:558 +msgid "&Undo" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:561 +msgid "&Redo" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:566 msgid "&Copy" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:505 +#: src/slic3r/GUI/MainFrame.cpp:567 msgid "Copy selection to clipboard" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:507 +#: src/slic3r/GUI/MainFrame.cpp:569 msgid "&Paste" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:508 +#: src/slic3r/GUI/MainFrame.cpp:570 msgid "Paste clipboard" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:517 +#: src/slic3r/GUI/MainFrame.cpp:579 msgid "&Plater Tab" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:517 +#: src/slic3r/GUI/MainFrame.cpp:579 msgid "Show the plater" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:524 +#: src/slic3r/GUI/MainFrame.cpp:586 msgid "P&rint Settings Tab" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:524 +#: src/slic3r/GUI/MainFrame.cpp:586 msgid "Show the print settings" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:526 src/slic3r/GUI/MainFrame.cpp:648 +#: src/slic3r/GUI/MainFrame.cpp:588 src/slic3r/GUI/MainFrame.cpp:711 msgid "&Filament Settings Tab" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:526 +#: src/slic3r/GUI/MainFrame.cpp:588 msgid "Show the filament settings" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:529 +#: src/slic3r/GUI/MainFrame.cpp:591 msgid "Print&er Settings Tab" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:529 +#: src/slic3r/GUI/MainFrame.cpp:591 msgid "Show the printer settings" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:533 +#: src/slic3r/GUI/MainFrame.cpp:595 msgid "3&D" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:533 +#: src/slic3r/GUI/MainFrame.cpp:595 msgid "Show the 3D editing view" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:536 +#: src/slic3r/GUI/MainFrame.cpp:598 msgid "Pre&view" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:536 +#: src/slic3r/GUI/MainFrame.cpp:598 msgid "Show the 3D slices preview" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:555 +#: src/slic3r/GUI/MainFrame.cpp:617 msgid "Print &Host Upload Queue" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:555 +#: src/slic3r/GUI/MainFrame.cpp:617 msgid "Display the Print Host Upload Queue window" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:564 +#: src/slic3r/GUI/MainFrame.cpp:626 msgid "Iso" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:564 +#: src/slic3r/GUI/MainFrame.cpp:626 msgid "Iso View" msgstr "" #. TRN To be shown in the main menu View->Top -#: src/slic3r/GUI/MainFrame.cpp:568 -msgid "Top" -msgstr "" - #. TRN To be shown in Print Settings "Top solid layers" -#: src/libslic3r/PrintConfig.cpp:2078 -msgctxt "Layers" +#: src/slic3r/GUI/MainFrame.cpp:630 src/libslic3r/PrintConfig.cpp:2094 msgid "Top" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:568 +#: src/slic3r/GUI/MainFrame.cpp:630 msgid "Top View" msgstr "" #. TRN To be shown in the main menu View->Bottom -#: src/slic3r/GUI/MainFrame.cpp:571 -msgid "Bottom" -msgstr "" - #. TRN To be shown in Print Settings "Bottom solid layers" -#: src/libslic3r/PrintConfig.cpp:150 -msgctxt "Layers" +#: src/slic3r/GUI/MainFrame.cpp:633 src/libslic3r/PrintConfig.cpp:159 msgid "Bottom" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:571 +#: src/slic3r/GUI/MainFrame.cpp:633 msgid "Bottom View" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:573 +#: src/slic3r/GUI/MainFrame.cpp:635 msgid "Front" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:573 +#: src/slic3r/GUI/MainFrame.cpp:635 msgid "Front View" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:575 src/libslic3r/PrintConfig.cpp:1597 +#: src/slic3r/GUI/MainFrame.cpp:637 src/libslic3r/PrintConfig.cpp:1611 msgid "Rear" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:575 +#: src/slic3r/GUI/MainFrame.cpp:637 msgid "Rear View" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:577 +#: src/slic3r/GUI/MainFrame.cpp:639 msgid "Left" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:577 +#: src/slic3r/GUI/MainFrame.cpp:639 msgid "Left View" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:579 +#: src/slic3r/GUI/MainFrame.cpp:641 msgid "Right" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:579 +#: src/slic3r/GUI/MainFrame.cpp:641 msgid "Right View" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:586 +#: src/slic3r/GUI/MainFrame.cpp:648 msgid "Prusa 3D &Drivers" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:586 +#: src/slic3r/GUI/MainFrame.cpp:648 msgid "Open the Prusa3D drivers download page in your browser" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:588 +#: src/slic3r/GUI/MainFrame.cpp:650 msgid "Software &Releases" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:588 +#: src/slic3r/GUI/MainFrame.cpp:650 msgid "Open the software releases page in your browser" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:594 +#: src/slic3r/GUI/MainFrame.cpp:656 #, possible-c-format msgid "%s &Website" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:595 +#: src/slic3r/GUI/MainFrame.cpp:657 #, possible-c-format msgid "Open the %s website in your browser" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:601 +#: src/slic3r/GUI/MainFrame.cpp:663 msgid "System &Info" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:601 +#: src/slic3r/GUI/MainFrame.cpp:663 msgid "Show system information" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:603 +#: src/slic3r/GUI/MainFrame.cpp:665 msgid "Show &Configuration Folder" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:603 +#: src/slic3r/GUI/MainFrame.cpp:665 msgid "Show user configuration folder (datadir)" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:605 +#: src/slic3r/GUI/MainFrame.cpp:667 msgid "Report an I&ssue" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:605 +#: src/slic3r/GUI/MainFrame.cpp:667 #, possible-c-format msgid "Report an issue on %s" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:607 +#: src/slic3r/GUI/MainFrame.cpp:669 #, possible-c-format msgid "&About %s" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:607 +#: src/slic3r/GUI/MainFrame.cpp:669 msgid "Show about dialog" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:610 +#: src/slic3r/GUI/MainFrame.cpp:672 msgid "Show the list of the keyboard shortcuts" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:618 +#: src/slic3r/GUI/MainFrame.cpp:680 msgid "&File" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:619 +#: src/slic3r/GUI/MainFrame.cpp:681 msgid "&Edit" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:620 +#: src/slic3r/GUI/MainFrame.cpp:682 msgid "&Window" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:621 +#: src/slic3r/GUI/MainFrame.cpp:683 msgid "&View" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:624 +#: src/slic3r/GUI/MainFrame.cpp:686 msgid "&Help" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:646 src/slic3r/GUI/Plater.cpp:3907 -msgid "Export" +#: src/slic3r/GUI/MainFrame.cpp:708 +msgid "E&xport" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:648 +#: src/slic3r/GUI/MainFrame.cpp:709 +msgid "S&end to print" +msgstr "" + +#: src/slic3r/GUI/MainFrame.cpp:711 msgid "Mate&rial Settings Tab" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:669 +#: src/slic3r/GUI/MainFrame.cpp:732 msgid "Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:683 +#: src/slic3r/GUI/MainFrame.cpp:746 msgid "No previously sliced file." msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:684 src/slic3r/GUI/PrintHostDialogs.cpp:231 -msgid "Error" -msgstr "" - -#: src/slic3r/GUI/MainFrame.cpp:689 +#: src/slic3r/GUI/MainFrame.cpp:752 msgid "Previously sliced file (" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:689 +#: src/slic3r/GUI/MainFrame.cpp:752 msgid ") not found." msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:690 +#: src/slic3r/GUI/MainFrame.cpp:753 msgid "File Not Found" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:725 +#: src/slic3r/GUI/MainFrame.cpp:788 #, possible-c-format msgid "Save %s file as:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:725 +#: src/slic3r/GUI/MainFrame.cpp:788 msgid "SVG" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:725 +#: src/slic3r/GUI/MainFrame.cpp:788 msgid "G-code" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:740 +#: src/slic3r/GUI/MainFrame.cpp:803 msgid "Save zip file as:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:750 src/slic3r/GUI/Plater.cpp:2476 -#: src/slic3r/GUI/Plater.cpp:3695 src/slic3r/GUI/Tab.cpp:1165 -#: src/slic3r/GUI/Tab.cpp:3493 +#: src/slic3r/GUI/MainFrame.cpp:815 src/slic3r/GUI/Plater.cpp:2933 +#: src/slic3r/GUI/Plater.cpp:4418 src/slic3r/GUI/Tab.cpp:1170 +#: src/slic3r/GUI/Tab.cpp:3700 msgid "Slicing" msgstr "" #. TRN "Processing input_file_basename" -#: src/slic3r/GUI/MainFrame.cpp:754 +#: src/slic3r/GUI/MainFrame.cpp:817 #, possible-c-format msgid "Processing %s" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:777 +#: src/slic3r/GUI/MainFrame.cpp:840 msgid " was successfully sliced." msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:779 +#: src/slic3r/GUI/MainFrame.cpp:842 msgid "Slicing Done!" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:794 +#: src/slic3r/GUI/MainFrame.cpp:857 msgid "Select the STL file to repair:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:807 +#: src/slic3r/GUI/MainFrame.cpp:870 msgid "Save OBJ file (less prone to coordinate errors than STL) as:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:822 +#: src/slic3r/GUI/MainFrame.cpp:885 msgid "Your file was repaired." msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:822 src/libslic3r/PrintConfig.cpp:3094 +#: src/slic3r/GUI/MainFrame.cpp:885 src/libslic3r/PrintConfig.cpp:3221 msgid "Repair" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:836 +#: src/slic3r/GUI/MainFrame.cpp:899 msgid "Save configuration as:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:856 src/slic3r/GUI/MainFrame.cpp:920 +#: src/slic3r/GUI/MainFrame.cpp:919 src/slic3r/GUI/MainFrame.cpp:983 msgid "Select configuration to load:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:893 +#: src/slic3r/GUI/MainFrame.cpp:956 msgid "Save presets bundle as:" msgstr "" -#: src/slic3r/GUI/MainFrame.cpp:944 +#: src/slic3r/GUI/MainFrame.cpp:1007 #, possible-c-format msgid "%d presets successfully imported." msgstr "" @@ -2527,493 +2862,590 @@ msgstr "" msgid "%s has encountered an error" msgstr "" -#: src/slic3r/GUI/Plater.cpp:137 +#: src/slic3r/GUI/Plater.cpp:146 msgid "Volume" msgstr "" -#: src/slic3r/GUI/Plater.cpp:138 +#: src/slic3r/GUI/Plater.cpp:147 msgid "Facets" msgstr "" -#: src/slic3r/GUI/Plater.cpp:139 +#: src/slic3r/GUI/Plater.cpp:148 msgid "Materials" msgstr "" -#: src/slic3r/GUI/Plater.cpp:142 +#: src/slic3r/GUI/Plater.cpp:151 msgid "Manifold" msgstr "" -#: src/slic3r/GUI/Plater.cpp:192 +#: src/slic3r/GUI/Plater.cpp:201 msgid "Sliced Info" msgstr "" -#: src/slic3r/GUI/Plater.cpp:211 src/slic3r/GUI/Plater.cpp:1049 +#: src/slic3r/GUI/Plater.cpp:220 src/slic3r/GUI/Plater.cpp:1135 msgid "Used Filament (m)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:212 +#: src/slic3r/GUI/Plater.cpp:221 msgid "Used Filament (mm³)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:213 +#: src/slic3r/GUI/Plater.cpp:222 msgid "Used Filament (g)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:214 +#: src/slic3r/GUI/Plater.cpp:223 msgid "Used Material (unit)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:215 src/slic3r/GUI/Plater.cpp:1064 -#: src/libslic3r/PrintConfig.cpp:728 +#: src/slic3r/GUI/Plater.cpp:224 src/slic3r/GUI/Plater.cpp:1150 +#: src/libslic3r/PrintConfig.cpp:742 msgid "Cost" msgstr "" -#: src/slic3r/GUI/Plater.cpp:216 src/slic3r/GUI/Plater.cpp:1036 -#: src/slic3r/GUI/Plater.cpp:1078 +#: src/slic3r/GUI/Plater.cpp:225 src/slic3r/GUI/Plater.cpp:1122 +#: src/slic3r/GUI/Plater.cpp:1164 msgid "Estimated printing time" msgstr "" -#: src/slic3r/GUI/Plater.cpp:217 +#: src/slic3r/GUI/Plater.cpp:226 msgid "Number of tool changes" msgstr "" -#: src/slic3r/GUI/Plater.cpp:291 +#: src/slic3r/GUI/Plater.cpp:317 msgid "Click to edit preset" msgstr "" -#: src/slic3r/GUI/Plater.cpp:431 +#: src/slic3r/GUI/Plater.cpp:469 msgid "Select what kind of support do you need" msgstr "" -#: src/slic3r/GUI/Plater.cpp:433 src/libslic3r/PrintConfig.cpp:1850 -#: src/libslic3r/PrintConfig.cpp:2458 +#: src/slic3r/GUI/Plater.cpp:471 src/libslic3r/PrintConfig.cpp:1865 +#: src/libslic3r/PrintConfig.cpp:2529 msgid "Support on build plate only" msgstr "" -#: src/slic3r/GUI/Plater.cpp:434 src/slic3r/GUI/Plater.cpp:527 +#: src/slic3r/GUI/Plater.cpp:472 src/slic3r/GUI/Plater.cpp:587 msgid "For support enforcers only" msgstr "" -#: src/slic3r/GUI/Plater.cpp:435 +#: src/slic3r/GUI/Plater.cpp:473 msgid "Everywhere" msgstr "" -#: src/slic3r/GUI/Plater.cpp:453 src/slic3r/GUI/Tab.cpp:1062 +#: src/slic3r/GUI/Plater.cpp:505 src/slic3r/GUI/Tab.cpp:1067 msgid "Brim" msgstr "" -#: src/slic3r/GUI/Plater.cpp:455 +#: src/slic3r/GUI/Plater.cpp:507 msgid "" "This flag enables the brim that will be printed around each object on the " "first layer." msgstr "" -#: src/slic3r/GUI/Plater.cpp:463 +#: src/slic3r/GUI/Plater.cpp:515 msgid "Purging volumes" msgstr "" -#: src/slic3r/GUI/Plater.cpp:688 +#: src/slic3r/GUI/Plater.cpp:766 msgid "Print settings" msgstr "" -#: src/slic3r/GUI/Plater.cpp:689 src/slic3r/GUI/Tab.cpp:1506 -#: src/slic3r/GUI/Tab.cpp:1507 +#: src/slic3r/GUI/Plater.cpp:767 src/slic3r/GUI/Tab.cpp:1604 +#: src/slic3r/GUI/Tab.cpp:1605 msgid "Filament" msgstr "" -#: src/slic3r/GUI/Plater.cpp:690 +#: src/slic3r/GUI/Plater.cpp:768 msgid "SLA print settings" msgstr "" -#: src/slic3r/GUI/Plater.cpp:691 src/slic3r/GUI/Preset.cpp:1285 +#: src/slic3r/GUI/Plater.cpp:769 src/slic3r/GUI/Preset.cpp:1310 msgid "SLA material" msgstr "" -#: src/slic3r/GUI/Plater.cpp:692 +#: src/slic3r/GUI/Plater.cpp:770 msgid "Printer" msgstr "" -#: src/slic3r/GUI/Plater.cpp:738 src/slic3r/GUI/Plater.cpp:3908 +#: src/slic3r/GUI/Plater.cpp:820 src/slic3r/GUI/Plater.cpp:4688 msgid "Send to printer" msgstr "" -#: src/slic3r/GUI/Plater.cpp:741 src/slic3r/GUI/Plater.cpp:2476 -#: src/slic3r/GUI/Plater.cpp:3698 +#: src/slic3r/GUI/Plater.cpp:823 src/slic3r/GUI/Plater.cpp:2933 +#: src/slic3r/GUI/Plater.cpp:4421 msgid "Slice now" msgstr "" -#: src/slic3r/GUI/Plater.cpp:881 +#: src/slic3r/GUI/Plater.cpp:963 msgid "Hold Shift to Slice & Export G-code" msgstr "" -#: src/slic3r/GUI/Plater.cpp:982 +#: src/slic3r/GUI/Plater.cpp:1068 #, possible-c-format msgid "%d (%d shells)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:987 +#: src/slic3r/GUI/Plater.cpp:1073 #, possible-c-format msgid "Auto-repaired (%d errors)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:990 +#: src/slic3r/GUI/Plater.cpp:1076 #, possible-c-format msgid "" "%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d " "facets reversed, %d backwards edges" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1000 +#: src/slic3r/GUI/Plater.cpp:1086 msgid "Yes" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1023 +#: src/slic3r/GUI/Plater.cpp:1109 msgid "Used Material (ml)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1026 +#: src/slic3r/GUI/Plater.cpp:1112 msgid "object(s)" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1026 +#: src/slic3r/GUI/Plater.cpp:1112 msgid "supports and pad" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1051 src/slic3r/GUI/Plater.cpp:1066 +#: src/slic3r/GUI/Plater.cpp:1137 src/slic3r/GUI/Plater.cpp:1152 msgid "objects" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1051 src/slic3r/GUI/Plater.cpp:1066 +#: src/slic3r/GUI/Plater.cpp:1137 src/slic3r/GUI/Plater.cpp:1152 msgid "wipe tower" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1081 +#: src/slic3r/GUI/Plater.cpp:1167 msgid "normal mode" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1085 +#: src/slic3r/GUI/Plater.cpp:1171 src/slic3r/GUI/Plater.cpp:1180 +msgid "Color " +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:1176 msgid "stealth mode" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1631 +#: src/slic3r/GUI/Plater.cpp:1271 +msgid "Load File" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:1275 +msgid "Load Files" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:1503 +msgid "ERROR: not enough resources to execute a new job." +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2056 +msgid "New Project" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2173 msgid "Loading" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1641 +#: src/slic3r/GUI/Plater.cpp:2183 #, possible-c-format msgid "Processing input file %s\n" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1699 +#: src/slic3r/GUI/Plater.cpp:2211 +msgid "" +"You can't to load SLA project if there is at least one multi-part object on " +"the bed" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2212 src/slic3r/GUI/Tab.cpp:3064 +msgid "Please check your object list before preset changing." +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2255 msgid "" "This file contains several objects positioned at multiple heights. Instead " "of considering them as multiple objects, should I consider\n" "this file as a single object having multiple parts?\n" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1702 src/slic3r/GUI/Plater.cpp:1810 +#: src/slic3r/GUI/Plater.cpp:2258 src/slic3r/GUI/Plater.cpp:2310 msgid "Multi-part object detected" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1753 +#: src/slic3r/GUI/Plater.cpp:2265 msgid "" "This file cannot be loaded in a simple mode. Do you want to switch to an " "advanced mode?\n" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1754 +#: src/slic3r/GUI/Plater.cpp:2266 msgid "Detected advanced data" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1787 +#: src/slic3r/GUI/Plater.cpp:2287 #, possible-c-format msgid "" "You can't to add the object(s) from %s because of one or some of them " "is(are) multi-part" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1807 +#: src/slic3r/GUI/Plater.cpp:2307 msgid "" "Multiple objects were loaded for a multi-material printer.\n" "Instead of considering them as multiple objects, should I consider\n" "these files to represent a single object having multiple parts?\n" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1823 +#: src/slic3r/GUI/Plater.cpp:2323 msgid "Loaded" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1921 +#: src/slic3r/GUI/Plater.cpp:2418 msgid "" "Your object appears to be too large, so it was automatically scaled down to " "fit your print bed." msgstr "" -#: src/slic3r/GUI/Plater.cpp:1922 +#: src/slic3r/GUI/Plater.cpp:2419 msgid "Object too large?" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1979 +#: src/slic3r/GUI/Plater.cpp:2476 msgid "Export STL file:" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1986 +#: src/slic3r/GUI/Plater.cpp:2483 msgid "Export AMF file:" msgstr "" -#: src/slic3r/GUI/Plater.cpp:1992 +#: src/slic3r/GUI/Plater.cpp:2489 msgid "Save file as:" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2160 -msgid "Arranging canceled" +#: src/slic3r/GUI/Plater.cpp:2592 +msgid "Delete Object" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2163 +#: src/slic3r/GUI/Plater.cpp:2603 +msgid "Reset Project" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2630 src/slic3r/GUI/Plater.cpp:3517 +msgid "Mirror" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2643 +msgid "Optimize Rotation" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2689 msgid "Arranging" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2200 +#: src/slic3r/GUI/Plater.cpp:2712 msgid "Could not arrange model objects! Some geometries may be invalid." msgstr "" -#: src/slic3r/GUI/Plater.cpp:2207 +#: src/slic3r/GUI/Plater.cpp:2718 +msgid "Arranging canceled." +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2719 msgid "Arranging done." msgstr "" -#: src/slic3r/GUI/Plater.cpp:2248 -msgid "Orientation search canceled" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:2253 +#: src/slic3r/GUI/Plater.cpp:2735 msgid "Searching for optimal orientation" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2315 +#: src/slic3r/GUI/Plater.cpp:2768 +msgid "Orientation search canceled." +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2769 msgid "Orientation found." msgstr "" -#: src/slic3r/GUI/Plater.cpp:2335 +#: src/slic3r/GUI/Plater.cpp:2785 msgid "" "The selected object can't be split because it contains more than one volume/" "material." msgstr "" -#: src/slic3r/GUI/Plater.cpp:2461 +#: src/slic3r/GUI/Plater.cpp:2796 +msgid "Split to Objects" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:2918 msgid "Invalid data" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2470 +#: src/slic3r/GUI/Plater.cpp:2927 msgid "Ready to slice" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2508 src/slic3r/GUI/PrintHostDialogs.cpp:232 +#: src/slic3r/GUI/Plater.cpp:2965 src/slic3r/GUI/PrintHostDialogs.cpp:232 msgid "Cancelling" msgstr "" -#: src/slic3r/GUI/Plater.cpp:2525 +#: src/slic3r/GUI/Plater.cpp:2982 msgid "Another export job is currently running." msgstr "" -#: src/slic3r/GUI/Plater.cpp:2786 -msgid "Export failed" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:2791 src/slic3r/GUI/PrintHostDialogs.cpp:233 -msgid "Cancelled" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:2877 src/slic3r/GUI/Plater.cpp:2889 -#: src/slic3r/GUI/Plater.cpp:3000 -msgid "Increase copies" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:2994 src/slic3r/GUI/Plater.cpp:3013 -msgid "Remove the selected object" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3000 -msgid "Place one more copy of the selected object" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3002 -msgid "Decrease copies" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3002 -msgid "Remove one copy of the selected object" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3004 -msgid "Set number of copies" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3004 -msgid "Change the number of copies of the selected object" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3020 +#: src/slic3r/GUI/Plater.cpp:3036 src/slic3r/GUI/Plater.cpp:3493 msgid "Reload from Disk" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3020 +#: src/slic3r/GUI/Plater.cpp:3072 +msgid "Fix Throught NetFabb" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3254 +msgid "Export failed" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3259 src/slic3r/GUI/PrintHostDialogs.cpp:233 +msgid "Cancelled" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3347 src/slic3r/GUI/Plater.cpp:3359 +#: src/slic3r/GUI/Plater.cpp:3473 +msgid "Increase copies" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3467 src/slic3r/GUI/Plater.cpp:3486 +msgid "Remove the selected object" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3473 +msgid "Place one more copy of the selected object" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3475 +msgid "Decrease copies" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3475 +msgid "Remove one copy of the selected object" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3477 +msgid "Set number of copies" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3477 +msgid "Change the number of copies of the selected object" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3493 msgid "Reload the selected file from Disk" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3023 +#: src/slic3r/GUI/Plater.cpp:3496 msgid "Export the selected object as STL file" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3035 +#: src/slic3r/GUI/Plater.cpp:3510 msgid "Along X axis" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3035 +#: src/slic3r/GUI/Plater.cpp:3510 msgid "Mirror the selected object along the X axis" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3037 +#: src/slic3r/GUI/Plater.cpp:3512 msgid "Along Y axis" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3037 +#: src/slic3r/GUI/Plater.cpp:3512 msgid "Mirror the selected object along the Y axis" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3039 +#: src/slic3r/GUI/Plater.cpp:3514 msgid "Along Z axis" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3039 +#: src/slic3r/GUI/Plater.cpp:3514 msgid "Mirror the selected object along the Z axis" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3042 -msgid "Mirror" -msgstr "" - -#: src/slic3r/GUI/Plater.cpp:3042 +#: src/slic3r/GUI/Plater.cpp:3517 msgid "Mirror the selected object" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3054 +#: src/slic3r/GUI/Plater.cpp:3529 msgid "To objects" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3054 src/slic3r/GUI/Plater.cpp:3070 +#: src/slic3r/GUI/Plater.cpp:3529 src/slic3r/GUI/Plater.cpp:3549 msgid "Split the selected object into individual objects" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3056 +#: src/slic3r/GUI/Plater.cpp:3531 msgid "To parts" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3056 src/slic3r/GUI/Plater.cpp:3084 +#: src/slic3r/GUI/Plater.cpp:3531 src/slic3r/GUI/Plater.cpp:3563 msgid "Split the selected object into individual sub-parts" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3059 src/slic3r/GUI/Plater.cpp:3070 -#: src/slic3r/GUI/Plater.cpp:3084 src/libslic3r/PrintConfig.cpp:3118 +#: src/slic3r/GUI/Plater.cpp:3534 src/slic3r/GUI/Plater.cpp:3549 +#: src/slic3r/GUI/Plater.cpp:3563 src/libslic3r/PrintConfig.cpp:3245 msgid "Split" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3059 +#: src/slic3r/GUI/Plater.cpp:3534 msgid "Split the selected object" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3076 +#: src/slic3r/GUI/Plater.cpp:3555 msgid "Optimize orientation" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3076 +#: src/slic3r/GUI/Plater.cpp:3555 msgid "Optimize the rotation of the object for better print results." msgstr "" -#: src/slic3r/GUI/Plater.cpp:3127 +#: src/slic3r/GUI/Plater.cpp:3595 msgid "3D editor view" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3138 src/slic3r/GUI/Tab.cpp:2325 +#: src/slic3r/GUI/Plater.cpp:3603 src/slic3r/GUI/Tab.cpp:2534 msgid "Preview" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3375 +#: src/slic3r/GUI/Plater.cpp:3831 +msgid "" +"%1% printer was active at the time the target Undo / Redo snapshot was " +"taken. Switching to %1% printer requires reloading of %1% presets." +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:3992 +msgid "Load Project" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4016 +msgid "Import Object" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4020 +msgid "Import Objects" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4075 msgid "All objects will be removed, continue ?" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3511 +#: src/slic3r/GUI/Plater.cpp:4083 +msgid "Delete Selected Objects" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4091 +msgid "Increase Instances" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4127 +msgid "Decrease Instances" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4163 +#, possible-c-format +msgid "Set numbers of copies to %d" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4193 +msgid "Cut by Plane" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4225 msgid "Save G-code file as:" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3511 +#: src/slic3r/GUI/Plater.cpp:4225 msgid "Save SL1 file as:" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3623 +#: src/slic3r/GUI/Plater.cpp:4337 #, possible-c-format msgid "STL file exported to %s" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3639 +#: src/slic3r/GUI/Plater.cpp:4353 #, possible-c-format msgid "AMF file exported to %s" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3642 +#: src/slic3r/GUI/Plater.cpp:4356 #, possible-c-format msgid "Error exporting AMF file %s" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3668 +#: src/slic3r/GUI/Plater.cpp:4382 #, possible-c-format msgid "3MF file exported to %s" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3673 +#: src/slic3r/GUI/Plater.cpp:4387 #, possible-c-format msgid "Error exporting 3MF file %s" msgstr "" -#: src/slic3r/GUI/Plater.cpp:3908 +#: src/slic3r/GUI/Plater.cpp:4687 +msgid "Export" +msgstr "" + +#: src/slic3r/GUI/Plater.cpp:4688 msgid "Send G-code" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:19 src/slic3r/GUI/Tab.cpp:1849 -#: src/slic3r/GUI/Tab.cpp:2050 +#: src/slic3r/GUI/Plater.cpp:4772 +msgid "Paste From Clipboard" +msgstr "" + +#: src/slic3r/GUI/Preferences.cpp:22 src/slic3r/GUI/Tab.cpp:1955 +#: src/slic3r/GUI/Tab.cpp:2193 msgid "General" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:36 +#: src/slic3r/GUI/Preferences.cpp:44 msgid "Remember output directory" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:38 +#: src/slic3r/GUI/Preferences.cpp:46 msgid "" "If this is enabled, Slic3r will prompt the last output directory instead of " "the one containing the input files." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:44 +#: src/slic3r/GUI/Preferences.cpp:52 msgid "Auto-center parts" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:46 +#: src/slic3r/GUI/Preferences.cpp:54 msgid "" "If this is enabled, Slic3r will auto-center objects around the print bed " "center." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:52 +#: src/slic3r/GUI/Preferences.cpp:60 msgid "Background processing" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:54 +#: src/slic3r/GUI/Preferences.cpp:62 msgid "" "If this is enabled, Slic3r will pre-process objects as soon as they're " "loaded in order to save time when exporting G-code." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:63 +#: src/slic3r/GUI/Preferences.cpp:71 msgid "" "If enabled, PrusaSlicer will check for the new versions of itself online. " "When a new version becomes available a notification is displayed at the next " @@ -3021,7 +3453,7 @@ msgid "" "notification mechanisms, no automatic installation is done." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:71 +#: src/slic3r/GUI/Preferences.cpp:79 msgid "" "If enabled, Slic3r downloads updates of built-in system presets in the " "background. These updates are downloaded into a separate temporary location. " @@ -3029,76 +3461,90 @@ msgid "" "startup." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:76 +#: src/slic3r/GUI/Preferences.cpp:84 msgid "Suppress \" - default - \" presets" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:78 +#: src/slic3r/GUI/Preferences.cpp:86 msgid "" "Suppress \" - default - \" presets in the Print / Filament / Printer " "selections once there are any other valid presets available." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:84 +#: src/slic3r/GUI/Preferences.cpp:92 msgid "Show incompatible print and filament presets" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:86 +#: src/slic3r/GUI/Preferences.cpp:94 msgid "" "When checked, the print and filament presets are shown in the preset editor " "even if they are marked as incompatible with the active printer" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:93 -msgid "Use legacy OpenGL 1.1 rendering" -msgstr "" - -#: src/slic3r/GUI/Preferences.cpp:95 -msgid "" -"If you have rendering issues caused by a buggy OpenGL 2.0 driver, you may " -"try to check this checkbox. This will disable the layer height editing and " -"anti aliasing, so it is likely better to upgrade your graphics driver." -msgstr "" - -#: src/slic3r/GUI/Preferences.cpp:103 +#: src/slic3r/GUI/Preferences.cpp:101 msgid "Use Retina resolution for the 3D scene" msgstr "" -#: src/slic3r/GUI/Preferences.cpp:105 +#: src/slic3r/GUI/Preferences.cpp:103 msgid "" "If enabled, the 3D scene will be rendered in Retina resolution. If you are " "experiencing 3D performance problems, disabling this option may help." msgstr "" -#: src/slic3r/GUI/Preferences.cpp:130 +#: src/slic3r/GUI/Preferences.cpp:110 +msgid "Use perspective camera" +msgstr "" + +#: src/slic3r/GUI/Preferences.cpp:112 +msgid "" +"If enabled, use perspective camera. If not enabled, use orthographic camera." +msgstr "" + +#: src/slic3r/GUI/Preferences.cpp:117 +msgid "Use custom size for toolbar icons" +msgstr "" + +#: src/slic3r/GUI/Preferences.cpp:119 +msgid "If enabled, you can change size of toolbar icons manually." +msgstr "" + +#: src/slic3r/GUI/Preferences.cpp:144 #, possible-c-format msgid "You need to restart %s to make the changes effective." msgstr "" +#: src/slic3r/GUI/Preferences.cpp:192 +msgid "Icon size in a respect to the default size" +msgstr "" + +#: src/slic3r/GUI/Preferences.cpp:207 +msgid "Select toolbar icon size in respect to the default one." +msgstr "" + #: src/slic3r/GUI/Preset.cpp:212 msgid "modified" msgstr "" -#: src/slic3r/GUI/Preset.cpp:938 src/slic3r/GUI/Preset.cpp:978 -#: src/slic3r/GUI/Preset.cpp:1043 src/slic3r/GUI/Preset.cpp:1075 -#: src/slic3r/GUI/PresetBundle.cpp:1478 src/slic3r/GUI/PresetBundle.cpp:1543 +#: src/slic3r/GUI/Preset.cpp:963 src/slic3r/GUI/Preset.cpp:1003 +#: src/slic3r/GUI/Preset.cpp:1068 src/slic3r/GUI/Preset.cpp:1100 +#: src/slic3r/GUI/PresetBundle.cpp:1480 src/slic3r/GUI/PresetBundle.cpp:1545 msgid "System presets" msgstr "" -#: src/slic3r/GUI/Preset.cpp:982 src/slic3r/GUI/Preset.cpp:1079 -#: src/slic3r/GUI/PresetBundle.cpp:1548 +#: src/slic3r/GUI/Preset.cpp:1007 src/slic3r/GUI/Preset.cpp:1104 +#: src/slic3r/GUI/PresetBundle.cpp:1550 msgid "User presets" msgstr "" -#: src/slic3r/GUI/Preset.cpp:1011 src/slic3r/GUI/Tab.cpp:241 +#: src/slic3r/GUI/Preset.cpp:1036 src/slic3r/GUI/Tab.cpp:241 msgid "Add a new printer" msgstr "" -#: src/slic3r/GUI/Preset.cpp:1283 +#: src/slic3r/GUI/Preset.cpp:1308 msgid "filament" msgstr "" -#: src/slic3r/GUI/Preset.cpp:1284 +#: src/slic3r/GUI/Preset.cpp:1309 msgid "SLA print" msgstr "" @@ -3293,10 +3739,10 @@ msgid "Time" msgstr "" #: src/slic3r/GUI/RammingChart.cpp:76 src/slic3r/GUI/WipeTowerDialog.cpp:82 -#: src/libslic3r/PrintConfig.cpp:613 src/libslic3r/PrintConfig.cpp:657 -#: src/libslic3r/PrintConfig.cpp:672 src/libslic3r/PrintConfig.cpp:2278 -#: src/libslic3r/PrintConfig.cpp:2287 src/libslic3r/PrintConfig.cpp:2347 -#: src/libslic3r/PrintConfig.cpp:2354 +#: src/libslic3r/PrintConfig.cpp:627 src/libslic3r/PrintConfig.cpp:671 +#: src/libslic3r/PrintConfig.cpp:686 src/libslic3r/PrintConfig.cpp:2349 +#: src/libslic3r/PrintConfig.cpp:2358 src/libslic3r/PrintConfig.cpp:2418 +#: src/libslic3r/PrintConfig.cpp:2425 msgid "s" msgstr "" @@ -3304,20 +3750,20 @@ msgstr "" msgid "Volumetric speed" msgstr "" -#: src/slic3r/GUI/RammingChart.cpp:81 src/libslic3r/PrintConfig.cpp:570 -#: src/libslic3r/PrintConfig.cpp:1220 +#: src/slic3r/GUI/RammingChart.cpp:81 src/libslic3r/PrintConfig.cpp:584 +#: src/libslic3r/PrintConfig.cpp:1234 msgid "mm³/s" msgstr "" -#: src/slic3r/GUI/SysInfoDialog.cpp:44 +#: src/slic3r/GUI/SysInfoDialog.cpp:78 msgid "System Information" msgstr "" -#: src/slic3r/GUI/SysInfoDialog.cpp:120 +#: src/slic3r/GUI/SysInfoDialog.cpp:154 msgid "Copy to Clipboard" msgstr "" -#: src/slic3r/GUI/Tab.cpp:52 src/libslic3r/PrintConfig.cpp:230 +#: src/slic3r/GUI/Tab.cpp:52 src/libslic3r/PrintConfig.cpp:239 msgid "Compatible printers" msgstr "" @@ -3325,7 +3771,7 @@ msgstr "" msgid "Select the printers this profile is compatible with." msgstr "" -#: src/slic3r/GUI/Tab.cpp:58 src/libslic3r/PrintConfig.cpp:245 +#: src/slic3r/GUI/Tab.cpp:58 src/libslic3r/PrintConfig.cpp:254 msgid "Compatible print profiles" msgstr "" @@ -3349,204 +3795,206 @@ msgid "" "or click this button." msgstr "" -#: src/slic3r/GUI/Tab.cpp:920 -msgid "It's a default preset." -msgstr "" - #: src/slic3r/GUI/Tab.cpp:921 -msgid "It's a system preset." -msgstr "" - -#: src/slic3r/GUI/Tab.cpp:922 -#, possible-c-format -msgid "Current preset is inherited from %s" +msgid "This is a default preset." msgstr "" #: src/slic3r/GUI/Tab.cpp:923 -msgid "default preset" +msgid "This is a system preset." msgstr "" -#: src/slic3r/GUI/Tab.cpp:927 -msgid "It can't be deleted or modified." +#: src/slic3r/GUI/Tab.cpp:925 +msgid "Current preset is inherited from the default preset." msgstr "" #: src/slic3r/GUI/Tab.cpp:928 +#, possible-c-format +msgid "" +"Current preset is inherited from:\n" +"\t%s" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:932 +msgid "It can't be deleted or modified." +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:933 msgid "" "Any modifications should be saved as a new preset inherited from this one." msgstr "" -#: src/slic3r/GUI/Tab.cpp:929 +#: src/slic3r/GUI/Tab.cpp:934 msgid "To do that please specify a new name for the preset." msgstr "" -#: src/slic3r/GUI/Tab.cpp:933 +#: src/slic3r/GUI/Tab.cpp:938 msgid "Additional information:" msgstr "" -#: src/slic3r/GUI/Tab.cpp:939 +#: src/slic3r/GUI/Tab.cpp:944 msgid "printer model" msgstr "" -#: src/slic3r/GUI/Tab.cpp:947 +#: src/slic3r/GUI/Tab.cpp:952 msgid "default print profile" msgstr "" -#: src/slic3r/GUI/Tab.cpp:950 +#: src/slic3r/GUI/Tab.cpp:955 msgid "default filament profile" msgstr "" -#: src/slic3r/GUI/Tab.cpp:964 +#: src/slic3r/GUI/Tab.cpp:969 msgid "default SLA material profile" msgstr "" -#: src/slic3r/GUI/Tab.cpp:968 +#: src/slic3r/GUI/Tab.cpp:973 msgid "default SLA print profile" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1003 src/slic3r/GUI/Tab.cpp:3419 +#: src/slic3r/GUI/Tab.cpp:1008 src/slic3r/GUI/Tab.cpp:3649 msgid "Layers and perimeters" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1004 src/slic3r/GUI/Tab.cpp:1253 -#: src/libslic3r/PrintConfig.cpp:56 +#: src/slic3r/GUI/Tab.cpp:1009 src/slic3r/GUI/Tab.cpp:1257 +#: src/libslic3r/PrintConfig.cpp:66 msgid "Layer height" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1008 +#: src/slic3r/GUI/Tab.cpp:1013 msgid "Vertical shells" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1019 +#: src/slic3r/GUI/Tab.cpp:1024 msgid "Horizontal shells" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1020 src/libslic3r/PrintConfig.cpp:1745 +#: src/slic3r/GUI/Tab.cpp:1025 src/libslic3r/PrintConfig.cpp:1759 msgid "Solid layers" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1025 +#: src/slic3r/GUI/Tab.cpp:1030 msgid "Quality (slower slicing)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1043 +#: src/slic3r/GUI/Tab.cpp:1048 msgid "Reducing printing time" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1055 +#: src/slic3r/GUI/Tab.cpp:1060 msgid "Skirt and brim" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1072 +#: src/slic3r/GUI/Tab.cpp:1077 msgid "Raft" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1076 +#: src/slic3r/GUI/Tab.cpp:1081 msgid "Options for support material and raft" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1091 +#: src/slic3r/GUI/Tab.cpp:1096 msgid "Speed for print moves" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1103 +#: src/slic3r/GUI/Tab.cpp:1108 msgid "Speed for non-print moves" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1106 +#: src/slic3r/GUI/Tab.cpp:1111 msgid "Modifiers" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1109 +#: src/slic3r/GUI/Tab.cpp:1114 msgid "Acceleration control (advanced)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1116 +#: src/slic3r/GUI/Tab.cpp:1121 msgid "Autospeed (advanced)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1124 +#: src/slic3r/GUI/Tab.cpp:1129 msgid "Multiple Extruders" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1132 +#: src/slic3r/GUI/Tab.cpp:1137 msgid "Ooze prevention" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1149 +#: src/slic3r/GUI/Tab.cpp:1154 msgid "Extrusion width" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1159 +#: src/slic3r/GUI/Tab.cpp:1164 msgid "Overlap" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1162 +#: src/slic3r/GUI/Tab.cpp:1167 msgid "Flow" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1171 +#: src/slic3r/GUI/Tab.cpp:1176 msgid "Other" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1174 src/slic3r/GUI/Tab.cpp:3496 +#: src/slic3r/GUI/Tab.cpp:1179 src/slic3r/GUI/Tab.cpp:3703 msgid "Output options" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1175 +#: src/slic3r/GUI/Tab.cpp:1180 msgid "Sequential printing" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1177 +#: src/slic3r/GUI/Tab.cpp:1182 msgid "Extruder clearance (mm)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1186 src/slic3r/GUI/Tab.cpp:3497 +#: src/slic3r/GUI/Tab.cpp:1191 src/slic3r/GUI/Tab.cpp:3704 msgid "Output file" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1193 src/libslic3r/PrintConfig.cpp:1418 +#: src/slic3r/GUI/Tab.cpp:1198 src/libslic3r/PrintConfig.cpp:1432 msgid "Post-processing scripts" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1199 src/slic3r/GUI/Tab.cpp:1200 -#: src/slic3r/GUI/Tab.cpp:1612 src/slic3r/GUI/Tab.cpp:1613 -#: src/slic3r/GUI/Tab.cpp:2022 src/slic3r/GUI/Tab.cpp:2023 -#: src/slic3r/GUI/Tab.cpp:2116 src/slic3r/GUI/Tab.cpp:2117 -#: src/slic3r/GUI/Tab.cpp:3385 src/slic3r/GUI/Tab.cpp:3386 +#: src/slic3r/GUI/Tab.cpp:1204 src/slic3r/GUI/Tab.cpp:1205 +#: src/slic3r/GUI/Tab.cpp:1716 src/slic3r/GUI/Tab.cpp:1717 +#: src/slic3r/GUI/Tab.cpp:2165 src/slic3r/GUI/Tab.cpp:2166 +#: src/slic3r/GUI/Tab.cpp:2273 src/slic3r/GUI/Tab.cpp:2274 +#: src/slic3r/GUI/Tab.cpp:3586 src/slic3r/GUI/Tab.cpp:3587 msgid "Notes" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1206 src/slic3r/GUI/Tab.cpp:1620 -#: src/slic3r/GUI/Tab.cpp:2029 src/slic3r/GUI/Tab.cpp:2123 -#: src/slic3r/GUI/Tab.cpp:3393 src/slic3r/GUI/Tab.cpp:3502 +#: src/slic3r/GUI/Tab.cpp:1211 src/slic3r/GUI/Tab.cpp:1724 +#: src/slic3r/GUI/Tab.cpp:2172 src/slic3r/GUI/Tab.cpp:2280 +#: src/slic3r/GUI/Tab.cpp:3594 src/slic3r/GUI/Tab.cpp:3709 msgid "Dependencies" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1207 src/slic3r/GUI/Tab.cpp:1621 -#: src/slic3r/GUI/Tab.cpp:2030 src/slic3r/GUI/Tab.cpp:2124 -#: src/slic3r/GUI/Tab.cpp:3394 src/slic3r/GUI/Tab.cpp:3503 +#: src/slic3r/GUI/Tab.cpp:1212 src/slic3r/GUI/Tab.cpp:1725 +#: src/slic3r/GUI/Tab.cpp:2173 src/slic3r/GUI/Tab.cpp:2281 +#: src/slic3r/GUI/Tab.cpp:3595 src/slic3r/GUI/Tab.cpp:3710 msgid "Profile dependencies" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1253 +#: src/slic3r/GUI/Tab.cpp:1256 msgid "" -"Layer height can't be equal to zero.\n" +"Zero layer height is not valid.\n" "\n" -"Shall I set its value to minimum (0.01)?" +"The layer height will be reset to 0.01." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1266 +#: src/slic3r/GUI/Tab.cpp:1268 msgid "" -"First layer height can't be equal to zero.\n" +"Zero first layer height is not valid.\n" "\n" -"Shall I set its value to minimum (0.01)?" +"The first layer height will be reset to 0.01." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1268 src/libslic3r/PrintConfig.cpp:852 +#: src/slic3r/GUI/Tab.cpp:1269 src/libslic3r/PrintConfig.cpp:866 msgid "First layer height" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1284 +#: src/slic3r/GUI/Tab.cpp:1283 #, possible-c-format msgid "" "The Spiral Vase mode requires:\n" @@ -3559,11 +4007,11 @@ msgid "" "Shall I adjust those settings in order to enable Spiral Vase?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1291 +#: src/slic3r/GUI/Tab.cpp:1290 msgid "Spiral Vase" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1312 +#: src/slic3r/GUI/Tab.cpp:1311 msgid "" "The Wipe Tower currently supports the non-soluble supports only\n" "if they are printed with the current extruder without triggering a tool " @@ -3574,11 +4022,11 @@ msgid "" "Shall I adjust those settings in order to enable the Wipe Tower?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1316 src/slic3r/GUI/Tab.cpp:1333 +#: src/slic3r/GUI/Tab.cpp:1315 src/slic3r/GUI/Tab.cpp:1332 msgid "Wipe Tower" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1330 +#: src/slic3r/GUI/Tab.cpp:1329 msgid "" "For the Wipe Tower to work with the soluble supports, the support layers\n" "need to be synchronized with the object layers.\n" @@ -3586,7 +4034,7 @@ msgid "" "Shall I synchronize support layers in order to enable the Wipe Tower?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1348 +#: src/slic3r/GUI/Tab.cpp:1347 msgid "" "Supports work better, if the following feature is enabled:\n" "- Detect bridging perimeters\n" @@ -3594,103 +4042,116 @@ msgid "" "Shall I adjust those settings for supports?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1351 +#: src/slic3r/GUI/Tab.cpp:1350 msgid "Support Generator" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1393 +#: src/slic3r/GUI/Tab.cpp:1392 msgid "" "The %1% infill pattern is not supposed to work at 100%% density.\n" "\n" "Shall I switch to rectilinear fill pattern?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1514 src/libslic3r/PrintConfig.cpp:2015 +#: src/slic3r/GUI/Tab.cpp:1502 src/slic3r/GUI/Tab.cpp:1557 +msgid "Filament Overrides" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:1503 src/slic3r/GUI/Tab.cpp:1562 +#: src/slic3r/GUI/Tab.cpp:2514 +msgid "Retraction" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:1612 src/libslic3r/PrintConfig.cpp:2030 msgid "Temperature" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1520 +#: src/slic3r/GUI/Tab.cpp:1618 msgid "Bed" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1525 +#: src/slic3r/GUI/Tab.cpp:1623 msgid "Cooling" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1526 src/libslic3r/PrintConfig.cpp:1321 -#: src/libslic3r/PrintConfig.cpp:2134 +#: src/slic3r/GUI/Tab.cpp:1624 src/libslic3r/PrintConfig.cpp:1335 +#: src/libslic3r/PrintConfig.cpp:2150 msgid "Enable" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1537 +#: src/slic3r/GUI/Tab.cpp:1635 msgid "Fan settings" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1538 +#: src/slic3r/GUI/Tab.cpp:1636 msgid "Fan speed" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1546 +#: src/slic3r/GUI/Tab.cpp:1644 msgid "Cooling thresholds" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1552 +#: src/slic3r/GUI/Tab.cpp:1650 msgid "Filament properties" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1556 +#: src/slic3r/GUI/Tab.cpp:1654 msgid "Print speed override" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1566 +#: src/slic3r/GUI/Tab.cpp:1664 +msgid "Wipe tower parameters" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:1667 msgid "Toolchange parameters with single extruder MM printers" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1581 +#: src/slic3r/GUI/Tab.cpp:1681 msgid "Ramming settings" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1599 src/slic3r/GUI/Tab.cpp:1985 +#: src/slic3r/GUI/Tab.cpp:1703 src/slic3r/GUI/Tab.cpp:2128 msgid "Custom G-code" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1600 src/slic3r/GUI/Tab.cpp:1986 -#: src/libslic3r/PrintConfig.cpp:1771 src/libslic3r/PrintConfig.cpp:1786 +#: src/slic3r/GUI/Tab.cpp:1704 src/slic3r/GUI/Tab.cpp:2129 +#: src/libslic3r/PrintConfig.cpp:1785 src/libslic3r/PrintConfig.cpp:1800 msgid "Start G-code" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1606 src/slic3r/GUI/Tab.cpp:1992 -#: src/libslic3r/PrintConfig.cpp:360 src/libslic3r/PrintConfig.cpp:370 +#: src/slic3r/GUI/Tab.cpp:1710 src/slic3r/GUI/Tab.cpp:2135 +#: src/libslic3r/PrintConfig.cpp:369 src/libslic3r/PrintConfig.cpp:379 msgid "End G-code" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1737 src/slic3r/GUI/Tab.cpp:1925 +#: src/slic3r/GUI/Tab.cpp:1843 src/slic3r/GUI/Tab.cpp:2068 msgid "Test" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1747 +#: src/slic3r/GUI/Tab.cpp:1853 msgid "Could not get a valid Printer Host reference" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1753 src/slic3r/GUI/Tab.cpp:1938 +#: src/slic3r/GUI/Tab.cpp:1859 src/slic3r/GUI/Tab.cpp:2081 msgid "Success!" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1768 +#: src/slic3r/GUI/Tab.cpp:1874 msgid "" "HTTPS CA file is optional. It is only needed if you use HTTPS with a self-" "signed certificate." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1781 +#: src/slic3r/GUI/Tab.cpp:1887 msgid "Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1782 +#: src/slic3r/GUI/Tab.cpp:1888 msgid "Open CA certificate file" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1810 +#: src/slic3r/GUI/Tab.cpp:1916 #, possible-c-format msgid "" "HTTPS CA File:\n" @@ -3700,278 +4161,283 @@ msgid "" "Store / Keychain." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1850 src/slic3r/GUI/Tab.cpp:2051 +#: src/slic3r/GUI/Tab.cpp:1956 src/slic3r/GUI/Tab.cpp:2194 msgid "Size and coordinates" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1855 src/slic3r/GUI/Tab.cpp:2056 -#: src/slic3r/GUI/Tab.cpp:3040 +#: src/slic3r/GUI/Tab.cpp:1961 src/slic3r/GUI/Tab.cpp:2199 +#: src/slic3r/GUI/Tab.cpp:3256 msgid "Set" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1877 +#: src/slic3r/GUI/Tab.cpp:1993 msgid "Capabilities" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1882 +#: src/slic3r/GUI/Tab.cpp:1998 msgid "Number of extruders of the printer." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1910 +#: src/slic3r/GUI/Tab.cpp:2023 +msgid "" +"Single Extruder Multi Material is selected, \n" +"and all extruders must have the same diameter.\n" +"Do you want to change the diameter for all extruders to first extruder " +"nozzle diameter value?" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:2026 src/slic3r/GUI/Tab.cpp:2484 +#: src/libslic3r/PrintConfig.cpp:1310 +msgid "Nozzle diameter" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:2053 msgid "USB/Serial connection" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1911 src/libslic3r/PrintConfig.cpp:1626 +#: src/slic3r/GUI/Tab.cpp:2054 src/libslic3r/PrintConfig.cpp:1640 msgid "Serial port" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1916 +#: src/slic3r/GUI/Tab.cpp:2059 msgid "Rescan serial ports" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1938 +#: src/slic3r/GUI/Tab.cpp:2081 msgid "Connection to printer works correctly." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1941 +#: src/slic3r/GUI/Tab.cpp:2084 msgid "Connection failed." msgstr "" -#: src/slic3r/GUI/Tab.cpp:1954 src/slic3r/GUI/Tab.cpp:2111 +#: src/slic3r/GUI/Tab.cpp:2097 src/slic3r/GUI/Tab.cpp:2268 msgid "Print Host upload" msgstr "" -#: src/slic3r/GUI/Tab.cpp:1998 src/libslic3r/PrintConfig.cpp:129 +#: src/slic3r/GUI/Tab.cpp:2141 src/libslic3r/PrintConfig.cpp:138 msgid "Before layer change G-code" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2004 src/libslic3r/PrintConfig.cpp:1042 +#: src/slic3r/GUI/Tab.cpp:2147 src/libslic3r/PrintConfig.cpp:1056 msgid "After layer change G-code" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2010 src/libslic3r/PrintConfig.cpp:2041 +#: src/slic3r/GUI/Tab.cpp:2153 src/libslic3r/PrintConfig.cpp:2056 msgid "Tool change G-code" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2016 +#: src/slic3r/GUI/Tab.cpp:2159 msgid "Between objects G-code (for sequential printing)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2078 +#: src/slic3r/GUI/Tab.cpp:2231 msgid "Display" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2089 +#: src/slic3r/GUI/Tab.cpp:2246 msgid "Tilt" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2090 +#: src/slic3r/GUI/Tab.cpp:2247 msgid "Tilt time" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2096 src/slic3r/GUI/Tab.cpp:3367 +#: src/slic3r/GUI/Tab.cpp:2253 src/slic3r/GUI/Tab.cpp:3568 msgid "Corrections" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2173 src/slic3r/GUI/Tab.cpp:2246 -#: src/libslic3r/PrintConfig.cpp:1092 src/libslic3r/PrintConfig.cpp:1110 -#: src/libslic3r/PrintConfig.cpp:1128 src/libslic3r/PrintConfig.cpp:1145 -#: src/libslic3r/PrintConfig.cpp:1156 src/libslic3r/PrintConfig.cpp:1167 -#: src/libslic3r/PrintConfig.cpp:1178 +#: src/slic3r/GUI/Tab.cpp:2333 src/slic3r/GUI/Tab.cpp:2418 +#: src/libslic3r/PrintConfig.cpp:1106 src/libslic3r/PrintConfig.cpp:1124 +#: src/libslic3r/PrintConfig.cpp:1142 src/libslic3r/PrintConfig.cpp:1159 +#: src/libslic3r/PrintConfig.cpp:1170 src/libslic3r/PrintConfig.cpp:1181 +#: src/libslic3r/PrintConfig.cpp:1192 msgid "Machine limits" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2187 +#: src/slic3r/GUI/Tab.cpp:2347 msgid "Values in this column are for Normal mode" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2188 +#: src/slic3r/GUI/Tab.cpp:2348 msgid "Normal" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2193 +#: src/slic3r/GUI/Tab.cpp:2353 msgid "Values in this column are for Stealth mode" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2194 +#: src/slic3r/GUI/Tab.cpp:2354 msgid "Stealth" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2202 +#: src/slic3r/GUI/Tab.cpp:2362 msgid "Maximum feedrates" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2207 +#: src/slic3r/GUI/Tab.cpp:2367 msgid "Maximum accelerations" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2214 +#: src/slic3r/GUI/Tab.cpp:2374 msgid "Jerk limits" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2219 +#: src/slic3r/GUI/Tab.cpp:2379 msgid "Minimum feedrates" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2268 src/slic3r/GUI/Tab.cpp:2276 +#: src/slic3r/GUI/Tab.cpp:2443 src/slic3r/GUI/Tab.cpp:2451 msgid "Single extruder MM setup" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2277 +#: src/slic3r/GUI/Tab.cpp:2452 msgid "Single extruder multimaterial parameters" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2290 src/libslic3r/GCode/PreviewData.cpp:475 +#: src/slic3r/GUI/Tab.cpp:2465 src/libslic3r/GCode/PreviewData.cpp:477 #, possible-c-format msgid "Extruder %d" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2297 +#: src/slic3r/GUI/Tab.cpp:2483 +msgid "Do you want to change the diameter for all extruders?" +msgstr "" + +#: src/slic3r/GUI/Tab.cpp:2506 msgid "Layer height limits" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2302 +#: src/slic3r/GUI/Tab.cpp:2511 msgid "Position (for multi-extruder printers)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2305 -msgid "Retraction" -msgstr "" - -#: src/slic3r/GUI/Tab.cpp:2308 +#: src/slic3r/GUI/Tab.cpp:2517 msgid "Only lift Z" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2321 +#: src/slic3r/GUI/Tab.cpp:2530 msgid "" "Retraction when tool is disabled (advanced settings for multi-extruder " "setups)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2480 +#: src/slic3r/GUI/Tab.cpp:2693 msgid "" "The Wipe option is not available when using the Firmware Retraction mode.\n" "\n" "Shall I disable it in order to enable Firmware Retraction?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2482 +#: src/slic3r/GUI/Tab.cpp:2695 msgid "Firmware Retraction" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2808 +#: src/slic3r/GUI/Tab.cpp:3024 #, possible-c-format msgid "Default preset (%s)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2809 +#: src/slic3r/GUI/Tab.cpp:3025 #, possible-c-format msgid "Preset (%s)" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2826 +#: src/slic3r/GUI/Tab.cpp:3042 msgid "has the following unsaved changes:" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2829 +#: src/slic3r/GUI/Tab.cpp:3045 msgid "is not compatible with printer" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2830 +#: src/slic3r/GUI/Tab.cpp:3046 msgid "is not compatible with print profile" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2832 +#: src/slic3r/GUI/Tab.cpp:3048 msgid "and it has the following unsaved changes:" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2836 +#: src/slic3r/GUI/Tab.cpp:3052 msgid "Unsaved Changes" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2848 -msgid "Please check your object list before preset changing." -msgstr "" - -#: src/slic3r/GUI/Tab.cpp:2927 +#: src/slic3r/GUI/Tab.cpp:3143 msgid "%1% - Copy" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2950 +#: src/slic3r/GUI/Tab.cpp:3166 msgid "The supplied name is empty. It can't be saved." msgstr "" -#: src/slic3r/GUI/Tab.cpp:2955 +#: src/slic3r/GUI/Tab.cpp:3171 msgid "Cannot overwrite a system profile." msgstr "" -#: src/slic3r/GUI/Tab.cpp:2959 +#: src/slic3r/GUI/Tab.cpp:3175 msgid "Cannot overwrite an external profile." msgstr "" -#: src/slic3r/GUI/Tab.cpp:2985 +#: src/slic3r/GUI/Tab.cpp:3201 msgid "remove" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2985 +#: src/slic3r/GUI/Tab.cpp:3201 msgid "delete" msgstr "" #. TRN remove/delete -#: src/slic3r/GUI/Tab.cpp:2987 +#: src/slic3r/GUI/Tab.cpp:3203 msgid "Are you sure you want to %1% the selected preset?" msgstr "" -#: src/slic3r/GUI/Tab.cpp:2988 -msgid "Remove" -msgstr "" - #. TRN Remove/Delete -#: src/slic3r/GUI/Tab.cpp:2990 +#: src/slic3r/GUI/Tab.cpp:3206 msgid "%1% Preset" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3116 +#: src/slic3r/GUI/Tab.cpp:3332 msgid "LOCKED LOCK" msgstr "" #. TRN Description for "LOCKED LOCK" -#: src/slic3r/GUI/Tab.cpp:3118 +#: src/slic3r/GUI/Tab.cpp:3334 msgid "" -"indicates that the settings are the same as the system values for the " -"current option group" +"indicates that the settings are the same as the system (or default) values " +"for the current option group" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3120 +#: src/slic3r/GUI/Tab.cpp:3336 msgid "UNLOCKED LOCK" msgstr "" #. TRN Description for "UNLOCKED LOCK" -#: src/slic3r/GUI/Tab.cpp:3122 +#: src/slic3r/GUI/Tab.cpp:3338 msgid "" "indicates that some settings were changed and are not equal to the system " -"values for the current option group.\n" +"(or default) values for the current option group.\n" "Click the UNLOCKED LOCK icon to reset all settings for current option group " -"to the system values." +"to the system (or default) values." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3127 +#: src/slic3r/GUI/Tab.cpp:3343 msgid "WHITE BULLET" msgstr "" #. TRN Description for "WHITE BULLET" -#: src/slic3r/GUI/Tab.cpp:3129 +#: src/slic3r/GUI/Tab.cpp:3345 msgid "" -"for the left button: \tindicates a non-system preset,\n" +"for the left button: \tindicates a non-system (or non-default) preset,\n" "for the right button: \tindicates that the settings hasn't been modified." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3103 +#: src/slic3r/GUI/Tab.cpp:3348 msgid "BACK ARROW" msgstr "" #. TRN Description for "BACK ARROW" -#: src/slic3r/GUI/Tab.cpp:3134 +#: src/slic3r/GUI/Tab.cpp:3350 msgid "" "indicates that the settings were changed and are not equal to the last saved " "preset for the current option group.\n" @@ -3979,30 +4445,31 @@ msgid "" "to the last saved preset." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3159 +#: src/slic3r/GUI/Tab.cpp:3360 msgid "" -"LOCKED LOCK icon indicates that the settings are the same as the system " -"values for the current option group" +"LOCKED LOCK icon indicates that the settings are the same as the system (or " +"default) values for the current option group" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3161 +#: src/slic3r/GUI/Tab.cpp:3362 msgid "" "UNLOCKED LOCK icon indicates that some settings were changed and are not " -"equal to the system values for the current option group.\n" -"Click to reset all settings for current option group to the system values." +"equal to the system (or default) values for the current option group.\n" +"Click to reset all settings for current option group to the system (or " +"default) values." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3164 -msgid "WHITE BULLET icon indicates a non system preset." +#: src/slic3r/GUI/Tab.cpp:3365 +msgid "WHITE BULLET icon indicates a non system (or non default) preset." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3167 +#: src/slic3r/GUI/Tab.cpp:3368 msgid "" "WHITE BULLET icon indicates that the settings are the same as in the last " "saved preset for the current option group." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3169 +#: src/slic3r/GUI/Tab.cpp:3370 msgid "" "BACK ARROW icon indicates that the settings were changed and are not equal " "to the last saved preset for the current option group.\n" @@ -4010,25 +4477,26 @@ msgid "" "preset." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3175 +#: src/slic3r/GUI/Tab.cpp:3376 msgid "" -"LOCKED LOCK icon indicates that the value is the same as the system value." +"LOCKED LOCK icon indicates that the value is the same as the system (or " +"default) value." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3176 +#: src/slic3r/GUI/Tab.cpp:3377 msgid "" "UNLOCKED LOCK icon indicates that the value was changed and is not equal to " -"the system value.\n" -"Click to reset current value to the system value." +"the system (or default) value.\n" +"Click to reset current value to the system (or default) value." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3182 +#: src/slic3r/GUI/Tab.cpp:3383 msgid "" "WHITE BULLET icon indicates that the value is the same as in the last saved " "preset." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3183 +#: src/slic3r/GUI/Tab.cpp:3384 msgid "" "BACK ARROW icon indicates that the value was changed and is not equal to the " "last saved preset.\n" @@ -4036,80 +4504,81 @@ msgid "" msgstr "" #. TRN Preset -#: src/slic3r/GUI/Tab.cpp:3296 +#: src/slic3r/GUI/Tab.cpp:3497 #, possible-c-format msgid "Save %s as:" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3340 +#: src/slic3r/GUI/Tab.cpp:3541 msgid "the following suffix is not allowed:" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3344 +#: src/slic3r/GUI/Tab.cpp:3545 msgid "The supplied name is not available." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3357 +#: src/slic3r/GUI/Tab.cpp:3558 msgid "Material" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3359 src/slic3r/GUI/Tab.cpp:3450 +#: src/slic3r/GUI/Tab.cpp:3560 src/slic3r/GUI/Tab.cpp:3651 +#: src/slic3r/GUI/wxExtensions.cpp:454 msgid "Layers" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3363 +#: src/slic3r/GUI/Tab.cpp:3564 msgid "Exposure" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3458 +#: src/slic3r/GUI/Tab.cpp:3659 msgid "Support head" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3463 +#: src/slic3r/GUI/Tab.cpp:3664 msgid "Support pillar" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3473 +#: src/slic3r/GUI/Tab.cpp:3675 msgid "Connection of the support sticks and junctions" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3478 +#: src/slic3r/GUI/Tab.cpp:3680 msgid "Automatic generation" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3540 +#: src/slic3r/GUI/Tab.cpp:3747 msgid "Head penetration should not be greater than the head width." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3541 +#: src/slic3r/GUI/Tab.cpp:3751 msgid "Invalid Head penetration" msgstr "" -#: src/slic3r/GUI/Tab.cpp:3553 +#: src/slic3r/GUI/Tab.cpp:3767 msgid "Pinhead diameter should be smaller than the pillar diameter." msgstr "" -#: src/slic3r/GUI/Tab.cpp:3554 +#: src/slic3r/GUI/Tab.cpp:3771 msgid "Invalid pinhead diameter" msgstr "" -#: src/slic3r/GUI/Tab.hpp:318 src/slic3r/GUI/Tab.hpp:411 +#: src/slic3r/GUI/Tab.hpp:324 src/slic3r/GUI/Tab.hpp:422 msgid "Print Settings" msgstr "" -#: src/slic3r/GUI/Tab.hpp:337 +#: src/slic3r/GUI/Tab.hpp:348 msgid "Filament Settings" msgstr "" -#: src/slic3r/GUI/Tab.hpp:372 +#: src/slic3r/GUI/Tab.hpp:383 msgid "Printer Settings" msgstr "" -#: src/slic3r/GUI/Tab.hpp:396 +#: src/slic3r/GUI/Tab.hpp:407 msgid "Material Settings" msgstr "" -#: src/slic3r/GUI/Tab.hpp:423 +#: src/slic3r/GUI/Tab.hpp:434 msgid "Save preset" msgstr "" @@ -4122,39 +4591,39 @@ msgstr "" msgid "New version of %s is available" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:46 +#: src/slic3r/GUI/UpdateDialogs.cpp:45 msgid "Current version:" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:48 +#: src/slic3r/GUI/UpdateDialogs.cpp:47 msgid "New version:" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:56 +#: src/slic3r/GUI/UpdateDialogs.cpp:55 msgid "Changelog && Download" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:63 src/slic3r/GUI/UpdateDialogs.cpp:126 +#: src/slic3r/GUI/UpdateDialogs.cpp:62 src/slic3r/GUI/UpdateDialogs.cpp:125 msgid "Open changelog page" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:68 +#: src/slic3r/GUI/UpdateDialogs.cpp:67 msgid "Open download page" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:74 +#: src/slic3r/GUI/UpdateDialogs.cpp:73 msgid "Don't notify about new releases any more" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:92 src/slic3r/GUI/UpdateDialogs.cpp:206 +#: src/slic3r/GUI/UpdateDialogs.cpp:91 src/slic3r/GUI/UpdateDialogs.cpp:205 msgid "Configuration update" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:92 +#: src/slic3r/GUI/UpdateDialogs.cpp:91 msgid "Configuration update is available" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:95 +#: src/slic3r/GUI/UpdateDialogs.cpp:94 msgid "" "Would you like to install it?\n" "\n" @@ -4164,21 +4633,21 @@ msgid "" "Updated configuration bundles:" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:116 +#: src/slic3r/GUI/UpdateDialogs.cpp:115 msgid "Comment:" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:150 +#: src/slic3r/GUI/UpdateDialogs.cpp:149 #, possible-c-format msgid "%s incompatibility" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:151 +#: src/slic3r/GUI/UpdateDialogs.cpp:150 #, possible-c-format msgid "%s configuration is incompatible" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:156 +#: src/slic3r/GUI/UpdateDialogs.cpp:155 #, possible-c-format msgid "" "This version of %s is not compatible with currently installed configuration " @@ -4191,25 +4660,25 @@ msgid "" "existing configuration before installing files compatible with this %s.\n" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:165 +#: src/slic3r/GUI/UpdateDialogs.cpp:164 #, possible-c-format msgid "This %s version: %s" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:170 +#: src/slic3r/GUI/UpdateDialogs.cpp:169 msgid "Incompatible bundles:" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:186 +#: src/slic3r/GUI/UpdateDialogs.cpp:185 #, possible-c-format msgid "Exit %s" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:189 +#: src/slic3r/GUI/UpdateDialogs.cpp:188 msgid "Re-configure" msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:210 +#: src/slic3r/GUI/UpdateDialogs.cpp:209 #, possible-c-format msgid "" "%s now uses an updated configuration structure.\n" @@ -4225,7 +4694,7 @@ msgid "" "choose whether to enable automatic preset updates." msgstr "" -#: src/slic3r/GUI/UpdateDialogs.cpp:226 +#: src/slic3r/GUI/UpdateDialogs.cpp:225 msgid "For more information please visit our wiki page:" msgstr "" @@ -4318,21 +4787,37 @@ msgstr "" msgid "Show advanced settings" msgstr "" -#: src/slic3r/GUI/wxExtensions.cpp:444 +#: src/slic3r/GUI/wxExtensions.cpp:443 msgid "Instances" msgstr "" -#: src/slic3r/GUI/wxExtensions.cpp:451 src/slic3r/GUI/wxExtensions.cpp:518 +#: src/slic3r/GUI/wxExtensions.cpp:447 src/slic3r/GUI/wxExtensions.cpp:592 #, possible-c-format msgid "Instance %d" msgstr "" -#: src/slic3r/GUI/wxExtensions.cpp:2508 +#: src/slic3r/GUI/wxExtensions.cpp:486 +msgid "Range" +msgstr "" + +#: src/slic3r/GUI/wxExtensions.cpp:2570 +msgid "One layer mode" +msgstr "" + +#: src/slic3r/GUI/wxExtensions.cpp:2571 +msgid "Add/Del color change" +msgstr "" + +#: src/slic3r/GUI/wxExtensions.cpp:2572 +msgid "Discard all color changes" +msgstr "" + +#: src/slic3r/GUI/wxExtensions.cpp:2832 #, possible-c-format msgid "Switch to the %s mode" msgstr "" -#: src/slic3r/GUI/wxExtensions.cpp:2509 +#: src/slic3r/GUI/wxExtensions.cpp:2833 #, possible-c-format msgid "Current mode is %s" msgstr "" @@ -4382,17 +4867,17 @@ msgstr "" msgid "Could not connect to Prusa SLA" msgstr "" -#: src/slic3r/Utils/PresetUpdater.cpp:584 +#: src/slic3r/Utils/PresetUpdater.cpp:614 #, possible-c-format msgid "requires min. %s and max. %s" msgstr "" -#: src/slic3r/Utils/PresetUpdater.cpp:589 +#: src/slic3r/Utils/PresetUpdater.cpp:619 #, possible-c-format msgid "requires min. %s" msgstr "" -#: src/slic3r/Utils/PresetUpdater.cpp:591 +#: src/slic3r/Utils/PresetUpdater.cpp:621 #, possible-c-format msgid "requires max. %s" msgstr "" @@ -4478,215 +4963,219 @@ msgstr "" msgid "Model repair failed: \n" msgstr "" -#: src/libslic3r/Zipper.cpp:35 +#: src/libslic3r/Zipper.cpp:32 msgid "undefined error" msgstr "" -#: src/libslic3r/Zipper.cpp:37 +#: src/libslic3r/Zipper.cpp:34 msgid "too many files" msgstr "" -#: src/libslic3r/Zipper.cpp:39 +#: src/libslic3r/Zipper.cpp:36 msgid "file too large" msgstr "" -#: src/libslic3r/Zipper.cpp:41 +#: src/libslic3r/Zipper.cpp:38 msgid "unsupported method" msgstr "" -#: src/libslic3r/Zipper.cpp:43 +#: src/libslic3r/Zipper.cpp:40 msgid "unsupported encryption" msgstr "" -#: src/libslic3r/Zipper.cpp:45 +#: src/libslic3r/Zipper.cpp:42 msgid "unsupported feature" msgstr "" -#: src/libslic3r/Zipper.cpp:47 +#: src/libslic3r/Zipper.cpp:44 msgid "failed finding central directory" msgstr "" -#: src/libslic3r/Zipper.cpp:49 +#: src/libslic3r/Zipper.cpp:46 msgid "not a ZIP archive" msgstr "" -#: src/libslic3r/Zipper.cpp:51 +#: src/libslic3r/Zipper.cpp:48 msgid "invalid header or archive is corrupted" msgstr "" -#: src/libslic3r/Zipper.cpp:53 +#: src/libslic3r/Zipper.cpp:50 msgid "unsupported multidisk archive" msgstr "" -#: src/libslic3r/Zipper.cpp:55 +#: src/libslic3r/Zipper.cpp:52 msgid "decompression failed or archive is corrupted" msgstr "" -#: src/libslic3r/Zipper.cpp:57 +#: src/libslic3r/Zipper.cpp:54 msgid "compression failed" msgstr "" -#: src/libslic3r/Zipper.cpp:59 +#: src/libslic3r/Zipper.cpp:56 msgid "unexpected decompressed size" msgstr "" -#: src/libslic3r/Zipper.cpp:61 +#: src/libslic3r/Zipper.cpp:58 msgid "CRC-32 check failed" msgstr "" -#: src/libslic3r/Zipper.cpp:63 +#: src/libslic3r/Zipper.cpp:60 msgid "unsupported central directory size" msgstr "" -#: src/libslic3r/Zipper.cpp:65 +#: src/libslic3r/Zipper.cpp:62 msgid "allocation failed" msgstr "" -#: src/libslic3r/Zipper.cpp:67 +#: src/libslic3r/Zipper.cpp:64 msgid "file open failed" msgstr "" -#: src/libslic3r/Zipper.cpp:69 +#: src/libslic3r/Zipper.cpp:66 msgid "file create failed" msgstr "" -#: src/libslic3r/Zipper.cpp:71 +#: src/libslic3r/Zipper.cpp:68 msgid "file write failed" msgstr "" -#: src/libslic3r/Zipper.cpp:73 +#: src/libslic3r/Zipper.cpp:70 msgid "file read failed" msgstr "" -#: src/libslic3r/Zipper.cpp:75 +#: src/libslic3r/Zipper.cpp:72 msgid "file close failed" msgstr "" -#: src/libslic3r/Zipper.cpp:77 +#: src/libslic3r/Zipper.cpp:74 msgid "file seek failed" msgstr "" -#: src/libslic3r/Zipper.cpp:79 +#: src/libslic3r/Zipper.cpp:76 msgid "file stat failed" msgstr "" -#: src/libslic3r/Zipper.cpp:81 +#: src/libslic3r/Zipper.cpp:78 msgid "invalid parameter" msgstr "" -#: src/libslic3r/Zipper.cpp:83 +#: src/libslic3r/Zipper.cpp:80 msgid "invalid filename" msgstr "" -#: src/libslic3r/Zipper.cpp:85 +#: src/libslic3r/Zipper.cpp:82 msgid "buffer too small" msgstr "" -#: src/libslic3r/Zipper.cpp:87 +#: src/libslic3r/Zipper.cpp:84 msgid "internal error" msgstr "" -#: src/libslic3r/Zipper.cpp:89 +#: src/libslic3r/Zipper.cpp:86 msgid "file not found" msgstr "" -#: src/libslic3r/Zipper.cpp:91 +#: src/libslic3r/Zipper.cpp:88 msgid "archive is too large" msgstr "" -#: src/libslic3r/Zipper.cpp:93 +#: src/libslic3r/Zipper.cpp:90 msgid "validation failed" msgstr "" -#: src/libslic3r/Zipper.cpp:95 +#: src/libslic3r/Zipper.cpp:92 msgid "write calledback failed" msgstr "" -#: src/libslic3r/Zipper.cpp:105 +#: src/libslic3r/Zipper.cpp:102 msgid "Error with zip archive" msgstr "" -#: src/libslic3r/Print.cpp:1135 +#: src/libslic3r/Print.cpp:1093 msgid "All objects are outside of the print volume." msgstr "" -#: src/libslic3r/Print.cpp:1162 +#: src/libslic3r/Print.cpp:1120 msgid "Some objects are too close; your extruder will collide with them." msgstr "" -#: src/libslic3r/Print.cpp:1177 +#: src/libslic3r/Print.cpp:1135 msgid "" "Some objects are too tall and cannot be printed without extruder collisions." msgstr "" -#: src/libslic3r/Print.cpp:1187 +#: src/libslic3r/Print.cpp:1145 msgid "The Spiral Vase option can only be used when printing a single object." msgstr "" -#: src/libslic3r/Print.cpp:1189 +#: src/libslic3r/Print.cpp:1147 msgid "" "The Spiral Vase option can only be used when printing single material " "objects." msgstr "" -#: src/libslic3r/Print.cpp:1195 +#: src/libslic3r/Print.cpp:1155 msgid "" -"All extruders must have the same diameter for single extruder multimaterial " -"printer." +"The wipe tower is only supported if all extruders have the same nozzle " +"diameter and use filaments of the same diameter." msgstr "" -#: src/libslic3r/Print.cpp:1200 +#: src/libslic3r/Print.cpp:1159 msgid "" "The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter " "and Repetier G-code flavors." msgstr "" -#: src/libslic3r/Print.cpp:1202 +#: src/libslic3r/Print.cpp:1161 msgid "" "The Wipe Tower is currently only supported with the relative extruder " "addressing (use_relative_e_distances=1)." msgstr "" -#: src/libslic3r/Print.cpp:1223 +#: src/libslic3r/Print.cpp:1165 +msgid "All extruders must have the same diameter for the Wipe Tower." +msgstr "" + +#: src/libslic3r/Print.cpp:1186 msgid "" "The Wipe Tower is only supported for multiple objects if they have equal " "layer heights" msgstr "" -#: src/libslic3r/Print.cpp:1225 +#: src/libslic3r/Print.cpp:1188 msgid "" "The Wipe Tower is only supported for multiple objects if they are printed " "over an equal number of raft layers" msgstr "" -#: src/libslic3r/Print.cpp:1227 +#: src/libslic3r/Print.cpp:1190 msgid "" "The Wipe Tower is only supported for multiple objects if they are printed " "with the same support_material_contact_distance" msgstr "" -#: src/libslic3r/Print.cpp:1229 +#: src/libslic3r/Print.cpp:1192 msgid "" "The Wipe Tower is only supported for multiple objects if they are sliced " "equally." msgstr "" -#: src/libslic3r/Print.cpp:1258 +#: src/libslic3r/Print.cpp:1220 msgid "" "The Wipe tower is only supported if all objects have the same layer height " "profile" msgstr "" -#: src/libslic3r/Print.cpp:1268 +#: src/libslic3r/Print.cpp:1230 msgid "The supplied settings will cause an empty print." msgstr "" -#: src/libslic3r/Print.cpp:1285 +#: src/libslic3r/Print.cpp:1247 msgid "" "One or more object were assigned an extruder that the printer does not have." msgstr "" -#: src/libslic3r/Print.cpp:1294 +#: src/libslic3r/Print.cpp:1256 msgid "" "Printing with multiple extruders of differing nozzle diameters. If support " "is to be printed with the current extruder (support_material_extruder == 0 " @@ -4694,13 +5183,13 @@ msgid "" "same diameter." msgstr "" -#: src/libslic3r/Print.cpp:1302 +#: src/libslic3r/Print.cpp:1264 msgid "" "For the Wipe Tower to work with the soluble supports, the support layers " "need to be synchronized with the object layers." msgstr "" -#: src/libslic3r/Print.cpp:1306 +#: src/libslic3r/Print.cpp:1268 msgid "" "The Wipe Tower currently supports the non-soluble supports only if they are " "printed with the current extruder without triggering a tool change. (both " @@ -4708,83 +5197,90 @@ msgid "" "set to 0)." msgstr "" -#: src/libslic3r/Print.cpp:1328 +#: src/libslic3r/Print.cpp:1290 msgid "First layer height can't be greater than nozzle diameter" msgstr "" -#: src/libslic3r/Print.cpp:1332 +#: src/libslic3r/Print.cpp:1294 msgid "Layer height can't be greater than nozzle diameter" msgstr "" -#: src/libslic3r/Print.cpp:1476 +#: src/libslic3r/Print.cpp:1438 msgid "Infilling layers" msgstr "" -#: src/libslic3r/Print.cpp:1484 +#: src/libslic3r/Print.cpp:1446 msgid "Generating skirt" msgstr "" -#: src/libslic3r/Print.cpp:1492 +#: src/libslic3r/Print.cpp:1454 msgid "Generating brim" msgstr "" -#: src/libslic3r/Print.cpp:1520 +#: src/libslic3r/Print.cpp:1482 msgid "Exporting G-code" msgstr "" -#: src/libslic3r/Print.cpp:1524 +#: src/libslic3r/Print.cpp:1486 msgid "Generating G-code" msgstr "" -#: src/libslic3r/SLAPrint.cpp:57 +#: src/libslic3r/SLAPrint.cpp:58 msgid "Slicing model" msgstr "" -#: src/libslic3r/SLAPrint.cpp:58 src/libslic3r/SLAPrint.cpp:819 +#: src/libslic3r/SLAPrint.cpp:59 src/libslic3r/SLAPrint.cpp:871 msgid "Generating support points" msgstr "" -#: src/libslic3r/SLAPrint.cpp:59 +#: src/libslic3r/SLAPrint.cpp:60 msgid "Generating support tree" msgstr "" -#: src/libslic3r/SLAPrint.cpp:60 +#: src/libslic3r/SLAPrint.cpp:61 msgid "Generating pad" msgstr "" -#: src/libslic3r/SLAPrint.cpp:61 +#: src/libslic3r/SLAPrint.cpp:62 msgid "Slicing supports" msgstr "" -#: src/libslic3r/SLAPrint.cpp:78 +#: src/libslic3r/SLAPrint.cpp:79 msgid "Merging slices and calculating statistics" msgstr "" -#: src/libslic3r/SLAPrint.cpp:79 +#: src/libslic3r/SLAPrint.cpp:80 msgid "Rasterizing layers" msgstr "" -#: src/libslic3r/SLAPrint.cpp:622 +#: src/libslic3r/SLAPrint.cpp:650 msgid "" "Cannot proceed without support points! Add support points or disable support " "generation." msgstr "" -#: src/libslic3r/SLAPrint.cpp:634 +#: src/libslic3r/SLAPrint.cpp:664 msgid "Elevation is too low for object." msgstr "" -#. TRN To be shown at the status bar on SLA slicing error. -#: src/libslic3r/SLAPrint.cpp:719 -msgid "Slicing had to be stopped due to an internal error." +#: src/libslic3r/SLAPrint.cpp:670 +msgid "" +"The endings of the support pillars will be deployed on the gap between the " +"object and the pad. 'Support base safety distance' has to be greater than " +"the 'Pad object gap' parameter to avoid this." msgstr "" -#: src/libslic3r/SLAPrint.cpp:867 src/libslic3r/SLAPrint.cpp:877 -#: src/libslic3r/SLAPrint.cpp:925 +#: src/libslic3r/SLAPrint.cpp:759 +msgid "" +"Slicing had to be stopped due to an internal error: Inconsistent slice index." +msgstr "" + +#: src/libslic3r/SLAPrint.cpp:954 src/libslic3r/SLAPrint.cpp:964 +#: src/libslic3r/SLAPrint.cpp:1005 msgid "Visualizing supports" msgstr "" -#: src/libslic3r/SLAPrint.cpp:1463 +#: src/libslic3r/SLAPrint.cpp:1537 msgid "Slicing done" msgstr "" @@ -4800,101 +5296,109 @@ msgstr "" msgid "Bed shape" msgstr "" -#: src/libslic3r/PrintConfig.cpp:58 +#: src/libslic3r/PrintConfig.cpp:56 +msgid "Bed custom texture" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:61 +msgid "Bed custom model" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:68 msgid "" "This setting controls the height (and thus the total number) of the slices/" "layers. Thinner layers give better accuracy but take more time to print." msgstr "" -#: src/libslic3r/PrintConfig.cpp:65 +#: src/libslic3r/PrintConfig.cpp:75 msgid "Max print height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:66 +#: src/libslic3r/PrintConfig.cpp:76 msgid "" "Set this to the maximum height that can be reached by your extruder while " "printing." msgstr "" -#: src/libslic3r/PrintConfig.cpp:72 +#: src/libslic3r/PrintConfig.cpp:82 msgid "Slice gap closing radius" msgstr "" -#: src/libslic3r/PrintConfig.cpp:74 +#: src/libslic3r/PrintConfig.cpp:84 msgid "" "Cracks smaller than 2x gap closing radius are being filled during the " "triangle mesh slicing. The gap closing operation may reduce the final print " "resolution, therefore it is advisable to keep the value reasonably low." msgstr "" -#: src/libslic3r/PrintConfig.cpp:82 +#: src/libslic3r/PrintConfig.cpp:92 msgid "Hostname, IP or URL" msgstr "" -#: src/libslic3r/PrintConfig.cpp:83 +#: src/libslic3r/PrintConfig.cpp:93 msgid "" "Slic3r can upload G-code files to a printer host. This field should contain " "the hostname, IP address or URL of the printer host instance." msgstr "" -#: src/libslic3r/PrintConfig.cpp:89 +#: src/libslic3r/PrintConfig.cpp:99 msgid "API Key / Password" msgstr "" -#: src/libslic3r/PrintConfig.cpp:90 +#: src/libslic3r/PrintConfig.cpp:100 msgid "" "Slic3r can upload G-code files to a printer host. This field should contain " "the API Key or the password required for authentication." msgstr "" -#: src/libslic3r/PrintConfig.cpp:96 +#: src/libslic3r/PrintConfig.cpp:106 msgid "HTTPS CA File" msgstr "" -#: src/libslic3r/PrintConfig.cpp:97 +#: src/libslic3r/PrintConfig.cpp:107 msgid "" "Custom CA certificate file can be specified for HTTPS OctoPrint connections, " "in crt/pem format. If left blank, the default OS CA certificate repository " "is used." msgstr "" -#: src/libslic3r/PrintConfig.cpp:112 +#: src/libslic3r/PrintConfig.cpp:121 msgid "Avoid crossing perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:113 +#: src/libslic3r/PrintConfig.cpp:122 msgid "" "Optimize travel moves in order to minimize the crossing of perimeters. This " "is mostly useful with Bowden extruders which suffer from oozing. This " "feature slows down both the print and the G-code generation." msgstr "" -#: src/libslic3r/PrintConfig.cpp:120 src/libslic3r/PrintConfig.cpp:2012 +#: src/libslic3r/PrintConfig.cpp:129 src/libslic3r/PrintConfig.cpp:2027 msgid "Other layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:121 +#: src/libslic3r/PrintConfig.cpp:130 msgid "" "Bed temperature for layers after the first one. Set this to zero to disable " "bed temperature control commands in the output." msgstr "" -#: src/libslic3r/PrintConfig.cpp:123 +#: src/libslic3r/PrintConfig.cpp:132 msgid "Bed temperature" msgstr "" -#: src/libslic3r/PrintConfig.cpp:130 +#: src/libslic3r/PrintConfig.cpp:139 msgid "" "This custom code is inserted at every layer change, right before the Z move. " "Note that you can use placeholder variables for all Slic3r settings as well " "as [layer_num] and [layer_z]." msgstr "" -#: src/libslic3r/PrintConfig.cpp:140 +#: src/libslic3r/PrintConfig.cpp:149 msgid "Between objects G-code" msgstr "" -#: src/libslic3r/PrintConfig.cpp:141 +#: src/libslic3r/PrintConfig.cpp:150 msgid "" "This code is inserted between objects when using sequential printing. By " "default extruder and bed temperature are reset using non-wait command; " @@ -4904,70 +5408,70 @@ msgid "" "S[first_layer_temperature]\" command wherever you want." msgstr "" -#: src/libslic3r/PrintConfig.cpp:152 +#: src/libslic3r/PrintConfig.cpp:161 msgid "Number of solid layers to generate on bottom surfaces." msgstr "" -#: src/libslic3r/PrintConfig.cpp:153 +#: src/libslic3r/PrintConfig.cpp:162 msgid "Bottom solid layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:158 +#: src/libslic3r/PrintConfig.cpp:167 msgid "Bridge" msgstr "" -#: src/libslic3r/PrintConfig.cpp:159 +#: src/libslic3r/PrintConfig.cpp:168 msgid "" "This is the acceleration your printer will use for bridges. Set zero to " "disable acceleration control for bridges." msgstr "" -#: src/libslic3r/PrintConfig.cpp:161 src/libslic3r/PrintConfig.cpp:304 -#: src/libslic3r/PrintConfig.cpp:826 src/libslic3r/PrintConfig.cpp:947 -#: src/libslic3r/PrintConfig.cpp:1116 src/libslic3r/PrintConfig.cpp:1169 -#: src/libslic3r/PrintConfig.cpp:1180 src/libslic3r/PrintConfig.cpp:1369 +#: src/libslic3r/PrintConfig.cpp:170 src/libslic3r/PrintConfig.cpp:313 +#: src/libslic3r/PrintConfig.cpp:840 src/libslic3r/PrintConfig.cpp:961 +#: src/libslic3r/PrintConfig.cpp:1130 src/libslic3r/PrintConfig.cpp:1183 +#: src/libslic3r/PrintConfig.cpp:1194 src/libslic3r/PrintConfig.cpp:1383 msgid "mm/s²" msgstr "" -#: src/libslic3r/PrintConfig.cpp:167 +#: src/libslic3r/PrintConfig.cpp:176 msgid "Bridging angle" msgstr "" -#: src/libslic3r/PrintConfig.cpp:169 +#: src/libslic3r/PrintConfig.cpp:178 msgid "" "Bridging angle override. If left to zero, the bridging angle will be " "calculated automatically. Otherwise the provided angle will be used for all " "bridges. Use 180° for zero angle." msgstr "" -#: src/libslic3r/PrintConfig.cpp:172 src/libslic3r/PrintConfig.cpp:744 -#: src/libslic3r/PrintConfig.cpp:1605 src/libslic3r/PrintConfig.cpp:1615 -#: src/libslic3r/PrintConfig.cpp:1843 src/libslic3r/PrintConfig.cpp:1997 -#: src/libslic3r/PrintConfig.cpp:2181 src/libslic3r/PrintConfig.cpp:2498 -#: src/libslic3r/PrintConfig.cpp:2607 +#: src/libslic3r/PrintConfig.cpp:181 src/libslic3r/PrintConfig.cpp:758 +#: src/libslic3r/PrintConfig.cpp:1619 src/libslic3r/PrintConfig.cpp:1629 +#: src/libslic3r/PrintConfig.cpp:1858 src/libslic3r/PrintConfig.cpp:2012 +#: src/libslic3r/PrintConfig.cpp:2197 src/libslic3r/PrintConfig.cpp:2582 +#: src/libslic3r/PrintConfig.cpp:2693 msgid "°" msgstr "" -#: src/libslic3r/PrintConfig.cpp:178 +#: src/libslic3r/PrintConfig.cpp:187 msgid "Bridges fan speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:179 +#: src/libslic3r/PrintConfig.cpp:188 msgid "This fan speed is enforced during all bridges and overhangs." msgstr "" -#: src/libslic3r/PrintConfig.cpp:180 src/libslic3r/PrintConfig.cpp:756 -#: src/libslic3r/PrintConfig.cpp:1189 src/libslic3r/PrintConfig.cpp:1252 -#: src/libslic3r/PrintConfig.cpp:1497 src/libslic3r/PrintConfig.cpp:2295 -#: src/libslic3r/PrintConfig.cpp:2537 +#: src/libslic3r/PrintConfig.cpp:189 src/libslic3r/PrintConfig.cpp:770 +#: src/libslic3r/PrintConfig.cpp:1203 src/libslic3r/PrintConfig.cpp:1266 +#: src/libslic3r/PrintConfig.cpp:1511 src/libslic3r/PrintConfig.cpp:2366 +#: src/libslic3r/PrintConfig.cpp:2623 msgid "%" msgstr "" -#: src/libslic3r/PrintConfig.cpp:187 +#: src/libslic3r/PrintConfig.cpp:196 msgid "Bridge flow ratio" msgstr "" -#: src/libslic3r/PrintConfig.cpp:189 +#: src/libslic3r/PrintConfig.cpp:198 msgid "" "This factor affects the amount of plastic for bridging. You can decrease it " "slightly to pull the extrudates and prevent sagging, although default " @@ -4975,83 +5479,83 @@ msgid "" "before tweaking this." msgstr "" -#: src/libslic3r/PrintConfig.cpp:199 +#: src/libslic3r/PrintConfig.cpp:208 msgid "Bridges" msgstr "" -#: src/libslic3r/PrintConfig.cpp:201 +#: src/libslic3r/PrintConfig.cpp:210 msgid "Speed for printing bridges." msgstr "" -#: src/libslic3r/PrintConfig.cpp:202 src/libslic3r/PrintConfig.cpp:578 -#: src/libslic3r/PrintConfig.cpp:586 src/libslic3r/PrintConfig.cpp:595 -#: src/libslic3r/PrintConfig.cpp:603 src/libslic3r/PrintConfig.cpp:630 -#: src/libslic3r/PrintConfig.cpp:649 src/libslic3r/PrintConfig.cpp:885 -#: src/libslic3r/PrintConfig.cpp:1012 src/libslic3r/PrintConfig.cpp:1098 -#: src/libslic3r/PrintConfig.cpp:1134 src/libslic3r/PrintConfig.cpp:1147 -#: src/libslic3r/PrintConfig.cpp:1158 src/libslic3r/PrintConfig.cpp:1211 -#: src/libslic3r/PrintConfig.cpp:1270 src/libslic3r/PrintConfig.cpp:1398 -#: src/libslic3r/PrintConfig.cpp:1572 src/libslic3r/PrintConfig.cpp:1581 -#: src/libslic3r/PrintConfig.cpp:1976 src/libslic3r/PrintConfig.cpp:2088 +#: src/libslic3r/PrintConfig.cpp:211 src/libslic3r/PrintConfig.cpp:592 +#: src/libslic3r/PrintConfig.cpp:600 src/libslic3r/PrintConfig.cpp:609 +#: src/libslic3r/PrintConfig.cpp:617 src/libslic3r/PrintConfig.cpp:644 +#: src/libslic3r/PrintConfig.cpp:663 src/libslic3r/PrintConfig.cpp:899 +#: src/libslic3r/PrintConfig.cpp:1026 src/libslic3r/PrintConfig.cpp:1112 +#: src/libslic3r/PrintConfig.cpp:1148 src/libslic3r/PrintConfig.cpp:1161 +#: src/libslic3r/PrintConfig.cpp:1172 src/libslic3r/PrintConfig.cpp:1225 +#: src/libslic3r/PrintConfig.cpp:1284 src/libslic3r/PrintConfig.cpp:1412 +#: src/libslic3r/PrintConfig.cpp:1586 src/libslic3r/PrintConfig.cpp:1595 +#: src/libslic3r/PrintConfig.cpp:1991 src/libslic3r/PrintConfig.cpp:2104 msgid "mm/s" msgstr "" -#: src/libslic3r/PrintConfig.cpp:209 +#: src/libslic3r/PrintConfig.cpp:218 msgid "Brim width" msgstr "" -#: src/libslic3r/PrintConfig.cpp:210 +#: src/libslic3r/PrintConfig.cpp:219 msgid "" "Horizontal width of the brim that will be printed around each object on the " "first layer." msgstr "" -#: src/libslic3r/PrintConfig.cpp:217 +#: src/libslic3r/PrintConfig.cpp:226 msgid "Clip multi-part objects" msgstr "" -#: src/libslic3r/PrintConfig.cpp:218 +#: src/libslic3r/PrintConfig.cpp:227 msgid "" "When printing multi-material objects, this settings will make Slic3r to clip " "the overlapping object parts one by the other (2nd part will be clipped by " "the 1st, 3rd part will be clipped by the 1st and 2nd etc)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:225 +#: src/libslic3r/PrintConfig.cpp:234 msgid "Colorprint height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:226 +#: src/libslic3r/PrintConfig.cpp:235 msgid "Heights at which a filament change is to occur." msgstr "" -#: src/libslic3r/PrintConfig.cpp:236 +#: src/libslic3r/PrintConfig.cpp:245 msgid "Compatible printers condition" msgstr "" -#: src/libslic3r/PrintConfig.cpp:237 +#: src/libslic3r/PrintConfig.cpp:246 msgid "" "A boolean expression using the configuration values of an active printer " "profile. If this expression evaluates to true, this profile is considered " "compatible with the active printer profile." msgstr "" -#: src/libslic3r/PrintConfig.cpp:251 +#: src/libslic3r/PrintConfig.cpp:260 msgid "Compatible print profiles condition" msgstr "" -#: src/libslic3r/PrintConfig.cpp:252 +#: src/libslic3r/PrintConfig.cpp:261 msgid "" "A boolean expression using the configuration values of an active print " "profile. If this expression evaluates to true, this profile is considered " "compatible with the active print profile." msgstr "" -#: src/libslic3r/PrintConfig.cpp:269 +#: src/libslic3r/PrintConfig.cpp:278 msgid "Complete individual objects" msgstr "" -#: src/libslic3r/PrintConfig.cpp:270 +#: src/libslic3r/PrintConfig.cpp:279 msgid "" "When printing multiple objects or copies, this feature will complete each " "object before moving onto next one (and starting it from its bottom layer). " @@ -5059,177 +5563,178 @@ msgid "" "warn and prevent you from extruder collisions, but beware." msgstr "" -#: src/libslic3r/PrintConfig.cpp:278 +#: src/libslic3r/PrintConfig.cpp:287 msgid "Enable auto cooling" msgstr "" -#: src/libslic3r/PrintConfig.cpp:279 +#: src/libslic3r/PrintConfig.cpp:288 msgid "" "This flag enables the automatic cooling logic that adjusts print speed and " "fan speed according to layer printing time." msgstr "" -#: src/libslic3r/PrintConfig.cpp:284 +#: src/libslic3r/PrintConfig.cpp:293 msgid "Cooling tube position" msgstr "" -#: src/libslic3r/PrintConfig.cpp:285 +#: src/libslic3r/PrintConfig.cpp:294 msgid "Distance of the center-point of the cooling tube from the extruder tip." msgstr "" -#: src/libslic3r/PrintConfig.cpp:292 +#: src/libslic3r/PrintConfig.cpp:301 msgid "Cooling tube length" msgstr "" -#: src/libslic3r/PrintConfig.cpp:293 +#: src/libslic3r/PrintConfig.cpp:302 msgid "Length of the cooling tube to limit space for cooling moves inside it." msgstr "" -#: src/libslic3r/PrintConfig.cpp:301 +#: src/libslic3r/PrintConfig.cpp:310 msgid "" "This is the acceleration your printer will be reset to after the role-" "specific acceleration values are used (perimeter/infill). Set zero to " "prevent resetting acceleration at all." msgstr "" -#: src/libslic3r/PrintConfig.cpp:310 +#: src/libslic3r/PrintConfig.cpp:319 msgid "Default filament profile" msgstr "" -#: src/libslic3r/PrintConfig.cpp:311 +#: src/libslic3r/PrintConfig.cpp:320 msgid "" "Default filament profile associated with the current printer profile. On " "selection of the current printer profile, this filament profile will be " "activated." msgstr "" -#: src/libslic3r/PrintConfig.cpp:317 +#: src/libslic3r/PrintConfig.cpp:326 msgid "Default print profile" msgstr "" -#: src/libslic3r/PrintConfig.cpp:318 src/libslic3r/PrintConfig.cpp:2376 -#: src/libslic3r/PrintConfig.cpp:2387 +#: src/libslic3r/PrintConfig.cpp:327 src/libslic3r/PrintConfig.cpp:2447 +#: src/libslic3r/PrintConfig.cpp:2458 msgid "" "Default print profile associated with the current printer profile. On " "selection of the current printer profile, this print profile will be " "activated." msgstr "" -#: src/libslic3r/PrintConfig.cpp:324 +#: src/libslic3r/PrintConfig.cpp:333 msgid "Disable fan for the first" msgstr "" -#: src/libslic3r/PrintConfig.cpp:325 +#: src/libslic3r/PrintConfig.cpp:334 msgid "" "You can set this to a positive value to disable fan at all during the first " "layers, so that it does not make adhesion worse." msgstr "" -#: src/libslic3r/PrintConfig.cpp:327 src/libslic3r/PrintConfig.cpp:957 -#: src/libslic3r/PrintConfig.cpp:1470 src/libslic3r/PrintConfig.cpp:1655 -#: src/libslic3r/PrintConfig.cpp:1716 src/libslic3r/PrintConfig.cpp:1879 -#: src/libslic3r/PrintConfig.cpp:1924 +#: src/libslic3r/PrintConfig.cpp:336 src/libslic3r/PrintConfig.cpp:971 +#: src/libslic3r/PrintConfig.cpp:1484 src/libslic3r/PrintConfig.cpp:1669 +#: src/libslic3r/PrintConfig.cpp:1730 src/libslic3r/PrintConfig.cpp:1894 +#: src/libslic3r/PrintConfig.cpp:1939 msgid "layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:334 +#: src/libslic3r/PrintConfig.cpp:343 msgid "Don't support bridges" msgstr "" -#: src/libslic3r/PrintConfig.cpp:336 +#: src/libslic3r/PrintConfig.cpp:345 msgid "" "Experimental option for preventing support material from being generated " "under bridged areas." msgstr "" -#: src/libslic3r/PrintConfig.cpp:342 +#: src/libslic3r/PrintConfig.cpp:351 msgid "Distance between copies" msgstr "" -#: src/libslic3r/PrintConfig.cpp:343 +#: src/libslic3r/PrintConfig.cpp:352 msgid "Distance used for the auto-arrange feature of the plater." msgstr "" -#: src/libslic3r/PrintConfig.cpp:350 +#: src/libslic3r/PrintConfig.cpp:359 msgid "Elephant foot compensation" msgstr "" -#: src/libslic3r/PrintConfig.cpp:352 +#: src/libslic3r/PrintConfig.cpp:361 msgid "" "The first layer will be shrunk in the XY plane by the configured value to " "compensate for the 1st layer squish aka an Elephant Foot effect." msgstr "" -#: src/libslic3r/PrintConfig.cpp:361 +#: src/libslic3r/PrintConfig.cpp:370 msgid "" "This end procedure is inserted at the end of the output file. Note that you " "can use placeholder variables for all Slic3r settings." msgstr "" -#: src/libslic3r/PrintConfig.cpp:371 +#: src/libslic3r/PrintConfig.cpp:380 msgid "" "This end procedure is inserted at the end of the output file, before the " -"printer end gcode. Note that you can use placeholder variables for all " +"printer end gcode (and before any toolchange from this filament in case of " +"multimaterial printers). Note that you can use placeholder variables for all " "Slic3r settings. If you have multiple extruders, the gcode is processed in " "extruder order." msgstr "" -#: src/libslic3r/PrintConfig.cpp:381 +#: src/libslic3r/PrintConfig.cpp:391 msgid "Ensure vertical shell thickness" msgstr "" -#: src/libslic3r/PrintConfig.cpp:383 +#: src/libslic3r/PrintConfig.cpp:393 msgid "" "Add solid infill near sloping surfaces to guarantee the vertical shell " "thickness (top+bottom solid layers)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:389 +#: src/libslic3r/PrintConfig.cpp:399 msgid "Top fill pattern" msgstr "" -#: src/libslic3r/PrintConfig.cpp:391 +#: src/libslic3r/PrintConfig.cpp:401 msgid "" "Fill pattern for top infill. This only affects the top visible layer, and " "not its adjacent solid shells." msgstr "" -#: src/libslic3r/PrintConfig.cpp:399 src/libslic3r/PrintConfig.cpp:807 -#: src/libslic3r/PrintConfig.cpp:1957 +#: src/libslic3r/PrintConfig.cpp:409 src/libslic3r/PrintConfig.cpp:821 +#: src/libslic3r/PrintConfig.cpp:1972 msgid "Rectilinear" msgstr "" -#: src/libslic3r/PrintConfig.cpp:400 src/libslic3r/PrintConfig.cpp:813 +#: src/libslic3r/PrintConfig.cpp:410 src/libslic3r/PrintConfig.cpp:827 msgid "Concentric" msgstr "" -#: src/libslic3r/PrintConfig.cpp:401 src/libslic3r/PrintConfig.cpp:817 +#: src/libslic3r/PrintConfig.cpp:411 src/libslic3r/PrintConfig.cpp:831 msgid "Hilbert Curve" msgstr "" -#: src/libslic3r/PrintConfig.cpp:402 src/libslic3r/PrintConfig.cpp:818 +#: src/libslic3r/PrintConfig.cpp:412 src/libslic3r/PrintConfig.cpp:832 msgid "Archimedean Chords" msgstr "" -#: src/libslic3r/PrintConfig.cpp:403 src/libslic3r/PrintConfig.cpp:819 +#: src/libslic3r/PrintConfig.cpp:413 src/libslic3r/PrintConfig.cpp:833 msgid "Octagram Spiral" msgstr "" -#: src/libslic3r/PrintConfig.cpp:410 +#: src/libslic3r/PrintConfig.cpp:419 msgid "Bottom fill pattern" msgstr "" -#: src/libslic3r/PrintConfig.cpp:411 +#: src/libslic3r/PrintConfig.cpp:421 msgid "" "Fill pattern for bottom infill. This only affects the bottom external " "visible layer, and not its adjacent solid shells." msgstr "" -#: src/libslic3r/PrintConfig.cpp:416 src/libslic3r/PrintConfig.cpp:426 +#: src/libslic3r/PrintConfig.cpp:430 src/libslic3r/PrintConfig.cpp:440 msgid "External perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:418 +#: src/libslic3r/PrintConfig.cpp:432 msgid "" "Set this to a non-zero value to set a manual extrusion width for external " "perimeters. If left zero, default extrusion width will be used if set, " @@ -5237,43 +5742,43 @@ msgid "" "(for example 200%), it will be computed over layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:421 src/libslic3r/PrintConfig.cpp:529 -#: src/libslic3r/PrintConfig.cpp:846 src/libslic3r/PrintConfig.cpp:858 -#: src/libslic3r/PrintConfig.cpp:978 src/libslic3r/PrintConfig.cpp:1003 -#: src/libslic3r/PrintConfig.cpp:1389 src/libslic3r/PrintConfig.cpp:1727 -#: src/libslic3r/PrintConfig.cpp:1832 src/libslic3r/PrintConfig.cpp:1900 -#: src/libslic3r/PrintConfig.cpp:2058 +#: src/libslic3r/PrintConfig.cpp:435 src/libslic3r/PrintConfig.cpp:543 +#: src/libslic3r/PrintConfig.cpp:860 src/libslic3r/PrintConfig.cpp:872 +#: src/libslic3r/PrintConfig.cpp:992 src/libslic3r/PrintConfig.cpp:1017 +#: src/libslic3r/PrintConfig.cpp:1403 src/libslic3r/PrintConfig.cpp:1741 +#: src/libslic3r/PrintConfig.cpp:1847 src/libslic3r/PrintConfig.cpp:1915 +#: src/libslic3r/PrintConfig.cpp:2074 msgid "mm or %" msgstr "" -#: src/libslic3r/PrintConfig.cpp:428 +#: src/libslic3r/PrintConfig.cpp:442 msgid "" "This separate setting will affect the speed of external perimeters (the " "visible ones). If expressed as percentage (for example: 80%) it will be " "calculated on the perimeters speed setting above. Set to zero for auto." msgstr "" -#: src/libslic3r/PrintConfig.cpp:431 src/libslic3r/PrintConfig.cpp:867 -#: src/libslic3r/PrintConfig.cpp:1686 src/libslic3r/PrintConfig.cpp:1737 -#: src/libslic3r/PrintConfig.cpp:1943 src/libslic3r/PrintConfig.cpp:2070 +#: src/libslic3r/PrintConfig.cpp:445 src/libslic3r/PrintConfig.cpp:881 +#: src/libslic3r/PrintConfig.cpp:1700 src/libslic3r/PrintConfig.cpp:1751 +#: src/libslic3r/PrintConfig.cpp:1958 src/libslic3r/PrintConfig.cpp:2086 msgid "mm/s or %" msgstr "" -#: src/libslic3r/PrintConfig.cpp:438 +#: src/libslic3r/PrintConfig.cpp:452 msgid "External perimeters first" msgstr "" -#: src/libslic3r/PrintConfig.cpp:440 +#: src/libslic3r/PrintConfig.cpp:454 msgid "" "Print contour perimeters from the outermost one to the innermost one instead " "of the default inverse order." msgstr "" -#: src/libslic3r/PrintConfig.cpp:446 +#: src/libslic3r/PrintConfig.cpp:460 msgid "Extra perimeters if needed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:448 +#: src/libslic3r/PrintConfig.cpp:462 #, possible-c-format msgid "" "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r " @@ -5281,14 +5786,14 @@ msgid "" "is supported." msgstr "" -#: src/libslic3r/PrintConfig.cpp:458 +#: src/libslic3r/PrintConfig.cpp:472 msgid "" "The extruder to use (unless more specific extruder settings are specified). " "This value overrides perimeter and infill extruders, but not the support " "extruders." msgstr "" -#: src/libslic3r/PrintConfig.cpp:470 +#: src/libslic3r/PrintConfig.cpp:484 msgid "" "Set this to the vertical distance between your nozzle tip and (usually) the " "X carriage rods. In other words, this is the height of the clearance " @@ -5296,30 +5801,30 @@ msgid "" "extruder can peek before colliding with other printed objects." msgstr "" -#: src/libslic3r/PrintConfig.cpp:480 +#: src/libslic3r/PrintConfig.cpp:494 msgid "Radius" msgstr "" -#: src/libslic3r/PrintConfig.cpp:481 +#: src/libslic3r/PrintConfig.cpp:495 msgid "" "Set this to the clearance radius around your extruder. If the extruder is " "not centered, choose the largest value for safety. This setting is used to " "check for collisions and to display the graphical preview in the plater." msgstr "" -#: src/libslic3r/PrintConfig.cpp:491 +#: src/libslic3r/PrintConfig.cpp:505 msgid "Extruder Color" msgstr "" -#: src/libslic3r/PrintConfig.cpp:492 src/libslic3r/PrintConfig.cpp:552 +#: src/libslic3r/PrintConfig.cpp:506 src/libslic3r/PrintConfig.cpp:566 msgid "This is only used in the Slic3r interface as a visual help." msgstr "" -#: src/libslic3r/PrintConfig.cpp:498 +#: src/libslic3r/PrintConfig.cpp:512 msgid "Extruder offset" msgstr "" -#: src/libslic3r/PrintConfig.cpp:499 +#: src/libslic3r/PrintConfig.cpp:513 msgid "" "If your firmware doesn't handle the extruder displacement you need the G-" "code to take it into account. This option lets you specify the displacement " @@ -5327,21 +5832,21 @@ msgid "" "coordinates (they will be subtracted from the XY coordinate)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:508 +#: src/libslic3r/PrintConfig.cpp:522 msgid "Extrusion axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:509 +#: src/libslic3r/PrintConfig.cpp:523 msgid "" "Use this option to set the axis letter associated to your printer's extruder " "(usually E but some printers use A)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:514 +#: src/libslic3r/PrintConfig.cpp:528 msgid "Extrusion multiplier" msgstr "" -#: src/libslic3r/PrintConfig.cpp:515 +#: src/libslic3r/PrintConfig.cpp:529 msgid "" "This factor changes the amount of flow proportionally. You may need to tweak " "this setting to get nice surface finish and correct single wall widths. " @@ -5349,11 +5854,11 @@ msgid "" "more, check filament diameter and your firmware E steps." msgstr "" -#: src/libslic3r/PrintConfig.cpp:523 +#: src/libslic3r/PrintConfig.cpp:537 msgid "Default extrusion width" msgstr "" -#: src/libslic3r/PrintConfig.cpp:525 +#: src/libslic3r/PrintConfig.cpp:539 msgid "" "Set this to a non-zero value to allow a manual extrusion width. If left to " "zero, Slic3r derives extrusion widths from the nozzle diameter (see the " @@ -5362,123 +5867,123 @@ msgid "" "height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:534 +#: src/libslic3r/PrintConfig.cpp:548 msgid "Keep fan always on" msgstr "" -#: src/libslic3r/PrintConfig.cpp:535 +#: src/libslic3r/PrintConfig.cpp:549 msgid "" "If this is enabled, fan will never be disabled and will be kept running at " "least at its minimum speed. Useful for PLA, harmful for ABS." msgstr "" -#: src/libslic3r/PrintConfig.cpp:540 +#: src/libslic3r/PrintConfig.cpp:554 msgid "Enable fan if layer print time is below" msgstr "" -#: src/libslic3r/PrintConfig.cpp:541 +#: src/libslic3r/PrintConfig.cpp:555 msgid "" "If layer print time is estimated below this number of seconds, fan will be " "enabled and its speed will be calculated by interpolating the minimum and " "maximum speeds." msgstr "" -#: src/libslic3r/PrintConfig.cpp:543 src/libslic3r/PrintConfig.cpp:1673 +#: src/libslic3r/PrintConfig.cpp:557 src/libslic3r/PrintConfig.cpp:1687 msgid "approximate seconds" msgstr "" -#: src/libslic3r/PrintConfig.cpp:551 +#: src/libslic3r/PrintConfig.cpp:565 msgid "Color" msgstr "" -#: src/libslic3r/PrintConfig.cpp:557 +#: src/libslic3r/PrintConfig.cpp:571 msgid "Filament notes" msgstr "" -#: src/libslic3r/PrintConfig.cpp:558 +#: src/libslic3r/PrintConfig.cpp:572 msgid "You can put your notes regarding the filament here." msgstr "" -#: src/libslic3r/PrintConfig.cpp:566 src/libslic3r/PrintConfig.cpp:1217 +#: src/libslic3r/PrintConfig.cpp:580 src/libslic3r/PrintConfig.cpp:1231 msgid "Max volumetric speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:567 +#: src/libslic3r/PrintConfig.cpp:581 msgid "" "Maximum volumetric speed allowed for this filament. Limits the maximum " "volumetric speed of a print to the minimum of print and filament volumetric " "speed. Set to zero for no limit." msgstr "" -#: src/libslic3r/PrintConfig.cpp:576 +#: src/libslic3r/PrintConfig.cpp:590 msgid "Loading speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:577 +#: src/libslic3r/PrintConfig.cpp:591 msgid "Speed used for loading the filament on the wipe tower." msgstr "" -#: src/libslic3r/PrintConfig.cpp:584 +#: src/libslic3r/PrintConfig.cpp:598 msgid "Loading speed at the start" msgstr "" -#: src/libslic3r/PrintConfig.cpp:585 +#: src/libslic3r/PrintConfig.cpp:599 msgid "Speed used at the very beginning of loading phase." msgstr "" -#: src/libslic3r/PrintConfig.cpp:592 +#: src/libslic3r/PrintConfig.cpp:606 msgid "Unloading speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:593 +#: src/libslic3r/PrintConfig.cpp:607 msgid "" "Speed used for unloading the filament on the wipe tower (does not affect " "initial part of unloading just after ramming)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:601 +#: src/libslic3r/PrintConfig.cpp:615 msgid "Unloading speed at the start" msgstr "" -#: src/libslic3r/PrintConfig.cpp:602 +#: src/libslic3r/PrintConfig.cpp:616 msgid "" "Speed used for unloading the tip of the filament immediately after ramming." msgstr "" -#: src/libslic3r/PrintConfig.cpp:609 +#: src/libslic3r/PrintConfig.cpp:623 msgid "Delay after unloading" msgstr "" -#: src/libslic3r/PrintConfig.cpp:610 +#: src/libslic3r/PrintConfig.cpp:624 msgid "" "Time to wait after the filament is unloaded. May help to get reliable " "toolchanges with flexible materials that may need more time to shrink to " "original dimensions." msgstr "" -#: src/libslic3r/PrintConfig.cpp:619 +#: src/libslic3r/PrintConfig.cpp:633 msgid "Number of cooling moves" msgstr "" -#: src/libslic3r/PrintConfig.cpp:620 +#: src/libslic3r/PrintConfig.cpp:634 msgid "" "Filament is cooled by being moved back and forth in the cooling tubes. " "Specify desired number of these moves." msgstr "" -#: src/libslic3r/PrintConfig.cpp:628 +#: src/libslic3r/PrintConfig.cpp:642 msgid "Speed of the first cooling move" msgstr "" -#: src/libslic3r/PrintConfig.cpp:629 +#: src/libslic3r/PrintConfig.cpp:643 msgid "Cooling moves are gradually accelerating beginning at this speed." msgstr "" -#: src/libslic3r/PrintConfig.cpp:636 +#: src/libslic3r/PrintConfig.cpp:650 msgid "Minimal purge on wipe tower" msgstr "" -#: src/libslic3r/PrintConfig.cpp:637 +#: src/libslic3r/PrintConfig.cpp:651 msgid "" "After a tool change, the exact position of the newly loaded filament inside " "the nozzle may not be known, and the filament pressure is likely not yet " @@ -5487,62 +5992,62 @@ msgid "" "to produce successive infill or sacrificial object extrusions reliably." msgstr "" -#: src/libslic3r/PrintConfig.cpp:641 +#: src/libslic3r/PrintConfig.cpp:655 msgid "mm³" msgstr "" -#: src/libslic3r/PrintConfig.cpp:647 +#: src/libslic3r/PrintConfig.cpp:661 msgid "Speed of the last cooling move" msgstr "" -#: src/libslic3r/PrintConfig.cpp:648 +#: src/libslic3r/PrintConfig.cpp:662 msgid "Cooling moves are gradually accelerating towards this speed." msgstr "" -#: src/libslic3r/PrintConfig.cpp:655 +#: src/libslic3r/PrintConfig.cpp:669 msgid "Filament load time" msgstr "" -#: src/libslic3r/PrintConfig.cpp:656 +#: src/libslic3r/PrintConfig.cpp:670 msgid "" "Time for the printer firmware (or the Multi Material Unit 2.0) to load a new " "filament during a tool change (when executing the T code). This time is " "added to the total print time by the G-code time estimator." msgstr "" -#: src/libslic3r/PrintConfig.cpp:663 +#: src/libslic3r/PrintConfig.cpp:677 msgid "Ramming parameters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:664 +#: src/libslic3r/PrintConfig.cpp:678 msgid "" "This string is edited by RammingDialog and contains ramming specific " "parameters." msgstr "" -#: src/libslic3r/PrintConfig.cpp:670 +#: src/libslic3r/PrintConfig.cpp:684 msgid "Filament unload time" msgstr "" -#: src/libslic3r/PrintConfig.cpp:671 +#: src/libslic3r/PrintConfig.cpp:685 msgid "" "Time for the printer firmware (or the Multi Material Unit 2.0) to unload a " "filament during a tool change (when executing the T code). This time is " "added to the total print time by the G-code time estimator." msgstr "" -#: src/libslic3r/PrintConfig.cpp:679 +#: src/libslic3r/PrintConfig.cpp:693 msgid "" "Enter your filament diameter here. Good precision is required, so use a " "caliper and do multiple measurements along the filament, then compute the " "average." msgstr "" -#: src/libslic3r/PrintConfig.cpp:686 +#: src/libslic3r/PrintConfig.cpp:700 msgid "Density" msgstr "" -#: src/libslic3r/PrintConfig.cpp:687 +#: src/libslic3r/PrintConfig.cpp:701 msgid "" "Enter your filament density here. This is only for statistical information. " "A decent way is to weigh a known length of filament and compute the ratio of " @@ -5550,113 +6055,113 @@ msgid "" "displacement." msgstr "" -#: src/libslic3r/PrintConfig.cpp:690 +#: src/libslic3r/PrintConfig.cpp:704 msgid "g/cm³" msgstr "" -#: src/libslic3r/PrintConfig.cpp:695 +#: src/libslic3r/PrintConfig.cpp:709 msgid "Filament type" msgstr "" -#: src/libslic3r/PrintConfig.cpp:696 +#: src/libslic3r/PrintConfig.cpp:710 msgid "The filament material type for use in custom G-codes." msgstr "" -#: src/libslic3r/PrintConfig.cpp:722 +#: src/libslic3r/PrintConfig.cpp:736 msgid "Soluble material" msgstr "" -#: src/libslic3r/PrintConfig.cpp:723 +#: src/libslic3r/PrintConfig.cpp:737 msgid "Soluble material is most likely used for a soluble support." msgstr "" -#: src/libslic3r/PrintConfig.cpp:729 +#: src/libslic3r/PrintConfig.cpp:743 msgid "" "Enter your filament cost per kg here. This is only for statistical " "information." msgstr "" -#: src/libslic3r/PrintConfig.cpp:730 +#: src/libslic3r/PrintConfig.cpp:744 msgid "money/kg" msgstr "" -#: src/libslic3r/PrintConfig.cpp:739 +#: src/libslic3r/PrintConfig.cpp:753 msgid "Fill angle" msgstr "" -#: src/libslic3r/PrintConfig.cpp:741 +#: src/libslic3r/PrintConfig.cpp:755 msgid "" "Default base angle for infill orientation. Cross-hatching will be applied to " "this. Bridges will be infilled using the best direction Slic3r can detect, " "so this setting does not affect them." msgstr "" -#: src/libslic3r/PrintConfig.cpp:753 +#: src/libslic3r/PrintConfig.cpp:767 msgid "Fill density" msgstr "" -#: src/libslic3r/PrintConfig.cpp:755 +#: src/libslic3r/PrintConfig.cpp:769 msgid "Density of internal infill, expressed in the range 0% - 100%." msgstr "" -#: src/libslic3r/PrintConfig.cpp:790 +#: src/libslic3r/PrintConfig.cpp:804 msgid "Fill pattern" msgstr "" -#: src/libslic3r/PrintConfig.cpp:792 +#: src/libslic3r/PrintConfig.cpp:806 msgid "Fill pattern for general low-density infill." msgstr "" -#: src/libslic3r/PrintConfig.cpp:808 +#: src/libslic3r/PrintConfig.cpp:822 msgid "Grid" msgstr "" -#: src/libslic3r/PrintConfig.cpp:809 +#: src/libslic3r/PrintConfig.cpp:823 msgid "Triangles" msgstr "" -#: src/libslic3r/PrintConfig.cpp:810 +#: src/libslic3r/PrintConfig.cpp:824 msgid "Stars" msgstr "" -#: src/libslic3r/PrintConfig.cpp:811 +#: src/libslic3r/PrintConfig.cpp:825 msgid "Cubic" msgstr "" -#: src/libslic3r/PrintConfig.cpp:812 +#: src/libslic3r/PrintConfig.cpp:826 msgid "Line" msgstr "" -#: src/libslic3r/PrintConfig.cpp:814 src/libslic3r/PrintConfig.cpp:1959 +#: src/libslic3r/PrintConfig.cpp:828 src/libslic3r/PrintConfig.cpp:1974 msgid "Honeycomb" msgstr "" -#: src/libslic3r/PrintConfig.cpp:815 +#: src/libslic3r/PrintConfig.cpp:829 msgid "3D Honeycomb" msgstr "" -#: src/libslic3r/PrintConfig.cpp:816 +#: src/libslic3r/PrintConfig.cpp:830 msgid "Gyroid" msgstr "" -#: src/libslic3r/PrintConfig.cpp:823 src/libslic3r/PrintConfig.cpp:832 -#: src/libslic3r/PrintConfig.cpp:840 src/libslic3r/PrintConfig.cpp:873 +#: src/libslic3r/PrintConfig.cpp:837 src/libslic3r/PrintConfig.cpp:846 +#: src/libslic3r/PrintConfig.cpp:854 src/libslic3r/PrintConfig.cpp:887 msgid "First layer" msgstr "" -#: src/libslic3r/PrintConfig.cpp:824 +#: src/libslic3r/PrintConfig.cpp:838 msgid "" "This is the acceleration your printer will use for first layer. Set zero to " "disable acceleration control for first layer." msgstr "" -#: src/libslic3r/PrintConfig.cpp:833 +#: src/libslic3r/PrintConfig.cpp:847 msgid "" "Heated build plate temperature for the first layer. Set this to zero to " "disable bed temperature control commands in the output." msgstr "" -#: src/libslic3r/PrintConfig.cpp:842 +#: src/libslic3r/PrintConfig.cpp:856 msgid "" "Set this to a non-zero value to set a manual extrusion width for first " "layer. You can use this to force fatter extrudates for better adhesion. If " @@ -5664,7 +6169,7 @@ msgid "" "layer height. If set to zero, it will use the default extrusion width." msgstr "" -#: src/libslic3r/PrintConfig.cpp:854 +#: src/libslic3r/PrintConfig.cpp:868 msgid "" "When printing with very low layer heights, you might still want to print a " "thicker bottom layer to improve adhesion and tolerance for non perfect build " @@ -5672,47 +6177,47 @@ msgid "" "example: 150%) over the default layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:863 +#: src/libslic3r/PrintConfig.cpp:877 msgid "First layer speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:864 +#: src/libslic3r/PrintConfig.cpp:878 msgid "" "If expressed as absolute value in mm/s, this speed will be applied to all " "the print moves of the first layer, regardless of their type. If expressed " "as a percentage (for example: 40%) it will scale the default speeds." msgstr "" -#: src/libslic3r/PrintConfig.cpp:874 +#: src/libslic3r/PrintConfig.cpp:888 msgid "" "Extruder temperature for first layer. If you want to control temperature " "manually during print, set this to zero to disable temperature control " "commands in the output file." msgstr "" -#: src/libslic3r/PrintConfig.cpp:883 +#: src/libslic3r/PrintConfig.cpp:897 msgid "" "Speed for filling small gaps using short zigzag moves. Keep this reasonably " "low to avoid too much shaking and resonance issues. Set zero to disable gaps " "filling." msgstr "" -#: src/libslic3r/PrintConfig.cpp:891 +#: src/libslic3r/PrintConfig.cpp:905 msgid "Verbose G-code" msgstr "" -#: src/libslic3r/PrintConfig.cpp:892 +#: src/libslic3r/PrintConfig.cpp:906 msgid "" "Enable this to get a commented G-code file, with each line explained by a " "descriptive text. If you print from SD card, the additional weight of the " "file could make your firmware slow down." msgstr "" -#: src/libslic3r/PrintConfig.cpp:899 +#: src/libslic3r/PrintConfig.cpp:913 msgid "G-code flavor" msgstr "" -#: src/libslic3r/PrintConfig.cpp:900 +#: src/libslic3r/PrintConfig.cpp:914 msgid "" "Some G/M-code commands, including temperature control and others, are not " "universal. Set this option to your printer's firmware to get a compatible " @@ -5720,15 +6225,15 @@ msgid "" "extrusion value at all." msgstr "" -#: src/libslic3r/PrintConfig.cpp:923 +#: src/libslic3r/PrintConfig.cpp:937 msgid "No extrusion" msgstr "" -#: src/libslic3r/PrintConfig.cpp:928 +#: src/libslic3r/PrintConfig.cpp:942 msgid "Label objects" msgstr "" -#: src/libslic3r/PrintConfig.cpp:929 +#: src/libslic3r/PrintConfig.cpp:943 msgid "" "Enable this to add comments into the G-Code labeling print moves with what " "object they belong to, which is useful for the Octoprint CancelObject " @@ -5736,46 +6241,46 @@ msgid "" "setup and Wipe into Object / Wipe into Infill." msgstr "" -#: src/libslic3r/PrintConfig.cpp:936 +#: src/libslic3r/PrintConfig.cpp:950 msgid "High extruder current on filament swap" msgstr "" -#: src/libslic3r/PrintConfig.cpp:937 +#: src/libslic3r/PrintConfig.cpp:951 msgid "" "It may be beneficial to increase the extruder motor current during the " "filament exchange sequence to allow for rapid ramming feed rates and to " "overcome resistance when loading a filament with an ugly shaped tip." msgstr "" -#: src/libslic3r/PrintConfig.cpp:945 +#: src/libslic3r/PrintConfig.cpp:959 msgid "" "This is the acceleration your printer will use for infill. Set zero to " "disable acceleration control for infill." msgstr "" -#: src/libslic3r/PrintConfig.cpp:953 +#: src/libslic3r/PrintConfig.cpp:967 msgid "Combine infill every" msgstr "" -#: src/libslic3r/PrintConfig.cpp:955 +#: src/libslic3r/PrintConfig.cpp:969 msgid "" "This feature allows to combine infill and speed up your print by extruding " "thicker infill layers while preserving thin perimeters, thus accuracy." msgstr "" -#: src/libslic3r/PrintConfig.cpp:958 +#: src/libslic3r/PrintConfig.cpp:972 msgid "Combine infill every n layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:964 +#: src/libslic3r/PrintConfig.cpp:978 msgid "Infill extruder" msgstr "" -#: src/libslic3r/PrintConfig.cpp:966 +#: src/libslic3r/PrintConfig.cpp:980 msgid "The extruder to use when printing infill." msgstr "" -#: src/libslic3r/PrintConfig.cpp:974 +#: src/libslic3r/PrintConfig.cpp:988 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill. If " "left zero, default extrusion width will be used if set, otherwise 1.125 x " @@ -5784,32 +6289,32 @@ msgid "" "example 90%) it will be computed over layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:983 +#: src/libslic3r/PrintConfig.cpp:997 msgid "Infill before perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:984 +#: src/libslic3r/PrintConfig.cpp:998 msgid "" "This option will switch the print order of perimeters and infill, making the " "latter first." msgstr "" -#: src/libslic3r/PrintConfig.cpp:989 +#: src/libslic3r/PrintConfig.cpp:1003 msgid "Only infill where needed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:991 +#: src/libslic3r/PrintConfig.cpp:1005 msgid "" "This option will limit infill to the areas actually needed for supporting " "ceilings (it will act as internal support material). If enabled, slows down " "the G-code generation due to the multiple checks involved." msgstr "" -#: src/libslic3r/PrintConfig.cpp:998 +#: src/libslic3r/PrintConfig.cpp:1012 msgid "Infill/perimeters overlap" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1000 +#: src/libslic3r/PrintConfig.cpp:1014 msgid "" "This setting applies an additional overlap between infill and perimeters for " "better bonding. Theoretically this shouldn't be needed, but backlash might " @@ -5817,30 +6322,30 @@ msgid "" "perimeter extrusion width." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1011 +#: src/libslic3r/PrintConfig.cpp:1025 msgid "Speed for printing the internal fill. Set to zero for auto." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1019 +#: src/libslic3r/PrintConfig.cpp:1033 msgid "Inherits profile" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1020 +#: src/libslic3r/PrintConfig.cpp:1034 msgid "Name of the profile, from which this profile inherits." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1033 +#: src/libslic3r/PrintConfig.cpp:1047 msgid "Interface shells" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1034 +#: src/libslic3r/PrintConfig.cpp:1048 msgid "" "Force the generation of solid shells between adjacent materials/volumes. " "Useful for multi-extruder prints with translucent materials or manual " "soluble support material." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1043 +#: src/libslic3r/PrintConfig.cpp:1057 msgid "" "This custom code is inserted at every layer change, right after the Z move " "and before the extruder moves to the first layer point. Note that you can " @@ -5848,11 +6353,11 @@ msgid "" "[layer_z]." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1054 +#: src/libslic3r/PrintConfig.cpp:1068 msgid "Supports remaining times" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1055 +#: src/libslic3r/PrintConfig.cpp:1069 msgid "" "Emit M73 P[percent printed] R[remaining time in minutes] at 1 minute " "intervals into the G-code to let the firmware show accurate remaining time. " @@ -5860,151 +6365,151 @@ msgid "" "firmware supports M73 Qxx Sxx for the silent mode." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1063 +#: src/libslic3r/PrintConfig.cpp:1077 msgid "Supports stealth mode" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1064 +#: src/libslic3r/PrintConfig.cpp:1078 msgid "The firmware supports stealth mode" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1088 +#: src/libslic3r/PrintConfig.cpp:1102 msgid "Maximum feedrate X" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1089 +#: src/libslic3r/PrintConfig.cpp:1103 msgid "Maximum feedrate Y" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1090 +#: src/libslic3r/PrintConfig.cpp:1104 msgid "Maximum feedrate Z" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1091 +#: src/libslic3r/PrintConfig.cpp:1105 msgid "Maximum feedrate E" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1094 +#: src/libslic3r/PrintConfig.cpp:1108 msgid "Maximum feedrate of the X axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1095 +#: src/libslic3r/PrintConfig.cpp:1109 msgid "Maximum feedrate of the Y axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1096 +#: src/libslic3r/PrintConfig.cpp:1110 msgid "Maximum feedrate of the Z axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1097 +#: src/libslic3r/PrintConfig.cpp:1111 msgid "Maximum feedrate of the E axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1106 +#: src/libslic3r/PrintConfig.cpp:1120 msgid "Maximum acceleration X" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1107 +#: src/libslic3r/PrintConfig.cpp:1121 msgid "Maximum acceleration Y" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1108 +#: src/libslic3r/PrintConfig.cpp:1122 msgid "Maximum acceleration Z" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1109 +#: src/libslic3r/PrintConfig.cpp:1123 msgid "Maximum acceleration E" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1112 +#: src/libslic3r/PrintConfig.cpp:1126 msgid "Maximum acceleration of the X axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1113 +#: src/libslic3r/PrintConfig.cpp:1127 msgid "Maximum acceleration of the Y axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1114 +#: src/libslic3r/PrintConfig.cpp:1128 msgid "Maximum acceleration of the Z axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1115 +#: src/libslic3r/PrintConfig.cpp:1129 msgid "Maximum acceleration of the E axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1124 +#: src/libslic3r/PrintConfig.cpp:1138 msgid "Maximum jerk X" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1125 +#: src/libslic3r/PrintConfig.cpp:1139 msgid "Maximum jerk Y" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1126 +#: src/libslic3r/PrintConfig.cpp:1140 msgid "Maximum jerk Z" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1127 +#: src/libslic3r/PrintConfig.cpp:1141 msgid "Maximum jerk E" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1130 +#: src/libslic3r/PrintConfig.cpp:1144 msgid "Maximum jerk of the X axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1131 +#: src/libslic3r/PrintConfig.cpp:1145 msgid "Maximum jerk of the Y axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1132 +#: src/libslic3r/PrintConfig.cpp:1146 msgid "Maximum jerk of the Z axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1133 +#: src/libslic3r/PrintConfig.cpp:1147 msgid "Maximum jerk of the E axis" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1144 +#: src/libslic3r/PrintConfig.cpp:1158 msgid "Minimum feedrate when extruding" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1146 +#: src/libslic3r/PrintConfig.cpp:1160 msgid "Minimum feedrate when extruding (M205 S)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1155 +#: src/libslic3r/PrintConfig.cpp:1169 msgid "Minimum travel feedrate" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1157 +#: src/libslic3r/PrintConfig.cpp:1171 msgid "Minimum travel feedrate (M205 T)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1166 +#: src/libslic3r/PrintConfig.cpp:1180 msgid "Maximum acceleration when extruding" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1168 +#: src/libslic3r/PrintConfig.cpp:1182 msgid "Maximum acceleration when extruding (M204 S)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1177 +#: src/libslic3r/PrintConfig.cpp:1191 msgid "Maximum acceleration when retracting" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1179 +#: src/libslic3r/PrintConfig.cpp:1193 msgid "Maximum acceleration when retracting (M204 T)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1187 src/libslic3r/PrintConfig.cpp:1196 +#: src/libslic3r/PrintConfig.cpp:1201 src/libslic3r/PrintConfig.cpp:1210 msgid "Max" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1188 +#: src/libslic3r/PrintConfig.cpp:1202 msgid "This setting represents the maximum speed of your fan." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1197 +#: src/libslic3r/PrintConfig.cpp:1211 #, possible-c-format msgid "" "This is the highest printable layer height for this extruder, used to cap " @@ -6013,28 +6518,28 @@ msgid "" "adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1207 +#: src/libslic3r/PrintConfig.cpp:1221 msgid "Max print speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1208 +#: src/libslic3r/PrintConfig.cpp:1222 msgid "" "When setting other speed settings to 0 Slic3r will autocalculate the optimal " "speed in order to keep constant extruder pressure. This experimental setting " "is used to set the highest print speed you want to allow." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1218 +#: src/libslic3r/PrintConfig.cpp:1232 msgid "" "This experimental setting is used to set the maximum volumetric speed your " "extruder supports." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1227 +#: src/libslic3r/PrintConfig.cpp:1241 msgid "Max volumetric slope positive" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1228 src/libslic3r/PrintConfig.cpp:1239 +#: src/libslic3r/PrintConfig.cpp:1242 src/libslic3r/PrintConfig.cpp:1253 msgid "" "This experimental setting is used to limit the speed of change in extrusion " "rate. A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " @@ -6042,99 +6547,95 @@ msgid "" "s) to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1232 src/libslic3r/PrintConfig.cpp:1243 +#: src/libslic3r/PrintConfig.cpp:1246 src/libslic3r/PrintConfig.cpp:1257 msgid "mm³/s²" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1238 +#: src/libslic3r/PrintConfig.cpp:1252 msgid "Max volumetric slope negative" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1250 src/libslic3r/PrintConfig.cpp:1259 +#: src/libslic3r/PrintConfig.cpp:1264 src/libslic3r/PrintConfig.cpp:1273 msgid "Min" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1251 +#: src/libslic3r/PrintConfig.cpp:1265 msgid "This setting represents the minimum PWM your fan needs to work." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1260 +#: src/libslic3r/PrintConfig.cpp:1274 msgid "" "This is the lowest printable layer height for this extruder and limits the " "resolution for variable layer height. Typical values are between 0.05 mm and " "0.1 mm." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1268 +#: src/libslic3r/PrintConfig.cpp:1282 msgid "Min print speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1269 +#: src/libslic3r/PrintConfig.cpp:1283 msgid "Slic3r will not scale speed down below this speed." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1276 +#: src/libslic3r/PrintConfig.cpp:1290 msgid "Minimal filament extrusion length" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1277 +#: src/libslic3r/PrintConfig.cpp:1291 msgid "" "Generate no less than the number of skirt loops required to consume the " "specified amount of filament on the bottom layer. For multi-extruder " "machines, this minimum applies to each extruder." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1286 +#: src/libslic3r/PrintConfig.cpp:1300 msgid "Configuration notes" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1287 +#: src/libslic3r/PrintConfig.cpp:1301 msgid "" "You can put here your personal notes. This text will be added to the G-code " "header comments." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1296 -msgid "Nozzle diameter" -msgstr "" - -#: src/libslic3r/PrintConfig.cpp:1297 +#: src/libslic3r/PrintConfig.cpp:1311 msgid "" "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1302 +#: src/libslic3r/PrintConfig.cpp:1316 msgid "Host Type" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1303 +#: src/libslic3r/PrintConfig.cpp:1317 msgid "" "Slic3r can upload G-code files to a printer host. This field must contain " "the kind of the host." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1314 +#: src/libslic3r/PrintConfig.cpp:1328 msgid "Only retract when crossing perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1315 +#: src/libslic3r/PrintConfig.cpp:1329 msgid "" "Disables retraction when the travel path does not exceed the upper layer's " "perimeters (and thus any ooze will be probably invisible)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1322 +#: src/libslic3r/PrintConfig.cpp:1336 msgid "" "This option will drop the temperature of the inactive extruders to prevent " "oozing. It will enable a tall skirt automatically and move extruders outside " "such skirt when changing temperatures." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1329 +#: src/libslic3r/PrintConfig.cpp:1343 msgid "Output filename format" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1330 +#: src/libslic3r/PrintConfig.cpp:1344 msgid "" "You can use all configuration options as variables inside this template. For " "example: [layer_height], [fill_density] etc. You can also use [timestamp], " @@ -6142,31 +6643,31 @@ msgid "" "[input_filename], [input_filename_base]." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1339 +#: src/libslic3r/PrintConfig.cpp:1353 msgid "Detect bridging perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1341 +#: src/libslic3r/PrintConfig.cpp:1355 msgid "" "Experimental option to adjust flow for overhangs (bridge flow will be used), " "to apply bridge speed to them and enable fan." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1347 +#: src/libslic3r/PrintConfig.cpp:1361 msgid "Filament parking position" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1348 +#: src/libslic3r/PrintConfig.cpp:1362 msgid "" "Distance of the extruder tip from the position where the filament is parked " "when unloaded. This should match the value in printer firmware." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1356 +#: src/libslic3r/PrintConfig.cpp:1370 msgid "Extra loading distance" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1357 +#: src/libslic3r/PrintConfig.cpp:1371 msgid "" "When set to zero, the distance the filament is moved from parking position " "during load is exactly the same as it was moved back during unload. When " @@ -6174,28 +6675,28 @@ msgid "" "than unloading." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1365 src/libslic3r/PrintConfig.cpp:1383 -#: src/libslic3r/PrintConfig.cpp:1395 src/libslic3r/PrintConfig.cpp:1405 +#: src/libslic3r/PrintConfig.cpp:1379 src/libslic3r/PrintConfig.cpp:1397 +#: src/libslic3r/PrintConfig.cpp:1409 src/libslic3r/PrintConfig.cpp:1419 msgid "Perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1366 +#: src/libslic3r/PrintConfig.cpp:1380 msgid "" "This is the acceleration your printer will use for perimeters. A high value " "like 9000 usually gives good results if your hardware is up to the job. Set " "zero to disable acceleration control for perimeters." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1374 +#: src/libslic3r/PrintConfig.cpp:1388 msgid "Perimeter extruder" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1376 +#: src/libslic3r/PrintConfig.cpp:1390 msgid "" "The extruder to use when printing perimeters and brim. First extruder is 1." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1385 +#: src/libslic3r/PrintConfig.cpp:1399 msgid "" "Set this to a non-zero value to set a manual extrusion width for perimeters. " "You may want to use thinner extrudates to get more accurate surfaces. If " @@ -6204,12 +6705,12 @@ msgid "" "it will be computed over layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1397 +#: src/libslic3r/PrintConfig.cpp:1411 msgid "" "Speed for perimeters (contours, aka vertical shells). Set to zero for auto." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1407 +#: src/libslic3r/PrintConfig.cpp:1421 msgid "" "This option sets the number of perimeters to generate for each layer. Note " "that Slic3r may increase this number automatically when it detects sloping " @@ -6217,11 +6718,11 @@ msgid "" "Perimeters option is enabled." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1411 +#: src/libslic3r/PrintConfig.cpp:1425 msgid "(minimum)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1419 +#: src/libslic3r/PrintConfig.cpp:1433 msgid "" "If you want to process the output G-code through custom scripts, just list " "their absolute paths here. Separate multiple scripts with a semicolon. " @@ -6230,55 +6731,55 @@ msgid "" "environment variables." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1431 +#: src/libslic3r/PrintConfig.cpp:1445 msgid "Printer type" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1432 +#: src/libslic3r/PrintConfig.cpp:1446 msgid "Type of the printer." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1437 +#: src/libslic3r/PrintConfig.cpp:1451 msgid "Printer notes" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1438 +#: src/libslic3r/PrintConfig.cpp:1452 msgid "You can put your notes regarding the printer here." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1446 +#: src/libslic3r/PrintConfig.cpp:1460 msgid "Printer vendor" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1447 +#: src/libslic3r/PrintConfig.cpp:1461 msgid "Name of the printer vendor." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1452 +#: src/libslic3r/PrintConfig.cpp:1466 msgid "Printer variant" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1453 +#: src/libslic3r/PrintConfig.cpp:1467 msgid "" "Name of the printer variant. For example, the printer variants may be " "differentiated by a nozzle diameter." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1466 +#: src/libslic3r/PrintConfig.cpp:1480 msgid "Raft layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1468 +#: src/libslic3r/PrintConfig.cpp:1482 msgid "" "The object will be raised by this number of layers, and support material " "will be generated under it." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1476 +#: src/libslic3r/PrintConfig.cpp:1490 msgid "Resolution" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1477 +#: src/libslic3r/PrintConfig.cpp:1491 msgid "" "Minimum detail resolution, used to simplify the input file for speeding up " "the slicing job and reducing memory usage. High-resolution models often " @@ -6286,278 +6787,278 @@ msgid "" "simplification and use full resolution from input." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1487 +#: src/libslic3r/PrintConfig.cpp:1501 msgid "Minimum travel after retraction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1488 +#: src/libslic3r/PrintConfig.cpp:1502 msgid "" "Retraction is not triggered when travel moves are shorter than this length." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1494 +#: src/libslic3r/PrintConfig.cpp:1508 msgid "Retract amount before wipe" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1495 +#: src/libslic3r/PrintConfig.cpp:1509 msgid "" "With bowden extruders, it may be wise to do some amount of quick retract " "before doing the wipe movement." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1502 +#: src/libslic3r/PrintConfig.cpp:1516 msgid "Retract on layer change" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1503 +#: src/libslic3r/PrintConfig.cpp:1517 msgid "This flag enforces a retraction whenever a Z move is done." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1508 src/libslic3r/PrintConfig.cpp:1516 +#: src/libslic3r/PrintConfig.cpp:1522 src/libslic3r/PrintConfig.cpp:1530 msgid "Length" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1509 +#: src/libslic3r/PrintConfig.cpp:1523 msgid "Retraction Length" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1510 +#: src/libslic3r/PrintConfig.cpp:1524 msgid "" "When retraction is triggered, filament is pulled back by the specified " "amount (the length is measured on raw filament, before it enters the " "extruder)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1512 src/libslic3r/PrintConfig.cpp:1521 +#: src/libslic3r/PrintConfig.cpp:1526 src/libslic3r/PrintConfig.cpp:1535 msgid "mm (zero to disable)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1517 +#: src/libslic3r/PrintConfig.cpp:1531 msgid "Retraction Length (Toolchange)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1518 +#: src/libslic3r/PrintConfig.cpp:1532 msgid "" "When retraction is triggered before changing tool, filament is pulled back " "by the specified amount (the length is measured on raw filament, before it " "enters the extruder)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1526 +#: src/libslic3r/PrintConfig.cpp:1540 msgid "Lift Z" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1527 +#: src/libslic3r/PrintConfig.cpp:1541 msgid "" "If you set this to a positive value, Z is quickly raised every time a " "retraction is triggered. When using multiple extruders, only the setting for " "the first extruder will be considered." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1534 +#: src/libslic3r/PrintConfig.cpp:1548 msgid "Above Z" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1535 +#: src/libslic3r/PrintConfig.cpp:1549 msgid "Only lift Z above" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1536 +#: src/libslic3r/PrintConfig.cpp:1550 msgid "" "If you set this to a positive value, Z lift will only take place above the " "specified absolute Z. You can tune this setting for skipping lift on the " "first layers." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1543 +#: src/libslic3r/PrintConfig.cpp:1557 msgid "Below Z" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1544 +#: src/libslic3r/PrintConfig.cpp:1558 msgid "Only lift Z below" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1545 +#: src/libslic3r/PrintConfig.cpp:1559 msgid "" "If you set this to a positive value, Z lift will only take place below the " "specified absolute Z. You can tune this setting for limiting lift to the " "first layers." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1553 src/libslic3r/PrintConfig.cpp:1561 +#: src/libslic3r/PrintConfig.cpp:1567 src/libslic3r/PrintConfig.cpp:1575 msgid "Extra length on restart" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1554 +#: src/libslic3r/PrintConfig.cpp:1568 msgid "" "When the retraction is compensated after the travel move, the extruder will " "push this additional amount of filament. This setting is rarely needed." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1562 +#: src/libslic3r/PrintConfig.cpp:1576 msgid "" "When the retraction is compensated after changing tool, the extruder will " "push this additional amount of filament." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1569 src/libslic3r/PrintConfig.cpp:1570 +#: src/libslic3r/PrintConfig.cpp:1583 src/libslic3r/PrintConfig.cpp:1584 msgid "Retraction Speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1571 +#: src/libslic3r/PrintConfig.cpp:1585 msgid "The speed for retractions (it only applies to the extruder motor)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1577 src/libslic3r/PrintConfig.cpp:1578 +#: src/libslic3r/PrintConfig.cpp:1591 src/libslic3r/PrintConfig.cpp:1592 msgid "Deretraction Speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1579 +#: src/libslic3r/PrintConfig.cpp:1593 msgid "" "The speed for loading of a filament into extruder after retraction (it only " "applies to the extruder motor). If left to zero, the retraction speed is " "used." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1586 +#: src/libslic3r/PrintConfig.cpp:1600 msgid "Seam position" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1588 +#: src/libslic3r/PrintConfig.cpp:1602 msgid "Position of perimeters starting points." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1594 +#: src/libslic3r/PrintConfig.cpp:1608 msgid "Random" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1595 +#: src/libslic3r/PrintConfig.cpp:1609 msgid "Nearest" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1596 +#: src/libslic3r/PrintConfig.cpp:1610 msgid "Aligned" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1604 +#: src/libslic3r/PrintConfig.cpp:1618 msgid "Direction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1606 +#: src/libslic3r/PrintConfig.cpp:1620 msgid "Preferred direction of the seam" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1607 +#: src/libslic3r/PrintConfig.cpp:1621 msgid "Seam preferred direction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1614 +#: src/libslic3r/PrintConfig.cpp:1628 msgid "Jitter" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1616 +#: src/libslic3r/PrintConfig.cpp:1630 msgid "Seam preferred direction jitter" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1617 +#: src/libslic3r/PrintConfig.cpp:1631 msgid "Preferred direction of the seam - jitter" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1627 +#: src/libslic3r/PrintConfig.cpp:1641 msgid "USB/serial port for printer connection." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1634 +#: src/libslic3r/PrintConfig.cpp:1648 msgid "Serial port speed" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1635 +#: src/libslic3r/PrintConfig.cpp:1649 msgid "Speed (baud) of USB/serial port for printer connection." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1644 +#: src/libslic3r/PrintConfig.cpp:1658 msgid "Distance from object" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1645 +#: src/libslic3r/PrintConfig.cpp:1659 msgid "" "Distance between skirt and object(s). Set this to zero to attach the skirt " "to the object(s) and get a brim for better adhesion." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1652 +#: src/libslic3r/PrintConfig.cpp:1666 msgid "Skirt height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1653 +#: src/libslic3r/PrintConfig.cpp:1667 msgid "" "Height of skirt expressed in layers. Set this to a tall value to use skirt " "as a shield against drafts." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1660 +#: src/libslic3r/PrintConfig.cpp:1674 msgid "Loops (minimum)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1661 +#: src/libslic3r/PrintConfig.cpp:1675 msgid "Skirt Loops" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1662 +#: src/libslic3r/PrintConfig.cpp:1676 msgid "" "Number of loops for the skirt. If the Minimum Extrusion Length option is " "set, the number of loops might be greater than the one configured here. Set " "this to zero to disable skirt completely." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1670 +#: src/libslic3r/PrintConfig.cpp:1684 msgid "Slow down if layer print time is below" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1671 +#: src/libslic3r/PrintConfig.cpp:1685 msgid "" "If layer print time is estimated below this number of seconds, print moves " "speed will be scaled down to extend duration to this value." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1681 +#: src/libslic3r/PrintConfig.cpp:1695 msgid "Small perimeters" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1683 +#: src/libslic3r/PrintConfig.cpp:1697 msgid "" "This separate setting will affect the speed of perimeters having radius <= " "6.5mm (usually holes). If expressed as percentage (for example: 80%) it will " "be calculated on the perimeters speed setting above. Set to zero for auto." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1693 +#: src/libslic3r/PrintConfig.cpp:1707 msgid "Solid infill threshold area" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1695 +#: src/libslic3r/PrintConfig.cpp:1709 msgid "" "Force solid infill for regions having a smaller area than the specified " "threshold." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1696 +#: src/libslic3r/PrintConfig.cpp:1710 msgid "mm²" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1702 +#: src/libslic3r/PrintConfig.cpp:1716 msgid "Solid infill extruder" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1704 +#: src/libslic3r/PrintConfig.cpp:1718 msgid "The extruder to use when printing solid infill." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1710 +#: src/libslic3r/PrintConfig.cpp:1724 msgid "Solid infill every" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1712 +#: src/libslic3r/PrintConfig.cpp:1726 msgid "" "This feature allows to force a solid layer every given number of layers. " "Zero to disable. You can set this to any value (for example 9999); Slic3r " @@ -6565,7 +7066,7 @@ msgid "" "according to nozzle diameter and layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1724 +#: src/libslic3r/PrintConfig.cpp:1738 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill for " "solid surfaces. If left zero, default extrusion width will be used if set, " @@ -6573,22 +7074,22 @@ msgid "" "(for example 90%) it will be computed over layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1734 +#: src/libslic3r/PrintConfig.cpp:1748 msgid "" "Speed for printing solid regions (top/bottom/internal horizontal shells). " "This can be expressed as a percentage (for example: 80%) over the default " "infill speed above. Set to zero for auto." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1746 +#: src/libslic3r/PrintConfig.cpp:1760 msgid "Number of solid layers to generate on top and bottom surfaces." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1752 +#: src/libslic3r/PrintConfig.cpp:1766 msgid "Spiral vase" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1753 +#: src/libslic3r/PrintConfig.cpp:1767 msgid "" "This feature will raise Z gradually while printing a single-walled object in " "order to remove any visible seam. This option requires a single perimeter, " @@ -6597,18 +7098,18 @@ msgid "" "when printing more than an object." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1761 +#: src/libslic3r/PrintConfig.cpp:1775 msgid "Temperature variation" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1762 +#: src/libslic3r/PrintConfig.cpp:1776 msgid "" "Temperature difference to be applied when an extruder is not active. Enables " "a full-height \"sacrificial\" skirt on which the nozzles are periodically " "wiped." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1772 +#: src/libslic3r/PrintConfig.cpp:1786 msgid "" "This start procedure is inserted at the beginning, after bed has reached the " "target temperature and extruder just started heating, and before extruder " @@ -6619,105 +7120,106 @@ msgid "" "\"M109 S[first_layer_temperature]\" command wherever you want." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1787 +#: src/libslic3r/PrintConfig.cpp:1801 msgid "" "This start procedure is inserted at the beginning, after any printer start " -"gcode. This is used to override settings for a specific filament. If Slic3r " -"detects M104, M109, M140 or M190 in your custom codes, such commands will " -"not be prepended automatically so you're free to customize the order of " +"gcode (and after any toolchange to this filament in case of multi-material " +"printers). This is used to override settings for a specific filament. If " +"Slic3r detects M104, M109, M140 or M190 in your custom codes, such commands " +"will not be prepended automatically so you're free to customize the order of " "heating commands and other custom actions. Note that you can use placeholder " "variables for all Slic3r settings, so you can put a \"M109 " "S[first_layer_temperature]\" command wherever you want. If you have multiple " "extruders, the gcode is processed in extruder order." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1802 +#: src/libslic3r/PrintConfig.cpp:1817 msgid "Single Extruder Multi Material" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1803 +#: src/libslic3r/PrintConfig.cpp:1818 msgid "The printer multiplexes filaments into a single hot end." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1808 +#: src/libslic3r/PrintConfig.cpp:1823 msgid "Prime all printing extruders" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1809 +#: src/libslic3r/PrintConfig.cpp:1824 msgid "" "If enabled, all printing extruders will be primed at the front edge of the " "print bed at the start of the print." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1814 +#: src/libslic3r/PrintConfig.cpp:1829 msgid "Generate support material" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1816 +#: src/libslic3r/PrintConfig.cpp:1831 msgid "Enable support material generation." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1820 +#: src/libslic3r/PrintConfig.cpp:1835 msgid "Auto generated supports" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1822 +#: src/libslic3r/PrintConfig.cpp:1837 msgid "" "If checked, supports will be generated automatically based on the overhang " "threshold value. If unchecked, supports will be generated inside the " "\"Support Enforcer\" volumes only." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1828 +#: src/libslic3r/PrintConfig.cpp:1843 msgid "XY separation between an object and its support" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1830 +#: src/libslic3r/PrintConfig.cpp:1845 msgid "" "XY separation between an object and its support. If expressed as percentage " "(for example 50%), it will be calculated over external perimeter width." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1840 +#: src/libslic3r/PrintConfig.cpp:1855 msgid "Pattern angle" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1842 +#: src/libslic3r/PrintConfig.cpp:1857 msgid "" "Use this setting to rotate the support material pattern on the horizontal " "plane." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1852 src/libslic3r/PrintConfig.cpp:2460 +#: src/libslic3r/PrintConfig.cpp:1867 src/libslic3r/PrintConfig.cpp:2531 msgid "" "Only create support if it lies on a build plate. Don't create support on a " "print." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1858 +#: src/libslic3r/PrintConfig.cpp:1873 msgid "Contact Z distance" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1860 +#: src/libslic3r/PrintConfig.cpp:1875 msgid "" "The vertical distance between object and support material interface. Setting " "this to 0 will also prevent Slic3r from using bridge flow and speed for the " "first object layer." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1867 +#: src/libslic3r/PrintConfig.cpp:1882 msgid "0 (soluble)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1868 +#: src/libslic3r/PrintConfig.cpp:1883 msgid "0.2 (detachable)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1873 +#: src/libslic3r/PrintConfig.cpp:1888 msgid "Enforce support for the first" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1875 +#: src/libslic3r/PrintConfig.cpp:1890 msgid "" "Generate support material for the specified number of layers counting from " "bottom, regardless of whether normal support material is enabled or not and " @@ -6725,21 +7227,21 @@ msgid "" "of objects having a very thin or poor footprint on the build plate." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1880 +#: src/libslic3r/PrintConfig.cpp:1895 msgid "Enforce support for the first n layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1886 +#: src/libslic3r/PrintConfig.cpp:1901 msgid "Support material/raft/skirt extruder" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1888 +#: src/libslic3r/PrintConfig.cpp:1903 msgid "" "The extruder to use when printing support material, raft and skirt (1+, 0 to " "use the current extruder to minimize tool changes)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1897 +#: src/libslic3r/PrintConfig.cpp:1912 msgid "" "Set this to a non-zero value to set a manual extrusion width for support " "material. If left zero, default extrusion width will be used if set, " @@ -6747,89 +7249,89 @@ msgid "" "example 90%) it will be computed over layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1905 +#: src/libslic3r/PrintConfig.cpp:1920 msgid "Interface loops" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1907 +#: src/libslic3r/PrintConfig.cpp:1922 msgid "" "Cover the top contact layer of the supports with loops. Disabled by default." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1912 +#: src/libslic3r/PrintConfig.cpp:1927 msgid "Support material/raft interface extruder" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1914 +#: src/libslic3r/PrintConfig.cpp:1929 msgid "" "The extruder to use when printing support material interface (1+, 0 to use " "the current extruder to minimize tool changes). This affects raft too." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1921 +#: src/libslic3r/PrintConfig.cpp:1936 msgid "Interface layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1923 +#: src/libslic3r/PrintConfig.cpp:1938 msgid "" "Number of interface layers to insert between the object(s) and support " "material." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1930 +#: src/libslic3r/PrintConfig.cpp:1945 msgid "Interface pattern spacing" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1932 +#: src/libslic3r/PrintConfig.cpp:1947 msgid "Spacing between interface lines. Set zero to get a solid interface." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1941 +#: src/libslic3r/PrintConfig.cpp:1956 msgid "" "Speed for printing support material interface layers. If expressed as " "percentage (for example 50%) it will be calculated over support material " "speed." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1950 +#: src/libslic3r/PrintConfig.cpp:1965 msgid "Pattern" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1952 +#: src/libslic3r/PrintConfig.cpp:1967 msgid "Pattern used to generate support material." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1958 +#: src/libslic3r/PrintConfig.cpp:1973 msgid "Rectilinear grid" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1964 +#: src/libslic3r/PrintConfig.cpp:1979 msgid "Pattern spacing" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1966 +#: src/libslic3r/PrintConfig.cpp:1981 msgid "Spacing between support material lines." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1975 +#: src/libslic3r/PrintConfig.cpp:1990 msgid "Speed for printing support material." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1982 +#: src/libslic3r/PrintConfig.cpp:1997 msgid "Synchronize with object layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1984 +#: src/libslic3r/PrintConfig.cpp:1999 msgid "" "Synchronize support layers with the object print layers. This is useful with " "multi-material printers, where the extruder switch is expensive." msgstr "" -#: src/libslic3r/PrintConfig.cpp:1990 +#: src/libslic3r/PrintConfig.cpp:2005 msgid "Overhang threshold" msgstr "" -#: src/libslic3r/PrintConfig.cpp:1992 +#: src/libslic3r/PrintConfig.cpp:2007 msgid "" "Support material will not be generated for overhangs whose slope angle (90° " "= vertical) is above the given threshold. In other words, this value " @@ -6838,50 +7340,53 @@ msgid "" "detection (recommended)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2004 +#: src/libslic3r/PrintConfig.cpp:2019 msgid "With sheath around the support" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2006 +#: src/libslic3r/PrintConfig.cpp:2021 msgid "" "Add a sheath (a single perimeter line) around the base support. This makes " "the support more reliable, but also more difficult to remove." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2013 +#: src/libslic3r/PrintConfig.cpp:2028 msgid "" "Extruder temperature for layers after the first one. Set this to zero to " "disable temperature control commands in the output." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2021 +#: src/libslic3r/PrintConfig.cpp:2036 msgid "Detect thin walls" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2023 +#: src/libslic3r/PrintConfig.cpp:2038 msgid "" "Detect single-width walls (parts where two extrusions don't fit and we need " "to collapse them into a single trace)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2029 +#: src/libslic3r/PrintConfig.cpp:2044 msgid "Threads" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2030 +#: src/libslic3r/PrintConfig.cpp:2045 msgid "" "Threads are used to parallelize long-running tasks. Optimal threads number " "is slightly above the number of available cores/processors." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2042 +#: src/libslic3r/PrintConfig.cpp:2057 msgid "" -"This custom code is inserted right before every extruder change. Note that " -"you can use placeholder variables for all Slic3r settings as well as " -"[previous_extruder] and [next_extruder]." +"This custom code is inserted at every extruder change. If you don't leave " +"this empty, you are expected to take care of the toolchange yourself - " +"PrusaSlicer will not output any other G-code to change the filament. You can " +"use placeholder variables for all Slic3r settings as well as " +"[previous_extruder] and [next_extruder], so e.g. the standard toolchange " +"command can be scripted as T[next_extruder]." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2054 +#: src/libslic3r/PrintConfig.cpp:2070 msgid "" "Set this to a non-zero value to set a manual extrusion width for infill for " "top surfaces. You may want to use thinner extrudates to fill all narrow " @@ -6890,7 +7395,7 @@ msgid "" "percentage (for example 90%) it will be computed over layer height." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2065 +#: src/libslic3r/PrintConfig.cpp:2081 msgid "" "Speed for printing top solid layers (it only applies to the uppermost " "external layers and not to their internal solid layers). You may want to " @@ -6899,43 +7404,43 @@ msgid "" "for auto." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2080 +#: src/libslic3r/PrintConfig.cpp:2096 msgid "Number of solid layers to generate on top surfaces." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2081 +#: src/libslic3r/PrintConfig.cpp:2097 msgid "Top solid layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2087 +#: src/libslic3r/PrintConfig.cpp:2103 msgid "Speed for travel moves (jumps between distant extrusion points)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2095 +#: src/libslic3r/PrintConfig.cpp:2111 msgid "Use firmware retraction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2096 +#: src/libslic3r/PrintConfig.cpp:2112 msgid "" "This experimental setting uses G10 and G11 commands to have the firmware " "handle the retraction. This is only supported in recent Marlin." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2102 +#: src/libslic3r/PrintConfig.cpp:2118 msgid "Use relative E distances" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2103 +#: src/libslic3r/PrintConfig.cpp:2119 msgid "" "If your firmware requires relative E values, check this, otherwise leave it " "unchecked. Most firmwares use absolute values." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2109 +#: src/libslic3r/PrintConfig.cpp:2125 msgid "Use volumetric E" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2110 +#: src/libslic3r/PrintConfig.cpp:2126 msgid "" "This experimental setting uses outputs the E values in cubic millimeters " "instead of linear millimeters. If your firmware doesn't already know " @@ -6945,127 +7450,127 @@ msgid "" "only supported in recent Marlin." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2120 +#: src/libslic3r/PrintConfig.cpp:2136 msgid "Enable variable layer height feature" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2121 +#: src/libslic3r/PrintConfig.cpp:2137 msgid "" "Some printers or printer setups may have difficulties printing with a " "variable layer height. Enabled by default." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2127 +#: src/libslic3r/PrintConfig.cpp:2143 msgid "Wipe while retracting" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2128 +#: src/libslic3r/PrintConfig.cpp:2144 msgid "" "This flag will move the nozzle while retracting to minimize the possible " "blob on leaky extruders." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2135 +#: src/libslic3r/PrintConfig.cpp:2151 msgid "" "Multi material printers may need to prime or purge extruders on tool " "changes. Extrude the excess material into the wipe tower." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2141 +#: src/libslic3r/PrintConfig.cpp:2157 msgid "Purging volumes - load/unload volumes" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2142 +#: src/libslic3r/PrintConfig.cpp:2158 msgid "" "This vector saves required volumes to change from/to each tool used on the " "wipe tower. These values are used to simplify creation of the full purging " "volumes below." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2148 +#: src/libslic3r/PrintConfig.cpp:2164 msgid "Purging volumes - matrix" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2149 +#: src/libslic3r/PrintConfig.cpp:2165 msgid "" "This matrix describes volumes (in cubic milimetres) required to purge the " "new filament on the wipe tower for any given pair of tools." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2158 +#: src/libslic3r/PrintConfig.cpp:2174 msgid "Position X" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2159 +#: src/libslic3r/PrintConfig.cpp:2175 msgid "X coordinate of the left front corner of a wipe tower" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2165 +#: src/libslic3r/PrintConfig.cpp:2181 msgid "Position Y" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2166 +#: src/libslic3r/PrintConfig.cpp:2182 msgid "Y coordinate of the left front corner of a wipe tower" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2173 +#: src/libslic3r/PrintConfig.cpp:2189 msgid "Width of a wipe tower" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2179 +#: src/libslic3r/PrintConfig.cpp:2195 msgid "Wipe tower rotation angle" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2180 +#: src/libslic3r/PrintConfig.cpp:2196 msgid "Wipe tower rotation angle with respect to x-axis." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2187 +#: src/libslic3r/PrintConfig.cpp:2203 msgid "Wipe into this object's infill" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2188 +#: src/libslic3r/PrintConfig.cpp:2204 msgid "" "Purging after toolchange will done inside this object's infills. This lowers " "the amount of waste but may result in longer print time due to additional " "travel moves." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2195 +#: src/libslic3r/PrintConfig.cpp:2211 msgid "Wipe into this object" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2196 +#: src/libslic3r/PrintConfig.cpp:2212 msgid "" "Object will be used to purge the nozzle after a toolchange to save material " "that would otherwise end up in the wipe tower and decrease print time. " "Colours of the objects will be mixed as a result." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2202 +#: src/libslic3r/PrintConfig.cpp:2218 msgid "Maximal bridging distance" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2203 +#: src/libslic3r/PrintConfig.cpp:2219 msgid "Maximal distance between supports on sparse infill sections." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2209 +#: src/libslic3r/PrintConfig.cpp:2225 msgid "XY Size Compensation" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2211 +#: src/libslic3r/PrintConfig.cpp:2227 msgid "" "The object will be grown/shrunk in the XY plane by the configured value " "(negative = inwards, positive = outwards). This might be useful for fine-" "tuning hole sizes." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2219 +#: src/libslic3r/PrintConfig.cpp:2235 msgid "Z offset" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2220 +#: src/libslic3r/PrintConfig.cpp:2236 msgid "" "This value will be added (or subtracted) from all the Z coordinates in the " "output G-code. It is used to compensate for bad Z endstop position: for " @@ -7073,308 +7578,345 @@ msgid "" "print bed, set this to -0.3 (or fix your endstop)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2237 +#: src/libslic3r/PrintConfig.cpp:2294 msgid "Display width" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2238 +#: src/libslic3r/PrintConfig.cpp:2295 msgid "Width of the display" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2243 +#: src/libslic3r/PrintConfig.cpp:2300 msgid "Display height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2244 +#: src/libslic3r/PrintConfig.cpp:2301 msgid "Height of the display" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2249 +#: src/libslic3r/PrintConfig.cpp:2306 msgid "Number of pixels in" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2251 +#: src/libslic3r/PrintConfig.cpp:2308 msgid "Number of pixels in X" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2257 +#: src/libslic3r/PrintConfig.cpp:2314 msgid "Number of pixels in Y" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2262 +#: src/libslic3r/PrintConfig.cpp:2319 +msgid "Display horizontal mirroring" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2320 +msgid "Mirror horizontally" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2321 +msgid "Enable horizontal mirroring of output images" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2326 +msgid "Display vertical mirroring" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2327 +msgid "Mirror vertically" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2328 +msgid "Enable vertical mirroring of output images" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2333 msgid "Display orientation" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2263 +#: src/libslic3r/PrintConfig.cpp:2334 msgid "" "Set the actual LCD display orientation inside the SLA printer. Portrait mode " "will flip the meaning of display width and height parameters and the output " "images will be rotated by 90 degrees." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2269 +#: src/libslic3r/PrintConfig.cpp:2340 msgid "Landscape" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2270 +#: src/libslic3r/PrintConfig.cpp:2341 msgid "Portrait" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2275 +#: src/libslic3r/PrintConfig.cpp:2346 msgid "Fast" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2276 +#: src/libslic3r/PrintConfig.cpp:2347 msgid "Fast tilt" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2277 +#: src/libslic3r/PrintConfig.cpp:2348 msgid "Time of the fast tilt" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2284 +#: src/libslic3r/PrintConfig.cpp:2355 msgid "Slow" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2285 +#: src/libslic3r/PrintConfig.cpp:2356 msgid "Slow tilt" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2286 +#: src/libslic3r/PrintConfig.cpp:2357 msgid "Time of the slow tilt" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2293 +#: src/libslic3r/PrintConfig.cpp:2364 msgid "Area fill" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2294 +#: src/libslic3r/PrintConfig.cpp:2365 msgid "" "The percentage of the bed area. \n" "If the print area exceeds the specified value, \n" "then a slow tilt will be used, otherwise - a fast tilt" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2301 src/libslic3r/PrintConfig.cpp:2302 -#: src/libslic3r/PrintConfig.cpp:2303 +#: src/libslic3r/PrintConfig.cpp:2372 src/libslic3r/PrintConfig.cpp:2373 +#: src/libslic3r/PrintConfig.cpp:2374 msgid "Printer scaling correction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2309 src/libslic3r/PrintConfig.cpp:2310 +#: src/libslic3r/PrintConfig.cpp:2380 src/libslic3r/PrintConfig.cpp:2381 msgid "Printer absolute correction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2311 +#: src/libslic3r/PrintConfig.cpp:2382 msgid "" "Will inflate or deflate the sliced 2D polygons according to the sign of the " "correction." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2317 src/libslic3r/PrintConfig.cpp:2318 +#: src/libslic3r/PrintConfig.cpp:2388 src/libslic3r/PrintConfig.cpp:2389 msgid "Printer gamma correction" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2319 +#: src/libslic3r/PrintConfig.cpp:2390 msgid "" "This will apply a gamma correction to the rasterized 2D polygons. A gamma " "value of zero means thresholding with the threshold in the middle. This " "behaviour eliminates antialiasing without losing holes in polygons." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2330 src/libslic3r/PrintConfig.cpp:2331 +#: src/libslic3r/PrintConfig.cpp:2401 src/libslic3r/PrintConfig.cpp:2402 msgid "Initial layer height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2337 +#: src/libslic3r/PrintConfig.cpp:2408 msgid "Faded layers" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2338 +#: src/libslic3r/PrintConfig.cpp:2409 msgid "" "Number of the layers needed for the exposure time fade from initial exposure " "time to the exposure time" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2345 src/libslic3r/PrintConfig.cpp:2346 +#: src/libslic3r/PrintConfig.cpp:2416 src/libslic3r/PrintConfig.cpp:2417 msgid "Exposure time" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2352 src/libslic3r/PrintConfig.cpp:2353 +#: src/libslic3r/PrintConfig.cpp:2423 src/libslic3r/PrintConfig.cpp:2424 msgid "Initial exposure time" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2359 src/libslic3r/PrintConfig.cpp:2360 +#: src/libslic3r/PrintConfig.cpp:2430 src/libslic3r/PrintConfig.cpp:2431 msgid "Correction for expansion" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2366 +#: src/libslic3r/PrintConfig.cpp:2437 msgid "SLA print material notes" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2367 +#: src/libslic3r/PrintConfig.cpp:2438 msgid "You can put your notes regarding the SLA print material here." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2375 src/libslic3r/PrintConfig.cpp:2386 +#: src/libslic3r/PrintConfig.cpp:2446 src/libslic3r/PrintConfig.cpp:2457 msgid "Default SLA material profile" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2397 +#: src/libslic3r/PrintConfig.cpp:2468 msgid "Generate supports" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2399 +#: src/libslic3r/PrintConfig.cpp:2470 msgid "Generate supports for the models" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2404 +#: src/libslic3r/PrintConfig.cpp:2475 msgid "Support head front diameter" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2406 +#: src/libslic3r/PrintConfig.cpp:2477 msgid "Diameter of the pointing side of the head" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2413 +#: src/libslic3r/PrintConfig.cpp:2484 msgid "Support head penetration" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2415 +#: src/libslic3r/PrintConfig.cpp:2486 msgid "How much the pinhead has to penetrate the model surface" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2422 +#: src/libslic3r/PrintConfig.cpp:2493 msgid "Support head width" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2424 +#: src/libslic3r/PrintConfig.cpp:2495 msgid "Width from the back sphere center to the front sphere center" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2432 +#: src/libslic3r/PrintConfig.cpp:2503 msgid "Support pillar diameter" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2434 +#: src/libslic3r/PrintConfig.cpp:2505 msgid "Diameter in mm of the support pillars" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2442 +#: src/libslic3r/PrintConfig.cpp:2513 msgid "Support pillar connection mode" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2443 +#: src/libslic3r/PrintConfig.cpp:2514 msgid "" "Controls the bridge type between two neighboring pillars. Can be zig-zag, " "cross (double zig-zag) or dynamic which will automatically switch between " "the first two depending on the distance of the two pillars." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2451 +#: src/libslic3r/PrintConfig.cpp:2522 msgid "Zig-Zag" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2452 +#: src/libslic3r/PrintConfig.cpp:2523 msgid "Cross" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2453 +#: src/libslic3r/PrintConfig.cpp:2524 msgid "Dynamic" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2465 +#: src/libslic3r/PrintConfig.cpp:2536 msgid "Pillar widening factor" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2467 +#: src/libslic3r/PrintConfig.cpp:2538 msgid "" "Merging bridges or pillars into another pillars can increase the radius. " "Zero means no increase, one means full increase." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2476 +#: src/libslic3r/PrintConfig.cpp:2547 msgid "Support base diameter" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2478 +#: src/libslic3r/PrintConfig.cpp:2549 msgid "Diameter in mm of the pillar base" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2486 +#: src/libslic3r/PrintConfig.cpp:2557 msgid "Support base height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2488 +#: src/libslic3r/PrintConfig.cpp:2559 msgid "The height of the pillar base cone" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2495 +#: src/libslic3r/PrintConfig.cpp:2566 +msgid "Support base safety distance" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2569 +msgid "" +"The minimum distance of the pillar base from the model in mm. Makes sense in " +"zero elevation mode where a gap according to this parameter is inserted " +"between the model and the pad." +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2579 msgid "Critical angle" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2497 +#: src/libslic3r/PrintConfig.cpp:2581 msgid "The default angle for connecting support sticks and junctions." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2505 +#: src/libslic3r/PrintConfig.cpp:2589 msgid "Max bridge length" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2507 +#: src/libslic3r/PrintConfig.cpp:2591 msgid "The max length of a bridge" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2514 +#: src/libslic3r/PrintConfig.cpp:2598 msgid "Max pillar linking distance" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2516 +#: src/libslic3r/PrintConfig.cpp:2600 msgid "" "The max distance of two pillars to get linked with each other. A zero value " "will prohibit pillar cascading." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2524 +#: src/libslic3r/PrintConfig.cpp:2608 msgid "Object elevation" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2526 -msgid "How much the supports should lift up the supported object." +#: src/libslic3r/PrintConfig.cpp:2610 +msgid "" +"How much the supports should lift up the supported object. If this value is " +"zero, the bottom of the model geometry will be considered as part of the pad." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2536 +#: src/libslic3r/PrintConfig.cpp:2622 msgid "This is a relative measure of support points density." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2542 +#: src/libslic3r/PrintConfig.cpp:2628 msgid "Minimal distance of the support points" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2544 +#: src/libslic3r/PrintConfig.cpp:2630 msgid "No support points will be placed closer than this threshold." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2550 +#: src/libslic3r/PrintConfig.cpp:2636 msgid "Use pad" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2552 +#: src/libslic3r/PrintConfig.cpp:2638 msgid "Add a pad underneath the supported model" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2557 +#: src/libslic3r/PrintConfig.cpp:2643 msgid "Pad wall thickness" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2559 +#: src/libslic3r/PrintConfig.cpp:2645 msgid "The thickness of the pad and its optional cavity walls." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2567 +#: src/libslic3r/PrintConfig.cpp:2653 msgid "Pad wall height" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2568 +#: src/libslic3r/PrintConfig.cpp:2654 msgid "" "Defines the pad cavity depth. Set to zero to disable the cavity. Be careful " "when enabling this feature, as some resins may produce an extreme suction " @@ -7382,279 +7924,317 @@ msgid "" "difficult." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2581 +#: src/libslic3r/PrintConfig.cpp:2667 msgid "Max merge distance" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2583 +#: src/libslic3r/PrintConfig.cpp:2669 msgid "" "Some objects can get along with a few smaller pads instead of a single big " "one. This parameter defines how far the center of two smaller pads should " "be. If theyare closer, they will get merged into one pad." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2594 +#: src/libslic3r/PrintConfig.cpp:2680 msgid "Pad edge radius" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2603 +#: src/libslic3r/PrintConfig.cpp:2689 msgid "Pad wall slope" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2605 +#: src/libslic3r/PrintConfig.cpp:2691 msgid "" "The slope of the pad wall relative to the bed plane. 90 degrees means " "straight walls." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2967 +#: src/libslic3r/PrintConfig.cpp:2700 +msgid "Pad object gap" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2702 +msgid "" +"The gap between the object bottom and the generated pad in zero elevation " +"mode." +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2711 +msgid "Pad object connector stride" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2713 +msgid "" +"Distance between two connector sticks between the object pad and the " +"generated pad." +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2721 +msgid "Pad object connector width" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2723 +msgid "" +"The width of the connectors sticks which connect the object pad and the " +"generated pad." +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2731 +msgid "Pad object connector penetration" +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:2734 +msgid "How much should the tiny connectors penetrate into the model body." +msgstr "" + +#: src/libslic3r/PrintConfig.cpp:3094 msgid "Export OBJ" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2968 +#: src/libslic3r/PrintConfig.cpp:3095 msgid "Export the model(s) as OBJ." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2979 +#: src/libslic3r/PrintConfig.cpp:3106 msgid "Export SLA" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2980 +#: src/libslic3r/PrintConfig.cpp:3107 msgid "Slice the model and export SLA printing layers as PNG." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2985 +#: src/libslic3r/PrintConfig.cpp:3112 msgid "Export 3MF" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2986 +#: src/libslic3r/PrintConfig.cpp:3113 msgid "Export the model(s) as 3MF." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2990 +#: src/libslic3r/PrintConfig.cpp:3117 msgid "Export AMF" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2991 +#: src/libslic3r/PrintConfig.cpp:3118 msgid "Export the model(s) as AMF." msgstr "" -#: src/libslic3r/PrintConfig.cpp:2995 +#: src/libslic3r/PrintConfig.cpp:3122 msgid "Export STL" msgstr "" -#: src/libslic3r/PrintConfig.cpp:2996 +#: src/libslic3r/PrintConfig.cpp:3123 msgid "Export the model(s) as STL." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3001 +#: src/libslic3r/PrintConfig.cpp:3128 msgid "Slice the model and export toolpaths as G-code." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3006 +#: src/libslic3r/PrintConfig.cpp:3133 msgid "Slice" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3007 +#: src/libslic3r/PrintConfig.cpp:3134 msgid "" "Slice the model as FFF or SLA based on the printer_technology configuration " "value." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3012 +#: src/libslic3r/PrintConfig.cpp:3139 msgid "Help" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3013 +#: src/libslic3r/PrintConfig.cpp:3140 msgid "Show this help." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3018 +#: src/libslic3r/PrintConfig.cpp:3145 msgid "Help (FFF options)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3019 +#: src/libslic3r/PrintConfig.cpp:3146 msgid "Show the full list of print/G-code configuration options." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3023 +#: src/libslic3r/PrintConfig.cpp:3150 msgid "Help (SLA options)" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3024 +#: src/libslic3r/PrintConfig.cpp:3151 msgid "Show the full list of SLA print configuration options." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3028 +#: src/libslic3r/PrintConfig.cpp:3155 msgid "Output Model Info" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3029 +#: src/libslic3r/PrintConfig.cpp:3156 msgid "Write information about the model to the console." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3033 +#: src/libslic3r/PrintConfig.cpp:3160 msgid "Save config file" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3034 +#: src/libslic3r/PrintConfig.cpp:3161 msgid "Save configuration to the specified file." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3044 +#: src/libslic3r/PrintConfig.cpp:3171 msgid "Align XY" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3045 +#: src/libslic3r/PrintConfig.cpp:3172 msgid "Align the model to the given point." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3050 +#: src/libslic3r/PrintConfig.cpp:3177 msgid "Cut model at the given Z." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3071 +#: src/libslic3r/PrintConfig.cpp:3198 msgid "Center" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3072 +#: src/libslic3r/PrintConfig.cpp:3199 msgid "Center the print around the given center." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3076 +#: src/libslic3r/PrintConfig.cpp:3203 msgid "Don't arrange" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3077 +#: src/libslic3r/PrintConfig.cpp:3204 msgid "" "Do not rearrange the given models before merging and keep their original XY " "coordinates." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3080 +#: src/libslic3r/PrintConfig.cpp:3207 msgid "Duplicate" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3081 +#: src/libslic3r/PrintConfig.cpp:3208 msgid "Multiply copies by this factor." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3085 +#: src/libslic3r/PrintConfig.cpp:3212 msgid "Duplicate by grid" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3086 +#: src/libslic3r/PrintConfig.cpp:3213 msgid "Multiply copies by creating a grid." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3089 +#: src/libslic3r/PrintConfig.cpp:3216 msgid "Merge" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3090 +#: src/libslic3r/PrintConfig.cpp:3217 msgid "" "Arrange the supplied models in a plate and merge them in a single model in " "order to perform actions once." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3095 +#: src/libslic3r/PrintConfig.cpp:3222 msgid "" "Try to repair any non-manifold meshes (this option is implicitly added " "whenever we need to slice the model to perform the requested action)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3099 +#: src/libslic3r/PrintConfig.cpp:3226 msgid "Rotation angle around the Z axis in degrees." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3103 +#: src/libslic3r/PrintConfig.cpp:3230 msgid "Rotate around X" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3104 +#: src/libslic3r/PrintConfig.cpp:3231 msgid "Rotation angle around the X axis in degrees." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3108 +#: src/libslic3r/PrintConfig.cpp:3235 msgid "Rotate around Y" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3109 +#: src/libslic3r/PrintConfig.cpp:3236 msgid "Rotation angle around the Y axis in degrees." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3114 +#: src/libslic3r/PrintConfig.cpp:3241 msgid "Scaling factor or percentage." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3119 +#: src/libslic3r/PrintConfig.cpp:3246 msgid "" "Detect unconnected parts in the given model(s) and split them into separate " "objects." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3122 +#: src/libslic3r/PrintConfig.cpp:3249 msgid "Scale to Fit" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3123 +#: src/libslic3r/PrintConfig.cpp:3250 msgid "Scale to fit the given volume." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3132 +#: src/libslic3r/PrintConfig.cpp:3259 msgid "Ignore non-existent config files" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3133 +#: src/libslic3r/PrintConfig.cpp:3260 msgid "Do not fail if a file supplied to --load does not exist." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3136 +#: src/libslic3r/PrintConfig.cpp:3263 msgid "Load config file" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3137 +#: src/libslic3r/PrintConfig.cpp:3264 msgid "" "Load configuration from the specified file. It can be used more than once to " "load options from multiple files." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3140 +#: src/libslic3r/PrintConfig.cpp:3267 msgid "Output File" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3141 +#: src/libslic3r/PrintConfig.cpp:3268 msgid "" "The file where the output will be written (if not specified, it will be " "based on the input file)." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3151 +#: src/libslic3r/PrintConfig.cpp:3278 msgid "Data directory" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3152 +#: src/libslic3r/PrintConfig.cpp:3279 msgid "" "Load and store settings at the given directory. This is useful for " "maintaining different profiles or including configurations from a network " "storage." msgstr "" -#: src/libslic3r/PrintConfig.cpp:3155 +#: src/libslic3r/PrintConfig.cpp:3282 msgid "Logging level" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3156 +#: src/libslic3r/PrintConfig.cpp:3283 msgid "" "Messages with severity lower or eqal to the loglevel will be printed out. 0:" "trace, 1:debug, 2:info, 3:warning, 4:error, 5:fatal" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3161 +#: src/libslic3r/PrintConfig.cpp:3288 msgid "Render with a software renderer" msgstr "" -#: src/libslic3r/PrintConfig.cpp:3162 +#: src/libslic3r/PrintConfig.cpp:3289 msgid "" "Render with a software renderer. The bundled MESA software renderer is " "loaded instead of the default OpenGL driver." @@ -7696,21 +8276,21 @@ msgstr "" msgid "Volumetric flow rate (mm3/s)" msgstr "" -#: src/libslic3r/GCode/PreviewData.cpp:491 +#: src/libslic3r/GCode/PreviewData.cpp:493 msgid "Default print color" msgstr "" -#: src/libslic3r/GCode/PreviewData.cpp:495 +#: src/libslic3r/GCode/PreviewData.cpp:500 #, possible-c-format msgid "up to %.2f mm" msgstr "" -#: src/libslic3r/GCode/PreviewData.cpp:499 +#: src/libslic3r/GCode/PreviewData.cpp:504 #, possible-c-format msgid "above %.2f mm" msgstr "" -#: src/libslic3r/GCode/PreviewData.cpp:504 +#: src/libslic3r/GCode/PreviewData.cpp:509 #, possible-c-format msgid "%.2f - %.2f mm" msgstr "" diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 1177c91804..a9be260bd5 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -212,7 +212,7 @@ wxPanel* BedShapePanel::init_texture_panel() wxStaticText* lbl = dynamic_cast(e.GetEventObject()); if (lbl != nullptr) { - wxString tooltip_text = (m_custom_texture == NONE) ? _(L("")) : _(m_custom_texture); + wxString tooltip_text = (m_custom_texture == NONE) ? "" : _(m_custom_texture); wxToolTip* tooltip = lbl->GetToolTip(); if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text)) lbl->SetToolTip(tooltip_text); @@ -280,7 +280,7 @@ wxPanel* BedShapePanel::init_model_panel() wxStaticText* lbl = dynamic_cast(e.GetEventObject()); if (lbl != nullptr) { - wxString tooltip_text = (m_custom_model == NONE) ? _(L("")) : _(m_custom_model); + wxString tooltip_text = (m_custom_model == NONE) ? "" : _(m_custom_model); wxToolTip* tooltip = lbl->GetToolTip(); if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text)) lbl->SetToolTip(tooltip_text); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 0716fc3f63..5eb9f6cf96 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2928,7 +2928,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else if (evt.Moving()) { m_mouse.position = pos.cast(); - std::string tooltip = L(""); + std::string tooltip = ""; if (tooltip.empty()) tooltip = m_gizmos.get_tooltip(); @@ -3216,7 +3216,7 @@ void GLCanvas3D::do_flatten(const Vec3d& normal, const std::string& snapshot_typ wxGetApp().plater()->take_snapshot(_(snapshot_type)); m_selection.flattening_rotate(normal); - do_rotate(L("")); // avoid taking another snapshot + do_rotate(""); // avoid taking another snapshot } void GLCanvas3D::do_mirror(const std::string& snapshot_type) @@ -3619,14 +3619,14 @@ bool GLCanvas3D::_init_undoredo_toolbar() std::string curr_additional_tooltip; m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); - std::string new_additional_tooltip = L(""); + std::string new_additional_tooltip = ""; if (can_undo) wxGetApp().plater()->undo_redo_topmost_string_getter(true, new_additional_tooltip); if (new_additional_tooltip != curr_additional_tooltip) { m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); - set_tooltip(L("")); + set_tooltip(""); } return can_undo; }; @@ -3648,14 +3648,14 @@ bool GLCanvas3D::_init_undoredo_toolbar() std::string curr_additional_tooltip; m_undoredo_toolbar.get_additional_tooltip(id, curr_additional_tooltip); - std::string new_additional_tooltip = L(""); + std::string new_additional_tooltip = ""; if (can_redo) wxGetApp().plater()->undo_redo_topmost_string_getter(false, new_additional_tooltip); if (new_additional_tooltip != curr_additional_tooltip) { m_undoredo_toolbar.set_additional_tooltip(id, new_additional_tooltip); - set_tooltip(L("")); + set_tooltip(""); } return can_redo; }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index dddf479eb7..bb188713ba 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4536,7 +4536,7 @@ void Plater::undo_redo_topmost_string_getter(const bool is_undo, std::string& ou return; } - out_text = L(""); + out_text = ""; } void Plater::on_extruders_change(int num_extruders) From e1ff808f14f214dfcad2e4e779b43273b69e82f0 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 6 Aug 2019 11:29:26 +0200 Subject: [PATCH 502/627] Fixed parallelization of texture compression: Memory synchronization (memory barriers) are introduced using std::atomic variables. --- src/slic3r/GUI/GLTexture.cpp | 75 +++++++++++++++++------------------- src/slic3r/GUI/GLTexture.hpp | 18 +++++---- 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 516f8b934f..4fdf12489d 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -27,49 +27,57 @@ namespace GUI { void GLTexture::Compressor::reset() { - if (m_is_compressing) - { - // force compression completion, if any + if (m_thread.joinable()) { m_abort_compressing = true; - // wait for compression completion, if any - while (m_is_compressing) {} - } - - m_levels.clear(); -} - -void GLTexture::Compressor::add_level(unsigned int w, unsigned int h, const std::vector& data) -{ - m_levels.emplace_back(w, h, data); + m_thread.join(); + m_levels.clear(); + m_num_levels_compressed = 0; + m_abort_compressing = false; + } + assert(m_levels.empty()); + assert(m_abort_compressing == false); + assert(m_num_levels_compressed == 0); } void GLTexture::Compressor::start_compressing() { - std::thread t(&GLTexture::Compressor::compress, this); - t.detach(); + // The worker thread should be stopped already. + assert(! m_thread.joinable()); + assert(! m_levels.empty()); + assert(m_abort_compressing == false); + assert(m_num_levels_compressed == 0); + if (! m_levels.empty()) { + std::thread thrd(&GLTexture::Compressor::compress, this); + m_thread = std::move(thrd); + } } bool GLTexture::Compressor::unsent_compressed_data_available() const { - for (const Level& level : m_levels) - { - if (!level.sent_to_gpu && level.compressed) + if (m_levels.empty()) + return false; + // Querying the atomic m_num_levels_compressed value synchronizes processor caches, so that the dat of m_levels modified by the worker thread are accessible to the calling thread. + unsigned int num_compressed = m_num_levels_compressed; + for (unsigned int i = 0; i < num_compressed; ++ i) + if (! m_levels[i].sent_to_gpu && ! m_levels[i].compressed_data.empty()) return true; - } - return false; } void GLTexture::Compressor::send_compressed_data_to_gpu() { // this method should be called inside the main thread of Slicer or a new OpenGL context (sharing resources) would be needed + if (m_levels.empty()) + return; glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.m_id)); - for (int i = 0; i < (int)m_levels.size(); ++i) + // Querying the atomic m_num_levels_compressed value synchronizes processor caches, so that the dat of m_levels modified by the worker thread are accessible to the calling thread. + int num_compressed = (int)m_num_levels_compressed; + for (int i = 0; i < num_compressed; ++ i) { Level& level = m_levels[i]; - if (!level.sent_to_gpu && level.compressed) + if (! level.sent_to_gpu && ! level.compressed_data.empty()) { glsafe(::glCompressedTexSubImage2D(GL_TEXTURE_2D, (GLint)i, 0, 0, (GLsizei)level.w, (GLsizei)level.h, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)level.compressed_data.size(), (const GLvoid*)level.compressed_data.data())); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i)); @@ -77,29 +85,21 @@ void GLTexture::Compressor::send_compressed_data_to_gpu() level.sent_to_gpu = true; // we are done with the compressed data, we can discard it level.compressed_data.clear(); - level.compressed = false; } } glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); -} -bool GLTexture::Compressor::all_compressed_data_sent_to_gpu() const -{ - for (const Level& level : m_levels) - { - if (!level.sent_to_gpu) - return false; - } - - return true; + if (num_compressed == (unsigned int)m_levels.size()) + // Finalize the worker thread, close it. + this->reset(); } void GLTexture::Compressor::compress() { // reference: https://github.com/Cyan4973/RygsDXTc - m_is_compressing = true; - m_abort_compressing = false; + assert(m_num_levels_compressed == 0); + assert(m_abort_compressing == false); for (Level& level : m_levels) { @@ -115,11 +115,8 @@ void GLTexture::Compressor::compress() // we are done with the source data, we can discard it level.src_data.clear(); - level.compressed = true; + ++ m_num_levels_compressed; } - - m_is_compressing = false; - m_abort_compressing = false; } GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } }; diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index ec362944d4..26829fa72f 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -19,30 +19,34 @@ namespace GUI { unsigned int h; std::vector src_data; std::vector compressed_data; - bool compressed; bool sent_to_gpu; - Level(unsigned int w, unsigned int h, const std::vector& data) : w(w), h(h), src_data(data), compressed(false), sent_to_gpu(false) {} + Level(unsigned int w, unsigned int h, const std::vector& data) : w(w), h(h), src_data(data), sent_to_gpu(false) {} }; GLTexture& m_texture; std::vector m_levels; - bool m_is_compressing; - bool m_abort_compressing; + std::thread m_thread; + // Does the caller want the background thread to stop? + // This atomic also works as a memory barrier for synchronizing the cancel event with the worker thread. + std::atomic m_abort_compressing; + // How many levels were compressed since the start of the background processing thread? + // This atomic also works as a memory barrier for synchronizing results of the worker thread with the calling thread. + std::atomic m_num_levels_compressed; public: - explicit Compressor(GLTexture& texture) : m_texture(texture), m_is_compressing(false), m_abort_compressing(false) {} + explicit Compressor(GLTexture& texture) : m_texture(texture), m_abort_compressing(false), m_num_levels_compressed(0) {} ~Compressor() { reset(); } void reset(); - void add_level(unsigned int w, unsigned int h, const std::vector& data); + void add_level(unsigned int w, unsigned int h, const std::vector& data) { m_levels.emplace_back(w, h, data); } void start_compressing(); bool unsent_compressed_data_available() const; void send_compressed_data_to_gpu(); - bool all_compressed_data_sent_to_gpu() const; + bool all_compressed_data_sent_to_gpu() const { return m_levels.empty(); } private: void compress(); From 29d9c65ee2c3b8d53bb53c0c619e299648f17c3d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 6 Aug 2019 11:40:33 +0200 Subject: [PATCH 503/627] Missing include (required by clang, not required by msvc) --- src/slic3r/GUI/GLTexture.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index 26829fa72f..2c8941632c 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -3,6 +3,7 @@ #include #include +#include class wxImage; From 74e592ceaa22ee8793047ccaf26fdadbd46eff5d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 6 Aug 2019 15:11:46 +0200 Subject: [PATCH 504/627] Improved handling of excessive extrusion width values (too small or too big). Fixes std: bad_alloc #2715 --- src/libslic3r/Flow.cpp | 20 +++++++++++++++----- src/libslic3r/Print.cpp | 33 +++++++++++++++++++++++++++++++-- src/slic3r/GUI/Tab.cpp | 16 +++++++++++++--- src/slic3r/GUI/Tab.hpp | 1 + 4 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index e71b935dbb..6069677a1b 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -76,10 +76,14 @@ float Flow::spacing() const return this->width + BRIDGE_EXTRA_SPACING; // rectangle with semicircles at the ends float min_flow_spacing = this->width - this->height * (1. - 0.25 * PI); - return this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing); + float res = this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing); #else - return float(this->bridge ? (this->width + BRIDGE_EXTRA_SPACING) : (this->width - this->height * (1. - 0.25 * PI))); + float res = float(this->bridge ? (this->width + BRIDGE_EXTRA_SPACING) : (this->width - this->height * (1. - 0.25 * PI))); #endif +// assert(res > 0.f); + if (res <= 0.f) + throw std::runtime_error("Flow::spacing() produced negative spacing. Did you set some extrusion width too small?"); + return res; } // This method returns the centerline spacing between an extrusion using this @@ -89,20 +93,26 @@ float Flow::spacing(const Flow &other) const { assert(this->height == other.height); assert(this->bridge == other.bridge); - return float(this->bridge ? + float res = float(this->bridge ? 0.5 * this->width + 0.5 * other.width + BRIDGE_EXTRA_SPACING : 0.5 * this->spacing() + 0.5 * other.spacing()); +// assert(res > 0.f); + if (res <= 0.f) + throw std::runtime_error("Flow::spacing() produced negative spacing. Did you set some extrusion width too small?"); + return res; } // This method returns extrusion volume per head move unit. double Flow::mm3_per_mm() const { - double res = this->bridge ? + float res = this->bridge ? // Area of a circle with dmr of this->width. (this->width * this->width) * 0.25 * PI : // Rectangle with semicircles at the ends. ~ h (w - 0.215 h) this->height * (this->width - this->height * (1. - 0.25 * PI)); - assert(res > 0.); + //assert(res > 0.); + if (res <= 0.) + throw std::runtime_error("Flow::mm3_per_mm() produced negative flow. Did you set some extrusion width too small?"); return res; } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e53f498106..1664acdded 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include //! macro used to mark string used at localization, @@ -1168,7 +1169,7 @@ std::string Print::validate() const bool has_custom_layering = false; std::vector> layer_height_profiles; for (const PrintObject *object : m_objects) { - has_custom_layering = ! object->model_object()->layer_config_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); // #ys_FIXME_experiment + has_custom_layering = ! object->model_object()->layer_config_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); if (has_custom_layering) { layer_height_profiles.assign(m_objects.size(), std::vector()); break; @@ -1247,6 +1248,18 @@ std::string Print::validate() const return L("One or more object were assigned an extruder that the printer does not have."); #endif + auto validate_extrusion_width = [min_nozzle_diameter, max_nozzle_diameter](const ConfigBase &config, const char *opt_key, double layer_height, std::string &err_msg) -> bool { + double extrusion_width_min = config.get_abs_value(opt_key, min_nozzle_diameter); + double extrusion_width_max = config.get_abs_value(opt_key, max_nozzle_diameter); + if (extrusion_width_min <= layer_height) { + err_msg = (boost::format(L("%1%=%2% mm is too low to be printable at a layer height %3% mm")) % opt_key % extrusion_width_min % layer_height).str(); + return false; + } else if (extrusion_width_max >= max_nozzle_diameter * 2.) { + err_msg = (boost::format(L("Excessive %1%=%2% mm to be printable with a nozzle diameter %3% mm")) % opt_key % extrusion_width_max % max_nozzle_diameter).str(); + return false; + } + return true; + }; for (PrintObject *object : m_objects) { if (object->config().raft_layers > 0 || object->config().support_material.value) { if ((object->config().support_material_extruder == 0 || object->config().support_material_interface_extruder == 0) && max_nozzle_diameter - min_nozzle_diameter > EPSILON) { @@ -1290,8 +1303,24 @@ std::string Print::validate() const return L("First layer height can't be greater than nozzle diameter"); // validate layer_height - if (object->config().layer_height.value > min_nozzle_diameter) + double layer_height = object->config().layer_height.value; + if (layer_height > min_nozzle_diameter) return L("Layer height can't be greater than nozzle diameter"); + + // Validate extrusion widths. + for (const char *opt_key : { "extrusion_width", "support_material_extrusion_width" }) { + std::string err_msg; + if (! validate_extrusion_width(object->config(), opt_key, layer_height, err_msg)) + return err_msg; + } + for (const char *opt_key : { "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width" }) { + for (size_t i = 0; i < object->region_volumes.size(); ++ i) + if (! object->region_volumes[i].empty()) { + std::string err_msg; + if (! validate_extrusion_width(this->get_region(i)->config(), opt_key, layer_height, err_msg)) + return err_msg; + } + } } } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9d7fc20a3e..ac89ba898c 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1758,6 +1758,17 @@ void TabFilament::reload_config() Tab::reload_config(); } +void TabFilament::update_volumetric_flow_preset_hints() +{ + wxString text; + try { + text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); + } catch (std::exception &ex) { + text = _(L("Volumetric flow hints not available\n\n")) + from_u8(ex.what()); + } + m_volumetric_speed_description_line->SetText(text); +} + void TabFilament::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) @@ -1767,8 +1778,7 @@ void TabFilament::update() wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); m_cooling_description_line->SetText(text); - text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); - m_volumetric_speed_description_line->SetText(text); + this->update_volumetric_flow_preset_hints(); Layout(); bool cooling = m_config->opt_bool("cooling", 0); @@ -1790,7 +1800,7 @@ void TabFilament::update() void TabFilament::OnActivate() { - m_volumetric_speed_description_line->SetText(from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle))); + this->update_volumetric_flow_preset_hints(); Tab::OnActivate(); } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 6ff76f5c4a..423bc198f1 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -340,6 +340,7 @@ class TabFilament : public Tab void add_filament_overrides_page(); void update_filament_overrides_page(); + void update_volumetric_flow_preset_hints(); std::map m_overrides_options; public: From 9905f8d349f8e4f0f4a38517adb5c9d6936ab305 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 6 Aug 2019 15:36:16 +0200 Subject: [PATCH 505/627] Fix of the previous commit: zero extrusion width parameter is always valid, it is replaced with an "auto" value. --- src/libslic3r/Print.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 1664acdded..9df122cee5 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1251,7 +1251,9 @@ std::string Print::validate() const auto validate_extrusion_width = [min_nozzle_diameter, max_nozzle_diameter](const ConfigBase &config, const char *opt_key, double layer_height, std::string &err_msg) -> bool { double extrusion_width_min = config.get_abs_value(opt_key, min_nozzle_diameter); double extrusion_width_max = config.get_abs_value(opt_key, max_nozzle_diameter); - if (extrusion_width_min <= layer_height) { + if (extrusion_width_min == 0) { + // Default "auto-generated" extrusion width is always valid. + } else if (extrusion_width_min <= layer_height) { err_msg = (boost::format(L("%1%=%2% mm is too low to be printable at a layer height %3% mm")) % opt_key % extrusion_width_min % layer_height).str(); return false; } else if (extrusion_width_max >= max_nozzle_diameter * 2.) { From 8a2362587d8301897afe991800398be196ab8da8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 6 Aug 2019 14:54:38 +0200 Subject: [PATCH 506/627] Save/load printable flag to/from 3mf and amf --- src/libslic3r/Format/3mf.cpp | 25 ++++++++++++++++++------- src/libslic3r/Format/AMF.cpp | 21 ++++++++++++++++++--- src/slic3r/GUI/Plater.cpp | 3 +++ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 0cc0a9d584..fd1d11c2fb 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -71,6 +71,7 @@ const char* V2_ATTR = "v2"; const char* V3_ATTR = "v3"; const char* OBJECTID_ATTR = "objectid"; const char* TRANSFORM_ATTR = "transform"; +const char* PRINTABLE_ATTR = "printable"; const char* KEY_ATTR = "key"; const char* VALUE_ATTR = "value"; @@ -131,6 +132,12 @@ int get_attribute_value_int(const char** attributes, unsigned int attributes_siz return (text != nullptr) ? ::atoi(text) : 0; } +bool get_attribute_value_bool(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); + return (text != nullptr) ? (bool)::atoi(text) : true; +} + Slic3r::Transform3d get_transform_from_string(const std::string& mat_str) { if (mat_str.empty()) @@ -428,7 +435,7 @@ namespace Slic3r { bool _handle_start_metadata(const char** attributes, unsigned int num_attributes); bool _handle_end_metadata(); - bool _create_object_instance(int object_id, const Transform3d& transform, unsigned int recur_counter); + bool _create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter); void _apply_transform(ModelInstance& instance, const Transform3d& transform); @@ -1367,8 +1374,9 @@ namespace Slic3r { int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); Transform3d transform = get_transform_from_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); + int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR); - return _create_object_instance(object_id, transform, 1); + return _create_object_instance(object_id, transform, printable, 1); } bool _3MF_Importer::_handle_end_item() @@ -1396,7 +1404,7 @@ namespace Slic3r { return true; } - bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, unsigned int recur_counter) + bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) { static const unsigned int MAX_RECURSIONS = 10; @@ -1432,6 +1440,7 @@ namespace Slic3r { add_error("Unable to add object instance"); return false; } + instance->printable = printable; m_instances.emplace_back(instance, transform); } @@ -1441,7 +1450,7 @@ namespace Slic3r { // recursively process nested components for (const Component& component : it->second) { - if (!_create_object_instance(component.object_id, transform * component.transform, recur_counter + 1)) + if (!_create_object_instance(component.object_id, transform * component.transform, printable, recur_counter + 1)) return false; } } @@ -1655,10 +1664,12 @@ namespace Slic3r { { unsigned int id; Transform3d transform; + bool printable; - BuildItem(unsigned int id, const Transform3d& transform) + BuildItem(unsigned int id, const Transform3d& transform, const bool printable) : id(id) , transform(transform) + , printable(printable) { } }; @@ -1951,7 +1962,7 @@ namespace Slic3r { Transform3d t = instance->get_matrix(); // instance_id is just a 1 indexed index in build_items. assert(instance_id == build_items.size() + 1); - build_items.emplace_back(instance_id, t); + build_items.emplace_back(instance_id, t, instance->printable); stream << " \n"; @@ -2059,7 +2070,7 @@ namespace Slic3r { stream << " "; } } - stream << "\" />\n"; + stream << "\" printable =\"" << item.printable << "\" />\n"; } stream << " \n"; diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 4370a2e1c0..08820f2ea1 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -137,6 +137,7 @@ struct AMFParserContext NODE_TYPE_MIRRORX, // amf/constellation/instance/mirrorx NODE_TYPE_MIRRORY, // amf/constellation/instance/mirrory NODE_TYPE_MIRRORZ, // amf/constellation/instance/mirrorz + NODE_TYPE_PRINTABLE, // amf/constellation/instance/mirrorz NODE_TYPE_METADATA, // anywhere under amf/*/metadata }; @@ -145,7 +146,8 @@ struct AMFParserContext : deltax_set(false), deltay_set(false), deltaz_set(false) , rx_set(false), ry_set(false), rz_set(false) , scalex_set(false), scaley_set(false), scalez_set(false) - , mirrorx_set(false), mirrory_set(false), mirrorz_set(false) {} + , mirrorx_set(false), mirrory_set(false), mirrorz_set(false) + , printable(true) {} // Shift in the X axis. float deltax; bool deltax_set; @@ -178,6 +180,8 @@ struct AMFParserContext bool mirrory_set; float mirrorz; bool mirrorz_set; + // printable property + bool printable; bool anything_set() const { return deltax_set || deltay_set || deltaz_set || rx_set || ry_set || rz_set || @@ -321,6 +325,8 @@ void AMFParserContext::startElement(const char *name, const char **atts) node_type_new = NODE_TYPE_MIRRORY; else if (strcmp(name, "mirrorz") == 0) node_type_new = NODE_TYPE_MIRRORZ; + else if (strcmp(name, "printable") == 0) + node_type_new = NODE_TYPE_PRINTABLE; } else if (m_path[2] == NODE_TYPE_LAYER_CONFIG && strcmp(name, "range") == 0) { assert(m_object); @@ -397,7 +403,8 @@ void AMFParserContext::characters(const XML_Char *s, int len) m_path.back() == NODE_TYPE_SCALE || m_path.back() == NODE_TYPE_MIRRORX || m_path.back() == NODE_TYPE_MIRRORY || - m_path.back() == NODE_TYPE_MIRRORZ) + m_path.back() == NODE_TYPE_MIRRORZ || + m_path.back() == NODE_TYPE_PRINTABLE) m_value[0].append(s, len); break; case 6: @@ -507,6 +514,11 @@ void AMFParserContext::endElement(const char * /* name */) m_instance->mirrorz_set = true; m_value[0].clear(); break; + case NODE_TYPE_PRINTABLE: + assert(m_instance); + m_instance->printable = bool(atoi(m_value[0].c_str())); + m_value[0].clear(); + break; // Object vertices: case NODE_TYPE_VERTEX: @@ -685,6 +697,7 @@ void AMFParserContext::endDocument() mi->set_rotation(Vec3d(instance.rx_set ? (double)instance.rx : 0.0, instance.ry_set ? (double)instance.ry : 0.0, instance.rz_set ? (double)instance.rz : 0.0)); mi->set_scaling_factor(Vec3d(instance.scalex_set ? (double)instance.scalex : 1.0, instance.scaley_set ? (double)instance.scaley : 1.0, instance.scalez_set ? (double)instance.scalez : 1.0)); mi->set_mirror(Vec3d(instance.mirrorx_set ? (double)instance.mirrorx : 1.0, instance.mirrory_set ? (double)instance.mirrory : 1.0, instance.mirrorz_set ? (double)instance.mirrorz : 1.0)); + mi->printable = instance.printable; } } } @@ -1037,6 +1050,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) " %lf\n" " %lf\n" " %lf\n" + " %d\n" " \n", object_id, instance->get_offset(X), @@ -1050,7 +1064,8 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) instance->get_scaling_factor(Z), instance->get_mirror(X), instance->get_mirror(Y), - instance->get_mirror(Z)); + instance->get_mirror(Z), + instance->printable); //FIXME missing instance->scaling_factor instances.append(buf); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 9e0b61a9bc..506f2b36b5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2326,6 +2326,9 @@ std::vector Plater::priv::load_files(const std::vector& input_ // automatic selection of added objects if (!obj_idxs.empty() && (view3D != nullptr)) { + // update printable state for new volumes on canvas3D + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs); + Selection& selection = view3D->get_canvas3d()->get_selection(); selection.clear(); for (size_t idx : obj_idxs) From 3c09473f2a936cfb7f6262b36816f9f48f8e6842 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Aug 2019 16:51:32 +0200 Subject: [PATCH 507/627] Added additional checkbox to enable zero elevation --- src/libslic3r/PrintConfig.cpp | 423 ++-- src/libslic3r/PrintConfig.hpp | 88 +- src/libslic3r/SLAPrint.cpp | 100 +- src/slic3r/GUI/Preset.cpp | 531 ++--- src/slic3r/GUI/Tab.cpp | 4052 +++++++++++++++++---------------- 5 files changed, 2626 insertions(+), 2568 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index e49c81f460..2033658640 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -12,7 +12,7 @@ namespace Slic3r { -//! macro used to mark string used at localization, +//! macro used to mark string used at localization, //! return same string #define L(s) (s) #define _(s) Slic3r::I18N::translate(s) @@ -51,7 +51,7 @@ void PrintConfigDef::init_common_params() def->label = L("Bed shape"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) }); - + def = this->add("bed_custom_texture", coString); def->label = L("Bed custom texture"); def->mode = comAdvanced; @@ -85,8 +85,8 @@ void PrintConfigDef::init_common_params() "The gap closing operation may reduce the final print resolution, therefore it is advisable to keep the value reasonably low."); def->sidetext = L("mm"); def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.049)); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.049)); def = this->add("print_host", coString); def->label = L("Hostname, IP or URL"); @@ -101,7 +101,7 @@ void PrintConfigDef::init_common_params() "the API Key or the password required for authentication."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); - + def = this->add("printhost_cafile", coString); def->label = L("HTTPS CA File"); def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " @@ -117,9 +117,9 @@ void PrintConfigDef::init_fff_params() // Maximum extruder temperature, bumped to 1500 to support printing of glass. const int max_temp = 1500; - def = this->add("avoid_crossing_perimeters", coBool); + def = this->add("avoid_crossing_perimeters", coBool); def->label = L("Avoid crossing perimeters"); - def->tooltip = L("Optimize travel moves in order to minimize the crossing of perimeters. " + def->tooltip = L("Optimize travel moves in order to minimize the crossing of perimeters. " "This is mostly useful with Bowden extruders which suffer from oozing. " "This feature slows down both the print and the G-code generation."); def->mode = comExpert; @@ -178,7 +178,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Bridging angle override. If left to zero, the bridging angle will be calculated " "automatically. Otherwise the provided angle will be used for all bridges. " "Use 180° for zero angle."); - def->sidetext = L("°"); + def->sidetext = L("°"); def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); @@ -200,9 +200,9 @@ void PrintConfigDef::init_fff_params() "although default settings are usually good and you should experiment " "with cooling (use a fan) before tweaking this."); def->min = 0; - def->max = 2; + def->max = 2; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1)); + def->set_default_value(new ConfigOptionFloat(1)); def = this->add("bridge_speed", coFloat); def->label = L("Bridges"); @@ -532,7 +532,7 @@ void PrintConfigDef::init_fff_params() "check filament diameter and your firmware E steps."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats { 1. }); - + def = this->add("extrusion_width", coFloatOrPercent); def->label = L("Default extrusion width"); def->category = L("Extrusion Width"); @@ -678,7 +678,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("This string is edited by RammingDialog and contains ramming specific parameters."); def->mode = comExpert; def->set_default_value(new ConfigOptionStrings { "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0|" - " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" }); + " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" }); def = this->add("filament_unload_time", coFloats); def->label = L("Filament unload time"); @@ -744,7 +744,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("money/kg"); def->min = 0; def->set_default_value(new ConfigOptionFloats { 0. }); - + def = this->add("filament_settings_id", coStrings); def->set_default_value(new ConfigOptionStrings { "" }); def->cli = ConfigOptionDef::nocli; @@ -890,7 +890,7 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->max = max_temp; def->set_default_value(new ConfigOptionInts { 200 }); - + def = this->add("gap_fill_speed", coFloat); def->label = L("Gap fill"); def->category = L("Speed"); @@ -1073,85 +1073,85 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); - def = this->add("silent_mode", coBool); - def->label = L("Supports stealth mode"); - def->tooltip = L("The firmware supports stealth mode"); + def = this->add("silent_mode", coBool); + def->label = L("Supports stealth mode"); + def->tooltip = L("The firmware supports stealth mode"); def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(true)); + def->set_default_value(new ConfigOptionBool(true)); - const int machine_limits_opt_width = 7; - { - struct AxisDefault { - std::string name; - std::vector max_feedrate; - std::vector max_acceleration; - std::vector max_jerk; - }; - std::vector axes { - // name, max_feedrate, max_acceleration, max_jerk - { "x", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } }, - { "y", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } }, - { "z", { 12., 12. }, { 500., 200. }, { 0.2, 0.4 } }, - { "e", { 120., 120. }, { 10000., 5000. }, { 2.5, 2.5 } } - }; - for (const AxisDefault &axis : axes) { - std::string axis_upper = boost::to_upper_copy(axis.name); - // Add the machine feedrate limits for XYZE axes. (M203) - def = this->add("machine_max_feedrate_" + axis.name, coFloats); - def->full_label = (boost::format("Maximum feedrate %1%") % axis_upper).str(); - (void)L("Maximum feedrate X"); - (void)L("Maximum feedrate Y"); - (void)L("Maximum feedrate Z"); - (void)L("Maximum feedrate E"); - def->category = L("Machine limits"); - def->tooltip = (boost::format("Maximum feedrate of the %1% axis") % axis_upper).str(); - (void)L("Maximum feedrate of the X axis"); - (void)L("Maximum feedrate of the Y axis"); - (void)L("Maximum feedrate of the Z axis"); - (void)L("Maximum feedrate of the E axis"); - def->sidetext = L("mm/s"); - def->min = 0; - def->width = machine_limits_opt_width; + const int machine_limits_opt_width = 7; + { + struct AxisDefault { + std::string name; + std::vector max_feedrate; + std::vector max_acceleration; + std::vector max_jerk; + }; + std::vector axes { + // name, max_feedrate, max_acceleration, max_jerk + { "x", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } }, + { "y", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } }, + { "z", { 12., 12. }, { 500., 200. }, { 0.2, 0.4 } }, + { "e", { 120., 120. }, { 10000., 5000. }, { 2.5, 2.5 } } + }; + for (const AxisDefault &axis : axes) { + std::string axis_upper = boost::to_upper_copy(axis.name); + // Add the machine feedrate limits for XYZE axes. (M203) + def = this->add("machine_max_feedrate_" + axis.name, coFloats); + def->full_label = (boost::format("Maximum feedrate %1%") % axis_upper).str(); + (void)L("Maximum feedrate X"); + (void)L("Maximum feedrate Y"); + (void)L("Maximum feedrate Z"); + (void)L("Maximum feedrate E"); + def->category = L("Machine limits"); + def->tooltip = (boost::format("Maximum feedrate of the %1% axis") % axis_upper).str(); + (void)L("Maximum feedrate of the X axis"); + (void)L("Maximum feedrate of the Y axis"); + (void)L("Maximum feedrate of the Z axis"); + (void)L("Maximum feedrate of the E axis"); + def->sidetext = L("mm/s"); + def->min = 0; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats(axis.max_feedrate)); - // Add the machine acceleration limits for XYZE axes (M201) - def = this->add("machine_max_acceleration_" + axis.name, coFloats); - def->full_label = (boost::format("Maximum acceleration %1%") % axis_upper).str(); - (void)L("Maximum acceleration X"); - (void)L("Maximum acceleration Y"); - (void)L("Maximum acceleration Z"); - (void)L("Maximum acceleration E"); - def->category = L("Machine limits"); - def->tooltip = (boost::format("Maximum acceleration of the %1% axis") % axis_upper).str(); - (void)L("Maximum acceleration of the X axis"); - (void)L("Maximum acceleration of the Y axis"); - (void)L("Maximum acceleration of the Z axis"); - (void)L("Maximum acceleration of the E axis"); - def->sidetext = L("mm/s²"); - def->min = 0; - def->width = machine_limits_opt_width; + def->set_default_value(new ConfigOptionFloats(axis.max_feedrate)); + // Add the machine acceleration limits for XYZE axes (M201) + def = this->add("machine_max_acceleration_" + axis.name, coFloats); + def->full_label = (boost::format("Maximum acceleration %1%") % axis_upper).str(); + (void)L("Maximum acceleration X"); + (void)L("Maximum acceleration Y"); + (void)L("Maximum acceleration Z"); + (void)L("Maximum acceleration E"); + def->category = L("Machine limits"); + def->tooltip = (boost::format("Maximum acceleration of the %1% axis") % axis_upper).str(); + (void)L("Maximum acceleration of the X axis"); + (void)L("Maximum acceleration of the Y axis"); + (void)L("Maximum acceleration of the Z axis"); + (void)L("Maximum acceleration of the E axis"); + def->sidetext = L("mm/s²"); + def->min = 0; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats(axis.max_acceleration)); - // Add the machine jerk limits for XYZE axes (M205) - def = this->add("machine_max_jerk_" + axis.name, coFloats); - def->full_label = (boost::format("Maximum jerk %1%") % axis_upper).str(); - (void)L("Maximum jerk X"); - (void)L("Maximum jerk Y"); - (void)L("Maximum jerk Z"); - (void)L("Maximum jerk E"); - def->category = L("Machine limits"); - def->tooltip = (boost::format("Maximum jerk of the %1% axis") % axis_upper).str(); - (void)L("Maximum jerk of the X axis"); - (void)L("Maximum jerk of the Y axis"); - (void)L("Maximum jerk of the Z axis"); - (void)L("Maximum jerk of the E axis"); - def->sidetext = L("mm/s"); - def->min = 0; - def->width = machine_limits_opt_width; + def->set_default_value(new ConfigOptionFloats(axis.max_acceleration)); + // Add the machine jerk limits for XYZE axes (M205) + def = this->add("machine_max_jerk_" + axis.name, coFloats); + def->full_label = (boost::format("Maximum jerk %1%") % axis_upper).str(); + (void)L("Maximum jerk X"); + (void)L("Maximum jerk Y"); + (void)L("Maximum jerk Z"); + (void)L("Maximum jerk E"); + def->category = L("Machine limits"); + def->tooltip = (boost::format("Maximum jerk of the %1% axis") % axis_upper).str(); + (void)L("Maximum jerk of the X axis"); + (void)L("Maximum jerk of the Y axis"); + (void)L("Maximum jerk of the Z axis"); + (void)L("Maximum jerk of the E axis"); + def->sidetext = L("mm/s"); + def->min = 0; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats(axis.max_jerk)); - } - } + def->set_default_value(new ConfigOptionFloats(axis.max_jerk)); + } + } // M205 S... [mm/sec] def = this->add("machine_min_extruding_rate", coFloats); @@ -1160,9 +1160,9 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Minimum feedrate when extruding (M205 S)"); def->sidetext = L("mm/s"); def->min = 0; - def->width = machine_limits_opt_width; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats{ 0., 0. }); + def->set_default_value(new ConfigOptionFloats{ 0., 0. }); // M205 T... [mm/sec] def = this->add("machine_min_travel_rate", coFloats); @@ -1171,9 +1171,9 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Minimum travel feedrate (M205 T)"); def->sidetext = L("mm/s"); def->min = 0; - def->width = machine_limits_opt_width; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats{ 0., 0. }); + def->set_default_value(new ConfigOptionFloats{ 0., 0. }); // M204 S... [mm/sec^2] def = this->add("machine_max_acceleration_extruding", coFloats); @@ -1182,7 +1182,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Maximum acceleration when extruding (M204 S)"); def->sidetext = L("mm/s²"); def->min = 0; - def->width = machine_limits_opt_width; + def->width = machine_limits_opt_width; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); @@ -1193,7 +1193,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Maximum acceleration when retracting (M204 T)"); def->sidetext = L("mm/s²"); def->min = 0; - def->width = machine_limits_opt_width; + def->width = machine_limits_opt_width; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); @@ -1437,9 +1437,9 @@ void PrintConfigDef::init_fff_params() def->gui_flags = "serialized"; def->multiline = true; def->full_width = true; - def->height = 6; + def->height = 6; def->mode = comExpert; - def->set_default_value(new ConfigOptionStrings()); + def->set_default_value(new ConfigOptionStrings()); def = this->add("printer_model", coString); def->label = L("Printer type"); @@ -1471,7 +1471,7 @@ void PrintConfigDef::init_fff_params() def = this->add("print_settings_id", coString); def->set_default_value(new ConfigOptionString("")); def->cli = ConfigOptionDef::nocli; - + def = this->add("printer_settings_id", coString); def->set_default_value(new ConfigOptionString("")); def->cli = ConfigOptionDef::nocli; @@ -1511,7 +1511,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("%"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionPercents { 0. }); - + def = this->add("retract_layer_change", coBools); def->label = L("Retract on layer change"); def->tooltip = L("This flag enforces a retraction whenever a Z move is done."); @@ -1608,7 +1608,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Random")); def->enum_labels.push_back(L("Nearest")); def->enum_labels.push_back(L("Aligned")); - def->enum_labels.push_back(L("Rear")); + def->enum_labels.push_back(L("Rear")); def->mode = comSimple; def->set_default_value(new ConfigOptionEnum(spAligned)); @@ -1679,7 +1679,7 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(1)); - + def = this->add("slowdown_below_layer_time", coInts); def->label = L("Slow down if layer print time is below"); def->tooltip = L("If layer print time is estimated below this number of seconds, print moves " @@ -1775,7 +1775,7 @@ void PrintConfigDef::init_fff_params() def->label = L("Temperature variation"); def->tooltip = L("Temperature difference to be applied when an extruder is not active. " "Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."); - def->sidetext = "∆°C"; + def->sidetext = "∆°C"; def->min = -max_temp; def->max = max_temp; def->mode = comExpert; @@ -1817,7 +1817,7 @@ void PrintConfigDef::init_fff_params() def->label = L("Single Extruder Multi Material"); def->tooltip = L("The printer multiplexes filaments into a single hot end."); def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(false)); + def->set_default_value(new ConfigOptionBool(false)); def = this->add("single_extruder_multi_material_priming", coBool); def->label = L("Prime all printing extruders"); @@ -1879,8 +1879,8 @@ void PrintConfigDef::init_fff_params() // def->min = 0; def->enum_values.push_back("0"); def->enum_values.push_back("0.2"); - def->enum_labels.push_back(L("0 (soluble)")); - def->enum_labels.push_back(L("0.2 (detachable)")); + def->enum_labels.push_back(L("0 (soluble)")); + def->enum_labels.push_back(L("0.2 (detachable)")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.2)); @@ -1969,7 +1969,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("rectilinear"); def->enum_values.push_back("rectilinear-grid"); def->enum_values.push_back("honeycomb"); - def->enum_labels.push_back(L("Rectilinear")); + def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Rectilinear grid")); def->enum_labels.push_back(L("Honeycomb")); def->mode = comAdvanced; @@ -2031,7 +2031,7 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->max = max_temp; def->set_default_value(new ConfigOptionInts { 200 }); - + def = this->add("thin_walls", coBool); def->label = L("Detect thin walls"); def->category = L("Layers and Perimeters"); @@ -2051,7 +2051,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInt(threads > 0 ? threads : 2)); def->cli == ConfigOptionDef::nocli; } - + def = this->add("toolchange_gcode", coString); def->label = L("Tool change G-code"); def->tooltip = L("This custom code is inserted at every extruder change. If you don't leave this empty, you are " @@ -2243,45 +2243,45 @@ void PrintConfigDef::init_fff_params() // Declare retract values for filament profile, overriding the printer's extruder profile. for (const char *opt_key : { - // floats - "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_restart_extra", "retract_before_travel", - // bools - "retract_layer_change", "wipe", - // percents - "retract_before_wipe"}) { - auto it_opt = options.find(opt_key); - assert(it_opt != options.end()); - def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type); - def->label = it_opt->second.label; - def->full_label = it_opt->second.full_label; - def->tooltip = it_opt->second.tooltip; - def->sidetext = it_opt->second.sidetext; - def->mode = it_opt->second.mode; - switch (def->type) { - case coFloats : def->set_default_value(new ConfigOptionFloatsNullable (static_cast(it_opt->second.default_value.get())->values)); break; - case coPercents : def->set_default_value(new ConfigOptionPercentsNullable(static_cast(it_opt->second.default_value.get())->values)); break; - case coBools : def->set_default_value(new ConfigOptionBoolsNullable (static_cast(it_opt->second.default_value.get())->values)); break; - default: assert(false); - } + // floats + "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_restart_extra", "retract_before_travel", + // bools + "retract_layer_change", "wipe", + // percents + "retract_before_wipe"}) { + auto it_opt = options.find(opt_key); + assert(it_opt != options.end()); + def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type); + def->label = it_opt->second.label; + def->full_label = it_opt->second.full_label; + def->tooltip = it_opt->second.tooltip; + def->sidetext = it_opt->second.sidetext; + def->mode = it_opt->second.mode; + switch (def->type) { + case coFloats : def->set_default_value(new ConfigOptionFloatsNullable (static_cast(it_opt->second.default_value.get())->values)); break; + case coPercents : def->set_default_value(new ConfigOptionPercentsNullable(static_cast(it_opt->second.default_value.get())->values)); break; + case coBools : def->set_default_value(new ConfigOptionBoolsNullable (static_cast(it_opt->second.default_value.get())->values)); break; + default: assert(false); + } } } void PrintConfigDef::init_extruder_retract_keys() { - m_extruder_retract_keys = { - "deretract_speed", - "retract_before_travel", - "retract_before_wipe", - "retract_layer_change", - "retract_length", - "retract_lift", - "retract_lift_above", - "retract_lift_below", - "retract_restart_extra", - "retract_speed", - "wipe" - }; - assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end())); + m_extruder_retract_keys = { + "deretract_speed", + "retract_before_travel", + "retract_before_wipe", + "retract_layer_change", + "retract_length", + "retract_lift", + "retract_lift_above", + "retract_lift_below", + "retract_restart_extra", + "retract_speed", + "wipe" + }; + assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end())); } void PrintConfigDef::init_sla_params() @@ -2375,7 +2375,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloats( { 1., 1. } )); - + def = this->add("absolute_correction", coFloat); def->label = L("Printer absolute correction"); def->full_label = L("Printer absolute correction"); @@ -2383,7 +2383,7 @@ void PrintConfigDef::init_sla_params() "to the sign of the correction."); def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0.0)); - + def = this->add("gamma_correction", coFloat); def->label = L("Printer gamma correction"); def->full_label = L("Printer gamma correction"); @@ -2394,7 +2394,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(1.0)); - + // SLA Material settings. def = this->add("initial_layer_height", coFloat); @@ -2561,7 +2561,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(1.0)); - + def = this->add("support_base_safety_distance", coFloat); def->label = L("Support base safety distance"); def->category = L("Supports"); @@ -2676,14 +2676,14 @@ void PrintConfigDef::init_sla_params() def->set_default_value(new ConfigOptionFloat(50.0)); // This is disabled on the UI. I hope it will never be enabled. - def = this->add("pad_edge_radius", coFloat); - def->label = L("Pad edge radius"); - def->category = L("Pad"); -// def->tooltip = L(""); - def->sidetext = L("mm"); - def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1.0)); +// def = this->add("pad_edge_radius", coFloat); +// def->label = L("Pad edge radius"); +// def->category = L("Pad"); +//// def->tooltip = L(""); +// def->sidetext = L("mm"); +// def->min = 0; +// def->mode = comAdvanced; +// def->set_default_value(new ConfigOptionFloat(1.0)); def = this->add("pad_wall_slope", coFloat); def->label = L("Pad wall slope"); @@ -2695,7 +2695,14 @@ void PrintConfigDef::init_sla_params() def->max = 90; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(45.0)); - + + def = this->add("pad_zero_elevation", coBool); + def->label = L("Pad around object"); + def->category = L("Pad"); + def->tooltip = L("Create pad around object and ignore the support elevation"); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("pad_object_gap", coFloat); def->label = L("Pad object gap"); def->category = L("Pad"); @@ -2706,7 +2713,7 @@ void PrintConfigDef::init_sla_params() def->max = 10; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(1)); - + def = this->add("pad_object_connector_stride", coFloat); def->label = L("Pad object connector stride"); def->category = L("Pad"); @@ -2716,7 +2723,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(10)); - + def = this->add("pad_object_connector_width", coFloat); def->label = L("Pad object connector width"); def->category = L("Pad"); @@ -2726,7 +2733,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0.5)); - + def = this->add("pad_object_connector_penetration", coFloat); def->label = L("Pad object connector penetration"); def->category = L("Pad"); @@ -2747,7 +2754,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va if (opt_key == "bottom_layer_speed") opt_key = "first_layer_speed"; try { float v = boost::lexical_cast(value); - if (v != 0) + if (v != 0) value = boost::lexical_cast(v*100) + "%"; } catch (boost::bad_lexical_cast &) { value = "0"; @@ -2787,14 +2794,14 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } else if (opt_key == "octoprint_apikey") { opt_key = "printhost_apikey"; } - + // Ignore the following obsolete configuration keys: static std::set ignore = { "duplicate_x", "duplicate_y", "gcode_arcs", "multiply_x", "multiply_y", - "support_material_tool", "acceleration", "adjust_overhang_flow", + "support_material_tool", "acceleration", "adjust_overhang_flow", "standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid", - "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", - "seal_position", "vibration_limit", "bed_size", + "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", + "seal_position", "vibration_limit", "bed_size", "print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe" #ifndef HAS_PRESSURE_EQUALIZER , "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative" @@ -2805,7 +2812,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va opt_key = ""; return; } - + if (! print_config_def.has(opt_key)) { opt_key = ""; return; @@ -2845,10 +2852,10 @@ void DynamicPrintConfig::normalize() // this->option("support_material_interface_extruder", true)->setInt(extruder); } } - + if (!this->has("solid_infill_extruder") && this->has("infill_extruder")) this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt()); - + if (this->has("spiral_vase") && this->opt("spiral_vase", true)->value) { { // this should be actually done only on the spiral layers instead of all @@ -2866,8 +2873,8 @@ void DynamicPrintConfig::normalize() std::string DynamicPrintConfig::validate() { // Full print config is initialized from the defaults. - const ConfigOption *opt = this->option("printer_technology", false); - auto printer_technology = (opt == nullptr) ? ptFFF : static_cast(dynamic_cast(opt)->value); + const ConfigOption *opt = this->option("printer_technology", false); + auto printer_technology = (opt == nullptr) ? ptFFF : static_cast(dynamic_cast(opt)->value); switch (printer_technology) { case ptFFF: { @@ -2891,7 +2898,7 @@ double PrintConfig::min_object_distance(const ConfigBase *config) { double extruder_clearance_radius = config->option("extruder_clearance_radius")->getFloat(); double duplicate_distance = config->option("duplicate_distance")->getFloat(); - + // min object distance is max(duplicate_distance, clearance_radius) return (config->option("complete_objects")->getBool() && extruder_clearance_radius > duplicate_distance) ? extruder_clearance_radius @@ -2920,7 +2927,7 @@ std::string FullPrintConfig::validate() for (double nd : this->nozzle_diameter.values) if (nd < 0.005) return "Invalid value for --nozzle-diameter"; - + // --perimeters if (this->perimeters.value < 0) return "Invalid value for --perimeters"; @@ -2930,8 +2937,8 @@ std::string FullPrintConfig::validate() return "Invalid value for --top-solid-layers"; if (this->bottom_solid_layers < 0) return "Invalid value for --bottom-solid-layers"; - - if (this->use_firmware_retraction.value && + + if (this->use_firmware_retraction.value && this->gcode_flavor.value != gcfSmoothie && this->gcode_flavor.value != gcfRepRap && this->gcode_flavor.value != gcfMarlin && @@ -2943,15 +2950,15 @@ std::string FullPrintConfig::validate() for (unsigned char wipe : this->wipe.values) if (wipe) return "--use-firmware-retraction is not compatible with --wipe"; - + // --gcode-flavor if (! print_config_def.get("gcode_flavor")->has_enum_value(this->gcode_flavor.serialize())) return "Invalid value for --gcode-flavor"; - + // --fill-pattern if (! print_config_def.get("fill_pattern")->has_enum_value(this->fill_pattern.serialize())) return "Invalid value for --fill-pattern"; - + // --top-fill-pattern if (! print_config_def.get("top_fill_pattern")->has_enum_value(this->top_fill_pattern.serialize())) return "Invalid value for --top-fill-pattern"; @@ -2964,7 +2971,7 @@ std::string FullPrintConfig::validate() if (fabs(this->fill_density.value - 100.) < EPSILON && ! print_config_def.get("top_fill_pattern")->has_enum_value(this->fill_pattern.serialize())) return "The selected fill pattern is not supposed to work at 100% density"; - + // --infill-every-layers if (this->infill_every_layers < 1) return "Invalid value for --infill-every-layers"; @@ -2972,11 +2979,11 @@ std::string FullPrintConfig::validate() // --skirt-height if (this->skirt_height < -1) // -1 means as tall as the object return "Invalid value for --skirt-height"; - + // --bridge-flow-ratio if (this->bridge_flow_ratio <= 0) return "Invalid value for --bridge-flow-ratio"; - + // extruder clearance if (this->extruder_clearance_radius <= 0) return "Invalid value for --extruder-clearance-radius"; @@ -3008,7 +3015,7 @@ std::string FullPrintConfig::validate() if (this->support_material || this->support_material_enforce_layers > 0) return "Spiral vase mode is not compatible with support material"; } - + // extrusion widths { double max_nozzle_diameter = 0.; @@ -3065,7 +3072,7 @@ std::string FullPrintConfig::validate() if (out_of_range) return std::string("Value out of range: " + opt_key); } - + // The configuration is valid. return ""; } @@ -3088,20 +3095,20 @@ StaticPrintConfig::StaticCache SLAFullPrint CLIActionsConfigDef::CLIActionsConfigDef() { ConfigOptionDef* def; - + // Actions: def = this->add("export_obj", coBool); def->label = L("Export OBJ"); def->tooltip = L("Export the model(s) as OBJ."); def->set_default_value(new ConfigOptionBool(false)); - + /* def = this->add("export_svg", coBool); def->label = L("Export SVG"); def->tooltip = L("Slice the model and export solid slices as SVG."); def->set_default_value(new ConfigOptionBool(false)); */ - + def = this->add("export_sla", coBool); def->label = L("Export SLA"); def->tooltip = L("Slice the model and export SLA printing layers as PNG."); @@ -3150,12 +3157,12 @@ CLIActionsConfigDef::CLIActionsConfigDef() def->label = L("Help (SLA options)"); def->tooltip = L("Show the full list of SLA print configuration options."); def->set_default_value(new ConfigOptionBool(false)); - + def = this->add("info", coBool); def->label = L("Output Model Info"); def->tooltip = L("Write information about the model to the console."); def->set_default_value(new ConfigOptionBool(false)); - + def = this->add("save", coString); def->label = L("Save config file"); def->tooltip = L("Save configuration to the specified file."); @@ -3165,35 +3172,35 @@ CLIActionsConfigDef::CLIActionsConfigDef() CLITransformConfigDef::CLITransformConfigDef() { ConfigOptionDef* def; - + // Transform options: def = this->add("align_xy", coPoint); def->label = L("Align XY"); def->tooltip = L("Align the model to the given point."); def->set_default_value(new ConfigOptionPoint(Vec2d(100,100))); - + def = this->add("cut", coFloat); def->label = L("Cut"); def->tooltip = L("Cut model at the given Z."); def->set_default_value(new ConfigOptionFloat(0)); - + /* def = this->add("cut_grid", coFloat); def->label = L("Cut"); def->tooltip = L("Cut model in the XY plane into tiles of the specified max size."); def->set_default_value(new ConfigOptionPoint()); - + def = this->add("cut_x", coFloat); def->label = L("Cut"); def->tooltip = L("Cut model at the given X."); def->set_default_value(new ConfigOptionFloat(0)); - + def = this->add("cut_y", coFloat); def->label = L("Cut"); def->tooltip = L("Cut model at the given Y."); def->set_default_value(new ConfigOptionFloat(0)); */ - + def = this->add("center", coPoint); def->label = L("Center"); def->tooltip = L("Center the print around the given center."); @@ -3202,12 +3209,12 @@ CLITransformConfigDef::CLITransformConfigDef() def = this->add("dont_arrange", coBool); def->label = L("Don't arrange"); def->tooltip = L("Do not rearrange the given models before merging and keep their original XY coordinates."); - + def = this->add("duplicate", coInt); def->label = L("Duplicate"); def->tooltip =L("Multiply copies by this factor."); def->min = 1; - + def = this->add("duplicate_grid", coPoint); def->label = L("Duplicate by grid"); def->tooltip = L("Multiply copies by creating a grid."); @@ -3220,22 +3227,22 @@ CLITransformConfigDef::CLITransformConfigDef() def = this->add("repair", coBool); def->label = L("Repair"); def->tooltip = L("Try to repair any non-manifold meshes (this option is implicitly added whenever we need to slice the model to perform the requested action)."); - + def = this->add("rotate", coFloat); def->label = L("Rotate"); def->tooltip = L("Rotation angle around the Z axis in degrees."); def->set_default_value(new ConfigOptionFloat(0)); - + def = this->add("rotate_x", coFloat); def->label = L("Rotate around X"); def->tooltip = L("Rotation angle around the X axis in degrees."); def->set_default_value(new ConfigOptionFloat(0)); - + def = this->add("rotate_y", coFloat); def->label = L("Rotate around Y"); def->tooltip = L("Rotation angle around the Y axis in degrees."); def->set_default_value(new ConfigOptionFloat(0)); - + def = this->add("scale", coFloatOrPercent); def->label = L("Scale"); def->tooltip = L("Scaling factor or percentage."); @@ -3244,7 +3251,7 @@ CLITransformConfigDef::CLITransformConfigDef() def = this->add("split", coBool); def->label = L("Split"); def->tooltip = L("Detect unconnected parts in the given model(s) and split them into separate objects."); - + def = this->add("scale_to_fit", coPoint3); def->label = L("Scale to Fit"); def->tooltip = L("Scale to fit the given volume."); @@ -3254,26 +3261,26 @@ CLITransformConfigDef::CLITransformConfigDef() CLIMiscConfigDef::CLIMiscConfigDef() { ConfigOptionDef* def; - + def = this->add("ignore_nonexistent_config", coBool); def->label = L("Ignore non-existent config files"); def->tooltip = L("Do not fail if a file supplied to --load does not exist."); - + def = this->add("load", coStrings); def->label = L("Load config file"); def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); - + def = this->add("output", coString); def->label = L("Output File"); def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file)."); def->cli = "output|o"; -/* +/* def = this->add("autosave", coString); def->label = L("Autosave"); def->tooltip = L("Automatically export current configuration to the specified file."); */ - + def = this->add("datadir", coString); def->label = L("Data directory"); def->tooltip = L("Load and store settings at the given directory. This is useful for maintaining different profiles or including configurations from a network storage."); @@ -3299,15 +3306,15 @@ DynamicPrintAndCLIConfig::PrintAndCLIConfigDef DynamicPrintAndCLIConfig::s_def; void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std::string &value) const { - if (cli_actions_config_def .options.find(opt_key) == cli_actions_config_def .options.end() && - cli_transform_config_def.options.find(opt_key) == cli_transform_config_def.options.end() && - cli_misc_config_def .options.find(opt_key) == cli_misc_config_def .options.end()) { - PrintConfigDef::handle_legacy(opt_key, value); - } + if (cli_actions_config_def .options.find(opt_key) == cli_actions_config_def .options.end() && + cli_transform_config_def.options.find(opt_key) == cli_transform_config_def.options.end() && + cli_misc_config_def .options.find(opt_key) == cli_misc_config_def .options.end()) { + PrintConfigDef::handle_legacy(opt_key, value); + } } } #include CEREAL_REGISTER_TYPE(Slic3r::DynamicPrintConfig) -CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::DynamicConfig, Slic3r::DynamicPrintConfig) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::DynamicConfig, Slic3r::DynamicPrintConfig) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f2d0775fa0..081f670e15 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -740,7 +740,7 @@ protected: class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig { STATIC_PRINT_CONFIG_CACHE_DERIVED(PrintConfig) - PrintConfig() : MachineEnvelopeConfig(0), GCodeConfig(0) { initialize_cache(); *this = s_cache_PrintConfig.defaults(); } + PrintConfig() : MachineEnvelopeConfig(0), GCodeConfig(0) { initialize_cache(); *this = s_cache_PrintConfig.defaults(); } public: double min_object_distance() const; static double min_object_distance(const ConfigBase *config); @@ -812,7 +812,7 @@ public: ConfigOptionFloat z_offset; protected: - PrintConfig(int) : MachineEnvelopeConfig(1), GCodeConfig(1) {} + PrintConfig(int) : MachineEnvelopeConfig(1), GCodeConfig(1) {} void initialize(StaticCacheBase &cache, const char *base_ptr) { this->MachineEnvelopeConfig::initialize(cache, base_ptr); @@ -991,7 +991,7 @@ public: // The height of the pillar base cone in mm. ConfigOptionFloat support_base_height /*= 1.0*/; - + // The minimum distance of the pillar base from the model in mm. ConfigOptionFloat support_base_safety_distance; /*= 1.0*/; @@ -1007,7 +1007,7 @@ public: // The elevation in Z direction upwards. This is the space between the pad // and the model object's bounding box bottom. Units in mm. ConfigOptionFloat support_object_elevation /*= 5.0*/; - + /////// Following options influence automatic support points placement: ConfigOptionInt support_points_density_relative; ConfigOptionFloat support_points_minimal_distance; @@ -1028,11 +1028,11 @@ public: ConfigOptionFloat pad_max_merge_distance /*= 50*/; // The smoothing radius of the pad edges - ConfigOptionFloat pad_edge_radius /*= 1*/; + // ConfigOptionFloat pad_edge_radius /*= 1*/; // The slope of the pad wall... ConfigOptionFloat pad_wall_slope; - + // ///////////////////////////////////////////////////////////////////////// // Zero elevation mode parameters: // - The object pad will be derived from the the model geometry. @@ -1040,16 +1040,19 @@ public: // according to the support_base_safety_distance parameter. // - The two pads will be connected with tiny connector sticks // ///////////////////////////////////////////////////////////////////////// - + + // Disable the elevation (ignore its value) and use the zero elevation mode + ConfigOptionBool pad_zero_elevation; + // This is the gap between the object bottom and the generated pad ConfigOptionFloat pad_object_gap; - + // How far to place the connector sticks on the object pad perimeter ConfigOptionFloat pad_object_connector_stride; - + // The width of the connectors sticks ConfigOptionFloat pad_object_connector_width; - + // How much should the tiny connectors penetrate into the model body ConfigOptionFloat pad_object_connector_penetration; @@ -1080,8 +1083,9 @@ protected: OPT_PTR(pad_wall_thickness); OPT_PTR(pad_wall_height); OPT_PTR(pad_max_merge_distance); - OPT_PTR(pad_edge_radius); + // OPT_PTR(pad_edge_radius); OPT_PTR(pad_wall_slope); + OPT_PTR(pad_zero_elevation); OPT_PTR(pad_object_gap); OPT_PTR(pad_object_connector_stride); OPT_PTR(pad_object_connector_width); @@ -1205,8 +1209,8 @@ extern const CLIMiscConfigDef cli_misc_config_def; class DynamicPrintAndCLIConfig : public DynamicPrintConfig { public: - DynamicPrintAndCLIConfig() {} - DynamicPrintAndCLIConfig(const DynamicPrintAndCLIConfig &other) : DynamicPrintConfig(other) {} + DynamicPrintAndCLIConfig() {} + DynamicPrintAndCLIConfig(const DynamicPrintAndCLIConfig &other) : DynamicPrintConfig(other) {} // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. const ConfigDef* def() const override { return &s_def; } @@ -1227,7 +1231,7 @@ private: this->options.insert(cli_transform_config_def.options.begin(), cli_transform_config_def.options.end()); this->options.insert(cli_misc_config_def.options.begin(), cli_misc_config_def.options.end()); for (const auto &kvp : this->options) - this->by_serialization_key_ordinal[kvp.second.serialization_key_ordinal] = &kvp.second; + this->by_serialization_key_ordinal[kvp.second.serialization_key_ordinal] = &kvp.second; } // Do not release the default values, they are handled by print_config_def & cli_actions_config_def / cli_transform_config_def / cli_misc_config_def. ~PrintAndCLIConfigDef() { this->options.clear(); } @@ -1239,36 +1243,36 @@ private: // Serialization through the Cereal library namespace cereal { - // Let cereal know that there are load / save non-member functions declared for DynamicPrintConfig, ignore serialize / load / save from parent class DynamicConfig. - template struct specialize {}; + // Let cereal know that there are load / save non-member functions declared for DynamicPrintConfig, ignore serialize / load / save from parent class DynamicConfig. + template struct specialize {}; - template void load(Archive& archive, Slic3r::DynamicPrintConfig &config) - { - size_t cnt; - archive(cnt); - config.clear(); - for (size_t i = 0; i < cnt; ++ i) { - size_t serialization_key_ordinal; - archive(serialization_key_ordinal); - assert(serialization_key_ordinal > 0); - auto it = Slic3r::print_config_def.by_serialization_key_ordinal.find(serialization_key_ordinal); - assert(it != Slic3r::print_config_def.by_serialization_key_ordinal.end()); - config.set_key_value(it->second->opt_key, it->second->load_option_from_archive(archive)); - } - } + template void load(Archive& archive, Slic3r::DynamicPrintConfig &config) + { + size_t cnt; + archive(cnt); + config.clear(); + for (size_t i = 0; i < cnt; ++ i) { + size_t serialization_key_ordinal; + archive(serialization_key_ordinal); + assert(serialization_key_ordinal > 0); + auto it = Slic3r::print_config_def.by_serialization_key_ordinal.find(serialization_key_ordinal); + assert(it != Slic3r::print_config_def.by_serialization_key_ordinal.end()); + config.set_key_value(it->second->opt_key, it->second->load_option_from_archive(archive)); + } + } - template void save(Archive& archive, const Slic3r::DynamicPrintConfig &config) - { - size_t cnt = config.size(); - archive(cnt); - for (auto it = config.cbegin(); it != config.cend(); ++it) { - const Slic3r::ConfigOptionDef* optdef = Slic3r::print_config_def.get(it->first); - assert(optdef != nullptr); - assert(optdef->serialization_key_ordinal > 0); - archive(optdef->serialization_key_ordinal); - optdef->save_option_to_archive(archive, it->second.get()); - } - } + template void save(Archive& archive, const Slic3r::DynamicPrintConfig &config) + { + size_t cnt = config.size(); + archive(cnt); + for (auto it = config.cbegin(); it != config.cend(); ++it) { + const Slic3r::ConfigOptionDef* optdef = Slic3r::print_config_def.get(it->first); + assert(optdef != nullptr); + assert(optdef->serialization_key_ordinal > 0); + archive(optdef->serialization_key_ordinal); + optdef->save_option_to_archive(archive, it->second.get()); + } + } } #endif diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 45f8a0c83a..d885ed4194 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -52,7 +52,7 @@ const std::array OBJ_STEP_LEVELS = }; // Object step to status label. The labels are localized at the time of calling, thus supporting language switching. -std::string OBJ_STEP_LABELS(size_t idx) +std::string OBJ_STEP_LABELS(size_t idx) { switch (idx) { case slaposObjectSlice: return L("Slicing model"); @@ -365,7 +365,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con // Synchronize Object's config. bool object_config_changed = model_object.config != model_object_new.config; if (object_config_changed) - static_cast(model_object.config) = static_cast(model_object_new.config); + static_cast(model_object.config) = static_cast(model_object_new.config); if (! object_diff.empty() || object_config_changed) { SLAPrintObjectConfig new_config = m_default_object_config; normalize_and_apply_config(new_config, model_object.config); @@ -424,10 +424,10 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con print_object->set_trafo(sla_trafo(*this, model_object), model_object.instances.front()->is_left_handed()); print_object->set_instances(std::move(new_instances)); - - SLAPrintObjectConfig new_config = m_default_object_config; - normalize_and_apply_config(new_config, model_object.config); - print_object->config_apply(new_config, true); + + SLAPrintObjectConfig new_config = m_default_object_config; + normalize_and_apply_config(new_config, model_object.config); + print_object->config_apply(new_config, true); print_objects_new.emplace_back(print_object); new_objects = true; } @@ -446,7 +446,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con if (new_objects) update_apply_status(false); } - + if(m_objects.empty()) { m_printer.release(); m_printer_input.clear(); @@ -569,6 +569,16 @@ std::string SLAPrint::output_filename(const std::string &filename_base) const } namespace { + +bool is_zero_elevation(const SLAPrintObjectConfig &c) { + bool en_implicit = c.support_object_elevation.getFloat() <= EPSILON && + c.pad_enable.getBool() && c.supports_enable.getBool(); + bool en_explicit = c.pad_zero_elevation.getBool() && + c.supports_enable.getBool(); + + return en_implicit || en_explicit; +} + // Compile the argument for support creation from the static print config. sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { sla::SupportConfig scfg; @@ -577,7 +587,8 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat(); scfg.head_penetration_mm = c.support_head_penetration.getFloat(); scfg.head_width_mm = c.support_head_width.getFloat(); - scfg.object_elevation_mm = c.support_object_elevation.getFloat(); + scfg.object_elevation_mm = is_zero_elevation(c) ? + 0. : c.support_object_elevation.getFloat(); scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat(); @@ -596,16 +607,15 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { scfg.pillar_base_safety_distance_mm = c.support_base_safety_distance.getFloat() < EPSILON ? scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); - + return scfg; } sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { sla::PoolConfig::EmbedObject ret; - - ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && - c.pad_enable.getBool() && c.supports_enable.getBool(); - + + ret.enabled = is_zero_elevation(c); + if(ret.enabled) { ret.object_gap_mm = c.pad_object_gap.getFloat(); ret.stick_width_mm = c.pad_object_connector_width.getFloat(); @@ -613,7 +623,7 @@ sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { ret.stick_penetration_mm = c.pad_object_connector_penetration .getFloat(); } - + return ret; } @@ -622,16 +632,16 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat(); pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0; - + // We do not support radius for now pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat(); - + pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat(); pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); // set builtin pad implicitly ON pcfg.embed_object = builtin_pad_cfg(c); - + return pcfg; } @@ -657,12 +667,14 @@ std::string SLAPrint::validate() const cfg.head_width_mm + 2 * cfg.head_back_radius_mm - cfg.head_penetration_mm; - + double elv = cfg.object_elevation_mm; if(supports_en && elv > EPSILON && elv < pinhead_width ) - return L("Elevation is too low for object."); - + return L( + "Elevation is too low for object. Use the \"Pad around " + "obect\" feature to print the object without elevation."); + sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config()); if(supports_en && builtinpad.enabled && cfg.pillar_base_safety_distance_mm < builtinpad.object_gap_mm) { @@ -740,15 +752,15 @@ void SLAPrint::process() coord_t maxZs = scaled(maxZ); po.m_slice_index.clear(); - + size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs); po.m_slice_index.reserve(cap); - + po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh); for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) po.m_slice_index.emplace_back(h, unscaled(h) - lh / 2.f, lh); - + // Just get the first record that is form the model: auto slindex_it = po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z))); @@ -781,9 +793,9 @@ void SLAPrint::process() { // We apply the printer correction offset here. if(clpr_offs != 0) - po.m_model_slices[id] = + po.m_model_slices[id] = offset_ex(po.m_model_slices[id], float(clpr_offs)); - + mit->set_model_slice_idx(po, id); ++mit; } @@ -876,10 +888,10 @@ void SLAPrint::process() // removed them on purpose. No calculation will be done. po.m_supportdata->support_points = po.transformed_support_points(); } - + // If the zero elevation mode is engaged, we have to filter out all the // points that are on the bottom of the object - if (po.config().support_object_elevation.getFloat() <= EPSILON) { + if (is_zero_elevation(po.config())) { double gnd = po.m_supportdata->emesh.ground_level(); auto & pts = po.m_supportdata->support_points; double tolerance = po.config().pad_enable.getBool() @@ -894,7 +906,7 @@ void SLAPrint::process() double diff = std::abs(gnd - double(sp.pos(Z))); return diff <= tolerance; }); - + // erase all elements after the new end pts.erase(endit, pts.end()); } @@ -904,7 +916,7 @@ void SLAPrint::process() auto support_tree = [this, ostepd](SLAPrintObject& po) { if(!po.m_supportdata) return; - + sla::PoolConfig pcfg = make_pool_config(po.m_config); if (pcfg.embed_object) @@ -912,11 +924,11 @@ void SLAPrint::process() pcfg.min_wall_thickness_mm); if(!po.m_config.supports_enable.getBool()) { - + // Generate empty support tree. It can still host a pad po.m_supportdata->support_tree_ptr.reset( new SLASupportTree(po.m_supportdata->emesh.ground_level())); - + return; } @@ -940,7 +952,7 @@ void SLAPrint::process() ctl.stopcondition = [this](){ return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; - + po.m_supportdata->support_tree_ptr.reset( new SLASupportTree(po.m_supportdata->support_points, po.m_supportdata->emesh, scfg, ctl)); @@ -1040,7 +1052,7 @@ void SLAPrint::process() if(clpr_offs != 0) sd->support_slices[i] = offset_ex(sd->support_slices[i], float(clpr_offs)); - + po.m_slice_index[i].set_support_slice_idx(po, i); } @@ -1268,7 +1280,7 @@ void SLAPrint::process() const SLAPrintObject *po = record.print_obj(); const ExPolygons &modelslices = record.get_slice(soModel); - + bool is_lefth = record.print_obj()->is_left_handed(); if (!modelslices.empty()) { ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth); @@ -1276,7 +1288,7 @@ void SLAPrint::process() } const ExPolygons &supportslices = record.get_slice(soSupport); - + if (!supportslices.empty()) { ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth); for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp)); @@ -1369,8 +1381,8 @@ void SLAPrint::process() { // create a raster printer for the current print parameters double layerh = m_default_object_config.layer_height.getFloat(); - m_printer.reset(new SLAPrinter(m_printer_config, - m_material_config, + m_printer.reset(new SLAPrinter(m_printer_config, + m_material_config, layerh)); } @@ -1647,6 +1659,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector= 2) { corr(X) *= material_config().material_correction.values[0]; @@ -1925,7 +1943,7 @@ void SLAPrint::StatusReporter::operator()(SLAPrint & p, BOOST_LOG_TRIVIAL(info) << st << "% " << msg << (logmsg.empty() ? "" : ": ") << logmsg << log_memory_info(); - + p.set_status(int(std::round(st)), msg, flags); } diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index fa8b5baeec..833da238a5 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -162,11 +162,11 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem if (from_pre_map != pre_family_model_map.end()) { model.family = from_pre_map->second; } } #if 0 - // Remove SLA printers from the initial alpha. - if (model.technology == ptSLA) - continue; + // Remove SLA printers from the initial alpha. + if (model.technology == ptSLA) + continue; #endif - section.second.get("variants", ""); + section.second.get("variants", ""); const auto variants_field = section.second.get("variants", ""); std::vector variants; if (Slic3r::unescape_strings_cstyle(variants_field, variants)) { @@ -209,7 +209,7 @@ const std::string& Preset::suffix_modified() void Preset::update_suffix_modified() { - g_suffix_modified = (" (" + _(L("modified")) + ")").ToUTF8().data(); + g_suffix_modified = (" (" + _(L("modified")) + ")").ToUTF8().data(); } // Remove an optional "(modified)" suffix from a name. // This converts a UI name to a unique preset identifier. @@ -224,8 +224,8 @@ void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extr { const auto &defaults = FullPrintConfig::defaults(); for (const std::string &key : Preset::nozzle_options()) { - if (key == "default_filament_profile") - continue; + if (key == "default_filament_profile") + continue; auto *opt = config.option(key, false); assert(opt != nullptr); assert(opt->is_vector()); @@ -247,8 +247,8 @@ void Preset::normalize(DynamicPrintConfig &config) size_t n = (nozzle_diameter == nullptr) ? 1 : nozzle_diameter->values.size(); const auto &defaults = FullPrintConfig::defaults(); for (const std::string &key : Preset::filament_options()) { - if (key == "compatible_prints" || key == "compatible_printers") - continue; + if (key == "compatible_prints" || key == "compatible_printers") + continue; auto *opt = config.option(key, false); /*assert(opt != nullptr); assert(opt->is_vector());*/ @@ -307,7 +307,7 @@ bool Preset::is_compatible_with_print(const Preset &active_print) const } } return this->is_default || active_print.name.empty() || ! has_compatible_prints || - std::find(compatible_prints->values.begin(), compatible_prints->values.end(), active_print.name) != + std::find(compatible_prints->values.begin(), compatible_prints->values.end(), active_print.name) != compatible_prints->values.end(); } @@ -326,7 +326,7 @@ bool Preset::is_compatible_with_printer(const Preset &active_printer, const Dyna } } return this->is_default || active_printer.name.empty() || ! has_compatible_printers || - std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.name) != + std::find(compatible_printers->values.begin(), compatible_printers->values.end(), active_printer.name) != compatible_printers->values.end(); } @@ -334,9 +334,9 @@ bool Preset::is_compatible_with_printer(const Preset &active_printer) const { DynamicPrintConfig config; config.set_key_value("printer_preset", new ConfigOptionString(active_printer.name)); - const ConfigOption *opt = active_printer.config.option("nozzle_diameter"); - if (opt) - config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); + const ConfigOption *opt = active_printer.config.option("nozzle_diameter"); + if (opt) + config.set_key_value("num_extruders", new ConfigOptionInt((int)static_cast(opt)->values.size())); return this->is_compatible_with_printer(active_printer, &config); } @@ -358,40 +358,40 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) } const std::vector& Preset::print_options() -{ +{ static std::vector s_opts { - "layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "top_solid_layers", "bottom_solid_layers", - "extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs", + "layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "top_solid_layers", "bottom_solid_layers", + "extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs", "seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", - "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", - "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "max_print_speed", - "max_volumetric_speed", + "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", + "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "max_print_speed", + "max_volumetric_speed", #ifdef HAS_PRESSURE_EQUALIZER - "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", + "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", #endif /* HAS_PRESSURE_EQUALIZER */ - "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", + "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", - "bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", + "bridge_speed", "gap_fill_speed", "travel_speed", "first_layer_speed", "perimeter_acceleration", "infill_acceleration", "bridge_acceleration", "first_layer_acceleration", "default_acceleration", "skirts", "skirt_distance", "skirt_height", - "min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", - "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", - "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", - "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", - "support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "extruder_clearance_radius", - "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder", - "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", - "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", - "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", - "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", + "min_skirt_length", "brim_width", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers", + "raft_layers", "support_material_pattern", "support_material_with_sheath", "support_material_spacing", + "support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", + "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance", + "support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "extruder_clearance_radius", + "extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder", + "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", + "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", + "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", + "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", - "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "single_extruder_multi_material_priming", + "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "single_extruder_multi_material_priming", "compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; } const std::vector& Preset::filament_options() -{ +{ static std::vector s_opts { "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", "extrusion_multiplier", "filament_density", "filament_cost", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time", @@ -401,16 +401,16 @@ const std::vector& Preset::filament_options() "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode", // Retract overrides - "filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel", - "filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe", - // Profile compatibility + "filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel", + "filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe", + // Profile compatibility "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; } const std::vector& Preset::printer_options() -{ +{ static std::vector s_opts; if (s_opts.empty()) { s_opts = { @@ -420,20 +420,20 @@ const std::vector& Preset::printer_options() "host_type", "print_host", "printhost_apikey", "printhost_cafile", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction", - "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", + "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits", "remaining_times", "silent_mode", "machine_max_acceleration_extruding", "machine_max_acceleration_retracting", - "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", - "machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e", - "machine_min_extruding_rate", "machine_min_travel_rate", - "machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e" + "machine_max_acceleration_x", "machine_max_acceleration_y", "machine_max_acceleration_z", "machine_max_acceleration_e", + "machine_max_feedrate_x", "machine_max_feedrate_y", "machine_max_feedrate_z", "machine_max_feedrate_e", + "machine_min_extruding_rate", "machine_min_travel_rate", + "machine_max_jerk_x", "machine_max_jerk_y", "machine_max_jerk_z", "machine_max_jerk_e" }; s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end()); } return s_opts; } -// The following nozzle options of a printer profile will be adjusted to match the size +// The following nozzle options of a printer profile will be adjusted to match the size // of the nozzle_diameter vector. const std::vector& Preset::nozzle_options() { @@ -442,14 +442,14 @@ const std::vector& Preset::nozzle_options() "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", - "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", + "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", "default_filament_profile" }; return s_opts; } const std::vector& Preset::sla_print_options() -{ +{ static std::vector s_opts; if (s_opts.empty()) { s_opts = { @@ -477,16 +477,17 @@ const std::vector& Preset::sla_print_options() "pad_wall_thickness", "pad_wall_height", "pad_max_merge_distance", - "pad_edge_radius", + // "pad_edge_radius", "pad_wall_slope", "pad_object_gap", + "pad_zero_elevation", "pad_object_connector_stride", "pad_object_connector_width", "pad_object_connector_penetration", - "output_filename_format", + "output_filename_format", "default_sla_print_profile", "compatible_printers", - "compatible_printers_condition", + "compatible_printers_condition", "inherits" }; } @@ -494,7 +495,7 @@ const std::vector& Preset::sla_print_options() } const std::vector& Preset::sla_material_options() -{ +{ static std::vector s_opts; if (s_opts.empty()) { s_opts = { @@ -503,7 +504,7 @@ const std::vector& Preset::sla_material_options() "material_correction", "material_notes", "default_sla_material_profile", - "compatible_prints", "compatible_prints_condition", + "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits" }; } @@ -511,7 +512,7 @@ const std::vector& Preset::sla_material_options() } const std::vector& Preset::sla_printer_options() -{ +{ static std::vector s_opts; if (s_opts.empty()) { s_opts = { @@ -539,7 +540,7 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vectoradd_default_preset(keys, defaults, default_name); @@ -552,8 +553,8 @@ PresetCollection::~PresetCollection() m_bitmap_main_frame = nullptr; delete m_bitmap_add; m_bitmap_add = nullptr; - delete m_bitmap_cache; - m_bitmap_cache = nullptr; + delete m_bitmap_cache; + m_bitmap_cache = nullptr; } void PresetCollection::reset(bool delete_files) @@ -575,8 +576,8 @@ void PresetCollection::add_default_preset(const std::vector &keys, { // Insert just the default preset. m_presets.emplace_back(Preset(this->type(), preset_name, true)); - m_presets.back().config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); - m_presets.back().loaded = true; + m_presets.back().config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); + m_presets.back().loaded = true; ++ m_num_default_presets; } @@ -584,13 +585,13 @@ void PresetCollection::add_default_preset(const std::vector &keys, // Throws an exception on error. void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) { - boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred(); - m_dir_path = dir.string(); + boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred(); + m_dir_path = dir.string(); std::string errors_cummulative; - // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. - // (see the "Preset already present, not loading" message). - std::deque presets_loaded; - for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) + // Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken. + // (see the "Preset already present, not loading" message). + std::deque presets_loaded; + for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) if (Slic3r::is_ini_file(dir_entry)) { std::string name = dir_entry.path().filename().string(); // Remove the .ini suffix. @@ -609,28 +610,28 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri DynamicPrintConfig config; config.load_from_ini(preset.file); // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. - const Preset &default_preset = this->default_preset_for(config); + const Preset &default_preset = this->default_preset_for(config); preset.config = default_preset.config; preset.config.apply(std::move(config)); Preset::normalize(preset.config); // Report configuration fields, which are misplaced into a wrong group. - std::string incorrect_keys = Preset::remove_invalid_keys(config, default_preset.config); + std::string incorrect_keys = Preset::remove_invalid_keys(config, default_preset.config); if (! incorrect_keys.empty()) BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << preset.file << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; preset.loaded = true; } catch (const std::ifstream::failure &err) { - throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); + throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); } catch (const std::runtime_error &err) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); + throw std::runtime_error(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); } - presets_loaded.emplace_back(preset); + presets_loaded.emplace_back(preset); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); errors_cummulative += "\n"; - } + } } - m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end())); + m_presets.insert(m_presets.end(), std::make_move_iterator(presets_loaded.begin()), std::make_move_iterator(presets_loaded.end())); std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); this->select_preset(first_visible_idx()); if (! errors_cummulative.empty()) @@ -651,8 +652,8 @@ static bool profile_print_params_same(const DynamicPrintConfig &cfg1, const Dyna t_config_option_keys diff = cfg1.diff(cfg2); // Following keys are used by the UI, not by the slicing core, therefore they are not important // when comparing profiles for equality. Ignore them. - for (const char *key : { "compatible_prints", "compatible_prints_condition", - "compatible_printers", "compatible_printers_condition", "inherits", + for (const char *key : { "compatible_prints", "compatible_prints_condition", + "compatible_printers", "compatible_printers_condition", "inherits", "print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id", "printer_model", "printer_variant", "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile", "printhost_apikey", "printhost_cafile" }) @@ -663,7 +664,7 @@ static bool profile_print_params_same(const DynamicPrintConfig &cfg1, const Dyna // Load a preset from an already parsed config file, insert it into the sorted sequence of presets // and select it, losing previous modifications. -// In case +// In case Preset& PresetCollection::load_external_preset( // Path to the profile source file (a G-code, an AMF or 3MF file, a config file) const std::string &path, @@ -681,7 +682,7 @@ Preset& PresetCollection::load_external_preset( cfg.apply_only(config, cfg.keys(), true); // Is there a preset already loaded with the name stored inside the config? std::deque::iterator it = this->find_preset_internal(original_name); - bool found = it != m_presets.end() && it->name == original_name; + bool found = it != m_presets.end() && it->name == original_name; if (found && profile_print_params_same(it->config, cfg)) { // The preset exists and it matches the values stored inside config. if (select) @@ -706,7 +707,7 @@ Preset& PresetCollection::load_external_preset( suffix = " (" + std::to_string(idx) + ")"; } else { if (idx == 0) - suffix = " (" + original_name + ")"; + suffix = " (" + original_name + ")"; else suffix = " (" + original_name + "-" + std::to_string(idx) + ")"; } @@ -751,8 +752,8 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string void PresetCollection::save_current_preset(const std::string &new_name) { - // 1) Find the preset with a new_name or create a new one, - // initialize it with the edited config. + // 1) Find the preset with a new_name or create a new one, + // initialize it with the edited config. auto it = this->find_preset_internal(new_name); if (it != m_presets.end() && it->name == new_name) { // Preset with the same name found. @@ -762,15 +763,15 @@ void PresetCollection::save_current_preset(const std::string &new_name) return; // Overwriting an existing preset. preset.config = std::move(m_edited_preset.config); - // The newly saved preset will be activated -> make it visible. - preset.is_visible = true; + // The newly saved preset will be activated -> make it visible. + preset.is_visible = true; } else { // Creating a new preset. - Preset &preset = *m_presets.insert(it, m_edited_preset); + Preset &preset = *m_presets.insert(it, m_edited_preset); std::string &inherits = preset.inherits(); std::string old_name = preset.name; preset.name = new_name; - preset.file = this->path_from_name(new_name); + preset.file = this->path_from_name(new_name); preset.vendor = nullptr; if (preset.is_system) { // Inheriting from a system preset. @@ -779,30 +780,30 @@ void PresetCollection::save_current_preset(const std::string &new_name) // Inheriting from a user preset. Link the new preset to the old preset. // inherits = old_name; } else { - // Inherited from a user preset. Just maintain the "inherited" flag, + // Inherited from a user preset. Just maintain the "inherited" flag, // meaning it will inherit from either the system preset, or the inherited user preset. } preset.is_default = false; preset.is_system = false; preset.is_external = false; - // The newly saved preset will be activated -> make it visible. - preset.is_visible = true; - } - // 2) Activate the saved preset. - this->select_preset_by_name(new_name, true); - // 2) Store the active preset to disk. - this->get_selected_preset().save(); + // The newly saved preset will be activated -> make it visible. + preset.is_visible = true; + } + // 2) Activate the saved preset. + this->select_preset_by_name(new_name, true); + // 2) Store the active preset to disk. + this->get_selected_preset().save(); } bool PresetCollection::delete_current_preset() { const Preset &selected = this->get_selected_preset(); - if (selected.is_default) - return false; - if (! selected.is_external && ! selected.is_system) { - // Erase the preset file. - boost::nowide::remove(selected.file.c_str()); - } + if (selected.is_default) + return false; + if (! selected.is_external && ! selected.is_system) { + // Erase the preset file. + boost::nowide::remove(selected.file.c_str()); + } // Remove the preset from the list. m_presets.erase(m_presets.begin() + m_idx_selected); // Find the next visible preset. @@ -810,9 +811,9 @@ bool PresetCollection::delete_current_preset() if (new_selected_idx < m_presets.size()) for (; new_selected_idx < m_presets.size() && ! m_presets[new_selected_idx].is_visible; ++ new_selected_idx) ; if (new_selected_idx == m_presets.size()) - for (--new_selected_idx; new_selected_idx > 0 && !m_presets[new_selected_idx].is_visible; --new_selected_idx); + for (--new_selected_idx; new_selected_idx > 0 && !m_presets[new_selected_idx].is_visible; --new_selected_idx); this->select_preset(new_selected_idx); - return true; + return true; } void PresetCollection::load_bitmap_default(wxWindow *window, const std::string &file_name) @@ -836,18 +837,18 @@ const Preset* PresetCollection::get_selected_preset_parent() const return nullptr; // const std::string &inherits = this->get_edited_preset().inherits(); // if (inherits.empty()) -// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; +// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; std::string inherits = this->get_edited_preset().inherits(); if (inherits.empty()) { - if (this->get_selected_preset().is_system || this->get_selected_preset().is_default) + if (this->get_selected_preset().is_system || this->get_selected_preset().is_default) return &this->get_selected_preset(); if (this->get_selected_preset().is_external) return nullptr; - + inherits = m_type != Preset::Type::TYPE_PRINTER ? "- default -" : - this->get_edited_preset().printer_technology() == ptFFF ? + this->get_edited_preset().printer_technology() == ptFFF ? "- default FFF -" : "- default SLA -" ; } @@ -859,14 +860,14 @@ const Preset* PresetCollection::get_preset_parent(const Preset& child) const { const std::string &inherits = child.inherits(); if (inherits.empty()) -// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; - return nullptr; +// return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; + return nullptr; const Preset* preset = this->find_preset(inherits, false); return (preset == nullptr/* || preset->is_default */|| preset->is_external) ? nullptr : preset; } const std::string& PresetCollection::get_suffix_modified() { - return g_suffix_modified; + return g_suffix_modified; } // Return a preset by its name. If the preset is active, a temporary copy is returned. @@ -876,7 +877,7 @@ Preset* PresetCollection::find_preset(const std::string &name, bool first_visibl Preset key(m_type, name, false); auto it = this->find_preset_internal(name); // Ensure that a temporary copy is returned if the preset found is currently selected. - return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin()) : + return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin()) : first_visible_if_not_found ? &this->first_visible() : nullptr; } @@ -896,9 +897,9 @@ void PresetCollection::set_default_suppressed(bool default_suppressed) { if (m_default_suppressed != default_suppressed) { m_default_suppressed = default_suppressed; - bool default_visible = ! default_suppressed || m_idx_selected < m_num_default_presets; - for (size_t i = 0; i < m_num_default_presets; ++ i) - m_presets[i].is_visible = default_visible; + bool default_visible = ! default_suppressed || m_idx_selected < m_num_default_presets; + for (size_t i = 0; i < m_num_default_presets; ++ i) + m_presets[i].is_visible = default_visible; } } @@ -931,7 +932,7 @@ size_t PresetCollection::update_compatible_internal(const Preset &active_printer //void PresetCollection::delete_current_preset(); // Update the wxChoice UI component from this list of presets. -// Hide the +// Hide the void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) { if (ui == nullptr) @@ -940,12 +941,12 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) // Otherwise fill in the list from scratch. ui->Freeze(); ui->Clear(); - size_t selected_preset_item = 0; + size_t selected_preset_item = 0; - const Preset &selected_preset = this->get_selected_preset(); - // Show wide icons if the currently selected preset is not compatible with the current printer, - // and draw a red flag in front of the selected preset. - bool wide_icons = ! selected_preset.is_compatible && m_bitmap_incompatible != nullptr; + const Preset &selected_preset = this->get_selected_preset(); + // Show wide icons if the currently selected preset is not compatible with the current printer, + // and draw a red flag in front of the selected preset. + bool wide_icons = ! selected_preset.is_compatible && m_bitmap_incompatible != nullptr; /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. * So set sizes for solid_colored icons used for filament preset @@ -957,87 +958,87 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) const int thin_space_icon_width = 4 * scale_f + 0.5f; const int wide_space_icon_width = 6 * scale_f + 0.5f; - std::map nonsys_presets; - wxString selected = ""; - if (!this->m_presets.front().is_visible) + std::map nonsys_presets; + wxString selected = ""; + if (!this->m_presets.front().is_visible) ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); - for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++ i) { + for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++ i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected)) continue; - std::string bitmap_key = ""; - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(icon_width, icon_height) : *m_bitmap_incompatible); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(*m_bitmap_main_frame); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } + std::string bitmap_key = ""; + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(icon_width, icon_height) : *m_bitmap_incompatible); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(*m_bitmap_main_frame); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); + bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } - if (preset.is_default || preset.is_system) { - ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), - (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); - if (i == m_idx_selected) - selected_preset_item = ui->GetCount() - 1; - } - else - { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); - if (i == m_idx_selected) - selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); - } - if (i + 1 == m_num_default_presets) + if (preset.is_default || preset.is_system) { + ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), + (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + if (i == m_idx_selected) + selected_preset_item = ui->GetCount() - 1; + } + else + { + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); + if (i == m_idx_selected) + selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); + } + if (i + 1 == m_num_default_presets) ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap)); - } - if (!nonsys_presets.empty()) - { + } + if (!nonsys_presets.empty()) + { ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap)); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - ui->Append(it->first, *it->second); - if (it->first == selected) - selected_preset_item = ui->GetCount() - 1; - } - } - if (m_type == Preset::TYPE_PRINTER) { - std::string bitmap_key = ""; - // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left - // to the filament color image. - if (wide_icons) - bitmap_key += "wide,"; - bitmap_key += "add_printer"; - wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - if (wide_icons) - // Paint a red flag for incompatible presets. - bmps.emplace_back(m_bitmap_cache->mkclear(icon_width, icon_height)); - // Paint the color bars. - bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); - bmps.emplace_back(*m_bitmap_main_frame); - // Paint a lock at the system presets. - bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); - bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } - ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add a new printer")), *bmp), GUI::PresetComboBox::LABEL_ITEM_CONFIG_WIZARD); - } + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + ui->Append(it->first, *it->second); + if (it->first == selected) + selected_preset_item = ui->GetCount() - 1; + } + } + if (m_type == Preset::TYPE_PRINTER) { + std::string bitmap_key = ""; + // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left + // to the filament color image. + if (wide_icons) + bitmap_key += "wide,"; + bitmap_key += "add_printer"; + wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(m_bitmap_cache->mkclear(icon_width, icon_height)); + // Paint the color bars. + bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(*m_bitmap_main_frame); + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height)); + bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } + ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add a new printer")), *bmp), GUI::PresetComboBox::LABEL_ITEM_CONFIG_WIZARD); + } - ui->SetSelection(selected_preset_item); - ui->SetToolTip(ui->GetString(selected_preset_item)); + ui->SetSelection(selected_preset_item); + ui->SetToolTip(ui->GetString(selected_preset_item)); ui->check_selection(); ui->Thaw(); @@ -1052,7 +1053,7 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati return 0; ui->Freeze(); ui->Clear(); - size_t selected_preset_item = 0; + size_t selected_preset_item = 0; /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. * So set sizes for solid_colored(empty) icons used for preset @@ -1062,52 +1063,52 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati const int icon_height = 16 * scale_f + 0.5f; const int icon_width = 16 * scale_f + 0.5f; - std::map nonsys_presets; - wxString selected = ""; - if (!this->m_presets.front().is_visible) - ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); - for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) { + std::map nonsys_presets; + wxString selected = ""; + if (!this->m_presets.front().is_visible) + ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); + for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) { const Preset &preset = this->m_presets[i]; if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected)) continue; - std::string bitmap_key = "tab"; - bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; - bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; - wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); - if (bmp == nullptr) { - // Create the bitmap with color bars. - std::vector bmps; - const wxBitmap* tmp_bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible; - bmps.emplace_back((tmp_bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *tmp_bmp); - // Paint a lock at the system presets. - bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height)); - bmp = m_bitmap_cache->insert(bitmap_key, bmps); - } + std::string bitmap_key = "tab"; + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + wxBitmap *bmp = m_bitmap_cache->find(bitmap_key); + if (bmp == nullptr) { + // Create the bitmap with color bars. + std::vector bmps; + const wxBitmap* tmp_bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible; + bmps.emplace_back((tmp_bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *tmp_bmp); + // Paint a lock at the system presets. + bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height)); + bmp = m_bitmap_cache->insert(bitmap_key, bmps); + } - if (preset.is_default || preset.is_system) { - ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), - (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); - if (i == m_idx_selected) - selected_preset_item = ui->GetCount() - 1; - } - else - { - nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); - if (i == m_idx_selected) - selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); - } - if (i + 1 == m_num_default_presets) - ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); + if (preset.is_default || preset.is_system) { + ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), + (bmp == 0) ? (m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap) : *bmp); + if (i == m_idx_selected) + selected_preset_item = ui->GetCount() - 1; + } + else + { + nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/); + if (i == m_idx_selected) + selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()); + } + if (i + 1 == m_num_default_presets) + ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap); + } + if (!nonsys_presets.empty()) + { + ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + ui->Append(it->first, *it->second); + if (it->first == selected) + selected_preset_item = ui->GetCount() - 1; + } } - if (!nonsys_presets.empty()) - { - ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { - ui->Append(it->first, *it->second); - if (it->first == selected) - selected_preset_item = ui->GetCount() - 1; - } - } if (m_type == Preset::TYPE_PRINTER) { wxBitmap *bmp = m_bitmap_cache->find("add_printer_tab"); if (bmp == nullptr) { @@ -1119,10 +1120,10 @@ size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompati } ui->Append(PresetCollection::separator("Add a new printer"), *bmp); } - ui->SetSelection(selected_preset_item); - ui->SetToolTip(ui->GetString(selected_preset_item)); + ui->SetSelection(selected_preset_item); + ui->SetToolTip(ui->GetString(selected_preset_item)); ui->Thaw(); - return selected_preset_item; + return selected_preset_item; } // Update a dirty floag of the current preset, update the labels of the UI component accordingly. @@ -1142,11 +1143,11 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) const Preset *preset = this->find_preset(preset_name, false); // The old_label could be the "----- system presets ------" or the "------- user presets --------" separator. // assert(preset != nullptr); - if (preset != nullptr) { - std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name; - if (old_label != new_label) - ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str())); - } + if (preset != nullptr) { + std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name; + if (old_label != new_label) + ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str())); + } } #ifdef __APPLE__ // wxWidgets on OSX do not upload the text of the combo box line automatically. @@ -1159,15 +1160,15 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) template void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& vec, const ConfigBase &other, const ConfigBase &this_c) { - const T* opt_init = static_cast(other.option(opt_key)); - const T* opt_cur = static_cast(this_c.option(opt_key)); - int opt_init_max_id = opt_init->values.size() - 1; - for (int i = 0; i < opt_cur->values.size(); i++) - { - int init_id = i <= opt_init_max_id ? i : 0; - if (opt_cur->values[i] != opt_init->values[init_id]) - vec.emplace_back(opt_key + "#" + std::to_string(i)); - } + const T* opt_init = static_cast(other.option(opt_key)); + const T* opt_cur = static_cast(this_c.option(opt_key)); + int opt_init_max_id = opt_init->values.size() - 1; + for (int i = 0; i < opt_cur->values.size(); i++) + { + int init_id = i <= opt_init_max_id ? i : 0; + if (opt_cur->values[i] != opt_init->values[init_id]) + vec.emplace_back(opt_key + "#" + std::to_string(i)); + } } // Use deep_diff to correct return of changed options, considering individual options for each extruder. @@ -1201,10 +1202,10 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference, const bool deep_compare /*= false*/) { std::vector changed; - if (edited != nullptr && reference != nullptr) { + if (edited != nullptr && reference != nullptr) { changed = deep_compare ? - deep_diff(edited->config, reference->config) : - reference->config.diff(edited->config); + deep_diff(edited->config, reference->config) : + reference->config.diff(edited->config); // The "compatible_printers" option key is handled differently from the others: // It is not mandatory. If the key is missing, it means it is compatible with any printer. // If the key exists and it is empty, it means it is compatible with no printer. @@ -1227,19 +1228,19 @@ Preset& PresetCollection::select_preset(size_t idx) idx = first_visible_idx(); m_idx_selected = idx; m_edited_preset = m_presets[idx]; - bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets; - for (size_t i = 0; i < m_num_default_presets; ++i) - m_presets[i].is_visible = default_visible; + bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets; + for (size_t i = 0; i < m_num_default_presets; ++i) + m_presets[i].is_visible = default_visible; return m_presets[idx]; } bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force) -{ +{ std::string name = Preset::remove_suffix_modified(name_w_suffix); // 1) Try to find the preset by its name. auto it = this->find_preset_internal(name); size_t idx = 0; - if (it != m_presets.end() && it->name == name && it->is_visible) + if (it != m_presets.end() && it->name == name && it->is_visible) // Preset found by its name and it is visible. idx = it - m_presets.begin(); else { @@ -1262,11 +1263,11 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b } bool PresetCollection::select_preset_by_name_strict(const std::string &name) -{ +{ // 1) Try to find the preset by its name. auto it = this->find_preset_internal(name); size_t idx = (size_t)-1; - if (it != m_presets.end() && it->name == name && it->is_visible) + if (it != m_presets.end() && it->name == name && it->is_visible) // Preset found by its name. idx = it - m_presets.begin(); // 2) Select the new preset. @@ -1333,9 +1334,9 @@ std::vector PresetCollection::system_preset_names() const ++ num; std::vector out; out.reserve(num); - for (const Preset &preset : m_presets) - if (preset.is_system) - out.emplace_back(preset.name); + for (const Preset &preset : m_presets) + if (preset.is_system) + out.emplace_back(preset.name); std::sort(out.begin(), out.end()); return out; } @@ -1343,7 +1344,7 @@ std::vector PresetCollection::system_preset_names() const // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string PresetCollection::path_from_name(const std::string &new_name) const { - std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini"); + std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini"); return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string(); } @@ -1354,13 +1355,13 @@ void PresetCollection::clear_bitmap_cache() wxString PresetCollection::separator(const std::string &label) { - return wxString::FromUTF8(PresetCollection::separator_head()) + _(label) + wxString::FromUTF8(PresetCollection::separator_tail()); + return wxString::FromUTF8(PresetCollection::separator_head()) + _(label) + wxString::FromUTF8(PresetCollection::separator_tail()); } -const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const -{ +const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const +{ const ConfigOptionEnumGeneric *opt_printer_technology = config.opt("printer_technology"); - return this->default_preset((opt_printer_technology == nullptr || opt_printer_technology->value == ptFFF) ? 0 : 1); + return this->default_preset((opt_printer_technology == nullptr || opt_printer_technology->value == ptFFF) ? 0 : 1); } const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model_id) const diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9d7fc20a3e..4d3782a16f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -38,36 +38,36 @@ namespace GUI { wxDEFINE_EVENT(EVT_TAB_VALUE_CHANGED, wxCommandEvent); wxDEFINE_EVENT(EVT_TAB_PRESETS_CHANGED, SimpleEvent); -// Tab::Tab(wxNotebook* parent, const wxString& title, const char* name) : +// Tab::Tab(wxNotebook* parent, const wxString& title, const char* name) : // m_parent(parent), m_title(title), m_name(name) Tab::Tab(wxNotebook* parent, const wxString& title, Preset::Type type) : - m_parent(parent), m_title(title), m_type(type) + m_parent(parent), m_title(title), m_type(type) { - Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL/*, name*/); - this->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL/*, name*/); + this->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - m_compatible_printers.type = Preset::TYPE_PRINTER; - m_compatible_printers.key_list = "compatible_printers"; - m_compatible_printers.key_condition = "compatible_printers_condition"; - m_compatible_printers.dialog_title = _(L("Compatible printers")); - m_compatible_printers.dialog_label = _(L("Select the printers this profile is compatible with.")); + m_compatible_printers.type = Preset::TYPE_PRINTER; + m_compatible_printers.key_list = "compatible_printers"; + m_compatible_printers.key_condition = "compatible_printers_condition"; + m_compatible_printers.dialog_title = _(L("Compatible printers")); + m_compatible_printers.dialog_label = _(L("Select the printers this profile is compatible with.")); - m_compatible_prints.type = Preset::TYPE_PRINT; - m_compatible_prints.key_list = "compatible_prints"; - m_compatible_prints.key_condition = "compatible_prints_condition"; - m_compatible_prints.dialog_title = _(L("Compatible print profiles")); - m_compatible_prints.dialog_label = _(L("Select the print profiles this profile is compatible with.")); + m_compatible_prints.type = Preset::TYPE_PRINT; + m_compatible_prints.key_list = "compatible_prints"; + m_compatible_prints.key_condition = "compatible_prints_condition"; + m_compatible_prints.dialog_title = _(L("Compatible print profiles")); + m_compatible_prints.dialog_label = _(L("Select the print profiles this profile is compatible with.")); - wxGetApp().tabs_list.push_back(this); + wxGetApp().tabs_list.push_back(this); m_em_unit = wxGetApp().em_unit(); - Bind(wxEVT_SIZE, ([this](wxSizeEvent &evt) { - for (auto page : m_pages) - if (! page.get()->IsShown()) - page->layout_valid = false; - evt.Skip(); - })); + Bind(wxEVT_SIZE, ([this](wxSizeEvent &evt) { + for (auto page : m_pages) + if (! page.get()->IsShown()) + page->layout_valid = false; + evt.Skip(); + })); } void Tab::set_type() @@ -89,169 +89,169 @@ void Tab::create_preset_tab() m_preset_bundle = wxGetApp().preset_bundle; - // Vertical sizer to hold the choice menu and the rest of the page. + // Vertical sizer to hold the choice menu and the rest of the page. #ifdef __WXOSX__ - auto *main_sizer = new wxBoxSizer(wxVERTICAL); - main_sizer->SetSizeHints(this); - this->SetSizer(main_sizer); + auto *main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->SetSizeHints(this); + this->SetSizer(main_sizer); - // Create additional panel to Fit() it from OnActivate() - // It's needed for tooltip showing on OSX - m_tmp_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); - auto panel = m_tmp_panel; - auto sizer = new wxBoxSizer(wxVERTICAL); - m_tmp_panel->SetSizer(sizer); - m_tmp_panel->Layout(); + // Create additional panel to Fit() it from OnActivate() + // It's needed for tooltip showing on OSX + m_tmp_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); + auto panel = m_tmp_panel; + auto sizer = new wxBoxSizer(wxVERTICAL); + m_tmp_panel->SetSizer(sizer); + m_tmp_panel->Layout(); - main_sizer->Add(m_tmp_panel, 1, wxEXPAND | wxALL, 0); + main_sizer->Add(m_tmp_panel, 1, wxEXPAND | wxALL, 0); #else - Tab *panel = this; - auto *sizer = new wxBoxSizer(wxVERTICAL); - sizer->SetSizeHints(panel); - panel->SetSizer(sizer); + Tab *panel = this; + auto *sizer = new wxBoxSizer(wxVERTICAL); + sizer->SetSizeHints(panel); + panel->SetSizer(sizer); #endif //__WXOSX__ - // preset chooser + // preset chooser m_presets_choice = new wxBitmapComboBox(panel, wxID_ANY, "", wxDefaultPosition, wxSize(35 * m_em_unit, -1), 0, 0, wxCB_READONLY); - auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - //buttons + //buttons m_scaled_buttons.reserve(6); m_scaled_buttons.reserve(2); add_scaled_button(panel, &m_btn_save_preset, "save"); add_scaled_button(panel, &m_btn_delete_preset, "cross"); - m_show_incompatible_presets = false; - add_scaled_bitmap(this, m_bmp_show_incompatible_presets, "flag_red"); - add_scaled_bitmap(this, m_bmp_hide_incompatible_presets, "flag_green"); + m_show_incompatible_presets = false; + add_scaled_bitmap(this, m_bmp_show_incompatible_presets, "flag_red"); + add_scaled_bitmap(this, m_bmp_hide_incompatible_presets, "flag_green"); add_scaled_button(panel, &m_btn_hide_incompatible_presets, m_bmp_hide_incompatible_presets.name()); // TRN "Save current Settings" - m_btn_save_preset->SetToolTip(wxString::Format(_(L("Save current %s")),m_title)); - m_btn_delete_preset->SetToolTip(_(L("Delete this preset"))); - m_btn_delete_preset->Disable(); + m_btn_save_preset->SetToolTip(wxString::Format(_(L("Save current %s")),m_title)); + m_btn_delete_preset->SetToolTip(_(L("Delete this preset"))); + m_btn_delete_preset->Disable(); add_scaled_button(panel, &m_question_btn, "question"); - m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information \n" - "or click this button."))); + m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information \n" + "or click this button."))); - // Determine the theme color of OS (dark or light) + // Determine the theme color of OS (dark or light) auto luma = wxGetApp().get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - add_scaled_bitmap(this, m_bmp_value_lock , luma >= 128 ? "lock_closed" : "lock_closed_white"); - add_scaled_bitmap(this, m_bmp_value_unlock, "lock_open"); - m_bmp_non_system = &m_bmp_white_bullet; - // Bitmaps to be shown on the "Undo user changes" button next to each input field. - add_scaled_bitmap(this, m_bmp_value_revert, "undo"); - add_scaled_bitmap(this, m_bmp_white_bullet, luma >= 128 ? "dot" : "dot_white"); + // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + add_scaled_bitmap(this, m_bmp_value_lock , luma >= 128 ? "lock_closed" : "lock_closed_white"); + add_scaled_bitmap(this, m_bmp_value_unlock, "lock_open"); + m_bmp_non_system = &m_bmp_white_bullet; + // Bitmaps to be shown on the "Undo user changes" button next to each input field. + add_scaled_bitmap(this, m_bmp_value_revert, "undo"); + add_scaled_bitmap(this, m_bmp_white_bullet, luma >= 128 ? "dot" : "dot_white"); - fill_icon_descriptions(); - set_tooltips_text(); + fill_icon_descriptions(); + set_tooltips_text(); add_scaled_button(panel, &m_undo_btn, m_bmp_white_bullet.name()); add_scaled_button(panel, &m_undo_to_sys_btn, m_bmp_white_bullet.name()); - m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(); })); - m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(true); })); - m_question_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) - { - auto dlg = new ButtonsDescription(this, m_icon_descriptions); - if (dlg->ShowModal() == wxID_OK) { - // Colors for ui "decoration" + m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(); })); + m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(true); })); + m_question_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) + { + auto dlg = new ButtonsDescription(this, m_icon_descriptions); + if (dlg->ShowModal() == wxID_OK) { + // Colors for ui "decoration" for (Tab *tab : wxGetApp().tabs_list) { tab->m_sys_label_clr = wxGetApp().get_label_clr_sys(); tab->m_modified_label_clr = wxGetApp().get_label_clr_modified(); - tab->update_labels_colour(); - } - } - })); + tab->update_labels_colour(); + } + } + })); - // Colors for ui "decoration" - m_sys_label_clr = wxGetApp().get_label_clr_sys(); - m_modified_label_clr = wxGetApp().get_label_clr_modified(); - m_default_text_clr = wxGetApp().get_label_clr_default(); + // Colors for ui "decoration" + m_sys_label_clr = wxGetApp().get_label_clr_sys(); + m_modified_label_clr = wxGetApp().get_label_clr_modified(); + m_default_text_clr = wxGetApp().get_label_clr_default(); // Sizer with buttons for mode changing m_mode_sizer = new ModeSizer(panel); const float scale_factor = /*wxGetApp().*/em_unit(this)*0.1;// GetContentScaleFactor(); - m_hsizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_hsizer, 0, wxEXPAND | wxBOTTOM, 3); - m_hsizer->Add(m_presets_choice, 0, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); - m_hsizer->AddSpacer(int(4*scale_factor)); - m_hsizer->Add(m_btn_save_preset, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_hsizer, 0, wxEXPAND | wxBOTTOM, 3); + m_hsizer->Add(m_presets_choice, 0, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); + m_hsizer->AddSpacer(int(4*scale_factor)); + m_hsizer->Add(m_btn_save_preset, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(4 * scale_factor)); - m_hsizer->Add(m_btn_delete_preset, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_btn_delete_preset, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(16 * scale_factor)); - m_hsizer->Add(m_btn_hide_incompatible_presets, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_btn_hide_incompatible_presets, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(64 * scale_factor)); - m_hsizer->Add(m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); - m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(int(32 * scale_factor)); - m_hsizer->Add(m_question_btn, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_question_btn, 0, wxALIGN_CENTER_VERTICAL); // m_hsizer->AddStretchSpacer(32); - // StretchSpacer has a strange behavior under OSX, so + // StretchSpacer has a strange behavior under OSX, so // There is used just additional sizer for m_mode_sizer with right alignment auto mode_sizer = new wxBoxSizer(wxVERTICAL); mode_sizer->Add(m_mode_sizer, 1, wxALIGN_RIGHT); m_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 10); - //Horizontal sizer to hold the tree and the selected page. - m_hsizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_hsizer, 1, wxEXPAND, 0); + //Horizontal sizer to hold the tree and the selected page. + m_hsizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_hsizer, 1, wxEXPAND, 0); - //left vertical sizer - m_left_sizer = new wxBoxSizer(wxVERTICAL); - m_hsizer->Add(m_left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); + //left vertical sizer + m_left_sizer = new wxBoxSizer(wxVERTICAL); + m_hsizer->Add(m_left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 3); - // tree + // tree m_treectrl = new wxTreeCtrl(panel, wxID_ANY, wxDefaultPosition, wxSize(20 * m_em_unit, -1), - wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN | wxWANTS_CHARS); - m_left_sizer->Add(m_treectrl, 1, wxEXPAND); + wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN | wxWANTS_CHARS); + m_left_sizer->Add(m_treectrl, 1, wxEXPAND); const int img_sz = int(16 * scale_factor + 0.5f); m_icons = new wxImageList(img_sz, img_sz, true, 1); - // Index of the last icon inserted into $self->{icons}. - m_icon_count = -1; - m_treectrl->AssignImageList(m_icons); - m_treectrl->AddRoot("root"); - m_treectrl->SetIndent(0); - m_disable_tree_sel_changed_event = 0; + // Index of the last icon inserted into $self->{icons}. + m_icon_count = -1; + m_treectrl->AssignImageList(m_icons); + m_treectrl->AddRoot("root"); + m_treectrl->SetIndent(0); + m_disable_tree_sel_changed_event = 0; - m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, &Tab::OnTreeSelChange, this); - m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); + m_treectrl->Bind(wxEVT_TREE_SEL_CHANGED, &Tab::OnTreeSelChange, this); + m_treectrl->Bind(wxEVT_KEY_DOWN, &Tab::OnKeyDown, this); - m_presets_choice->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { - //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, - //! but the OSX version derived from wxOwnerDrawnCombo, instead of: - //! select_preset(m_presets_choice->GetStringSelection().ToUTF8().data()); - //! we doing next: - int selected_item = m_presets_choice->GetSelection(); - if (m_selected_preset_item == selected_item && !m_presets->current_is_dirty()) - return; - if (selected_item >= 0) { - std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); - if (selected_string.find(PresetCollection::separator_head()) == 0 - /*selected_string == "------- System presets -------" || - selected_string == "------- User presets -------"*/) { - m_presets_choice->SetSelection(m_selected_preset_item); - if (wxString::FromUTF8(selected_string.c_str()) == PresetCollection::separator(L("Add a new printer"))) - wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); }); - return; - } - m_selected_preset_item = selected_item; - select_preset(selected_string); - } - })); + m_presets_choice->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { + //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, + //! but the OSX version derived from wxOwnerDrawnCombo, instead of: + //! select_preset(m_presets_choice->GetStringSelection().ToUTF8().data()); + //! we doing next: + int selected_item = m_presets_choice->GetSelection(); + if (m_selected_preset_item == selected_item && !m_presets->current_is_dirty()) + return; + if (selected_item >= 0) { + std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); + if (selected_string.find(PresetCollection::separator_head()) == 0 + /*selected_string == "------- System presets -------" || + selected_string == "------- User presets -------"*/) { + m_presets_choice->SetSelection(m_selected_preset_item); + if (wxString::FromUTF8(selected_string.c_str()) == PresetCollection::separator(L("Add a new printer"))) + wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); }); + return; + } + m_selected_preset_item = selected_item; + select_preset(selected_string); + } + })); - m_btn_save_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { save_preset(); })); - m_btn_delete_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { delete_preset(); })); - m_btn_hide_incompatible_presets->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { - toggle_show_hide_incompatible(); - })); + m_btn_save_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { save_preset(); })); + m_btn_delete_preset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { delete_preset(); })); + m_btn_hide_incompatible_presets->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { + toggle_show_hide_incompatible(); + })); // Fill cache for mode bitmaps m_mode_bitmap_cache.reserve(3); @@ -259,24 +259,24 @@ void Tab::create_preset_tab() m_mode_bitmap_cache.push_back(ScalableBitmap(this, "mode_advanced_.png")); m_mode_bitmap_cache.push_back(ScalableBitmap(this, "mode_expert_.png")); - // Initialize the DynamicPrintConfig by default keys/values. - build(); - rebuild_page_tree(); + // Initialize the DynamicPrintConfig by default keys/values. + build(); + rebuild_page_tree(); m_complited = true; } -void Tab::add_scaled_button(wxWindow* parent, - ScalableButton** btn, - const std::string& icon_name, - const wxString& label/* = wxEmptyString*/, +void Tab::add_scaled_button(wxWindow* parent, + ScalableButton** btn, + const std::string& icon_name, + const wxString& label/* = wxEmptyString*/, long style /*= wxBU_EXACTFIT | wxNO_BORDER*/) { *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, style); m_scaled_buttons.push_back(*btn); } -void Tab::add_scaled_bitmap(wxWindow* parent, - ScalableBitmap& bmp, +void Tab::add_scaled_bitmap(wxWindow* parent, + ScalableBitmap& bmp, const std::string& icon_name) { bmp = ScalableBitmap(parent, icon_name); @@ -285,233 +285,233 @@ void Tab::add_scaled_bitmap(wxWindow* parent, void Tab::load_initial_data() { - m_config = &m_presets->get_edited_preset().config; - bool has_parent = m_presets->get_selected_preset_parent() != nullptr; - m_bmp_non_system = has_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; - m_ttg_non_system = has_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; - m_tt_non_system = has_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + m_config = &m_presets->get_edited_preset().config; + bool has_parent = m_presets->get_selected_preset_parent() != nullptr; + m_bmp_non_system = has_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; + m_ttg_non_system = has_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = has_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; } Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages /*= false*/) { - // Index of icon in an icon list $self->{icons}. - auto icon_idx = 0; - if (!icon.empty()) { - icon_idx = (m_icon_index.find(icon) == m_icon_index.end()) ? -1 : m_icon_index.at(icon); - if (icon_idx == -1) { - // Add a new icon to the icon list. + // Index of icon in an icon list $self->{icons}. + auto icon_idx = 0; + if (!icon.empty()) { + icon_idx = (m_icon_index.find(icon) == m_icon_index.end()) ? -1 : m_icon_index.at(icon); + if (icon_idx == -1) { + // Add a new icon to the icon list. m_scaled_icons_list.push_back(ScalableBitmap(this, icon)); m_icons->Add(m_scaled_icons_list.back().bmp()); icon_idx = ++m_icon_count; - m_icon_index[icon] = icon_idx; - } - } - // Initialize the page. + m_icon_index[icon] = icon_idx; + } + } + // Initialize the page. #ifdef __WXOSX__ - auto panel = m_tmp_panel; + auto panel = m_tmp_panel; #else - auto panel = this; + auto panel = this; #endif - PageShp page(new Page(panel, title, icon_idx, m_mode_bitmap_cache)); + PageShp page(new Page(panel, title, icon_idx, m_mode_bitmap_cache)); // page->SetBackgroundStyle(wxBG_STYLE_SYSTEM); #ifdef __WINDOWS__ // page->SetDoubleBuffered(true); #endif //__WINDOWS__ - page->SetScrollbars(1, 20, 1, 2); - page->Hide(); - m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); + page->SetScrollbars(1, 20, 1, 2); + page->Hide(); + m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); - if (!is_extruder_pages) - m_pages.push_back(page); + if (!is_extruder_pages) + m_pages.push_back(page); - page->set_config(m_config); - return page; + page->set_config(m_config); + return page; } void Tab::OnActivate() { -#ifdef __WXOSX__ - wxWindowUpdateLocker noUpdates(this); +#ifdef __WXOSX__ + wxWindowUpdateLocker noUpdates(this); - auto size = GetSizer()->GetSize(); - m_tmp_panel->GetSizer()->SetMinSize(size.x + m_size_move, size.y); - Fit(); - m_size_move *= -1; + auto size = GetSizer()->GetSize(); + m_tmp_panel->GetSizer()->SetMinSize(size.x + m_size_move, size.y); + Fit(); + m_size_move *= -1; #endif // __WXOSX__ } void Tab::update_labels_colour() { // Freeze(); - //update options "decoration" - for (const auto opt : m_options_list) - { - const wxColour *color = &m_sys_label_clr; + //update options "decoration" + for (const auto opt : m_options_list) + { + const wxColour *color = &m_sys_label_clr; - // value isn't equal to system value - if ((opt.second & osSystemValue) == 0) { - // value is equal to last saved - if ((opt.second & osInitValue) != 0) - color = &m_default_text_clr; - // value is modified - else - color = &m_modified_label_clr; - } - if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { - if (m_colored_Label != nullptr) { - m_colored_Label->SetForegroundColour(*color); - m_colored_Label->Refresh(true); - } - continue; - } + // value isn't equal to system value + if ((opt.second & osSystemValue) == 0) { + // value is equal to last saved + if ((opt.second & osInitValue) != 0) + color = &m_default_text_clr; + // value is modified + else + color = &m_modified_label_clr; + } + if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { + if (m_colored_Label != nullptr) { + m_colored_Label->SetForegroundColour(*color); + m_colored_Label->Refresh(true); + } + continue; + } - Field* field = get_field(opt.first); - if (field == nullptr) continue; - field->set_label_colour_force(color); - } + Field* field = get_field(opt.first); + if (field == nullptr) continue; + field->set_label_colour_force(color); + } // Thaw(); - auto cur_item = m_treectrl->GetFirstVisibleItem(); - while (cur_item) { - auto title = m_treectrl->GetItemText(cur_item); - for (auto page : m_pages) - { - if (page->title() != title) - continue; - - const wxColor *clr = !page->m_is_nonsys_values ? &m_sys_label_clr : - page->m_is_modified_values ? &m_modified_label_clr : - &m_default_text_clr; + auto cur_item = m_treectrl->GetFirstVisibleItem(); + while (cur_item) { + auto title = m_treectrl->GetItemText(cur_item); + for (auto page : m_pages) + { + if (page->title() != title) + continue; - m_treectrl->SetItemTextColour(cur_item, *clr); - break; - } - cur_item = m_treectrl->GetNextVisible(cur_item); - } + const wxColor *clr = !page->m_is_nonsys_values ? &m_sys_label_clr : + page->m_is_modified_values ? &m_modified_label_clr : + &m_default_text_clr; + + m_treectrl->SetItemTextColour(cur_item, *clr); + break; + } + cur_item = m_treectrl->GetNextVisible(cur_item); + } } // Update UI according to changes void Tab::update_changed_ui() { - if (m_postpone_update_ui) - return; + if (m_postpone_update_ui) + return; - const bool deep_compare = (m_type == Slic3r::Preset::TYPE_PRINTER || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL); - auto dirty_options = m_presets->current_dirty_options(deep_compare); - auto nonsys_options = m_presets->current_different_from_parent_options(deep_compare); + const bool deep_compare = (m_type == Slic3r::Preset::TYPE_PRINTER || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL); + auto dirty_options = m_presets->current_dirty_options(deep_compare); + auto nonsys_options = m_presets->current_different_from_parent_options(deep_compare); if (m_type == Slic3r::Preset::TYPE_PRINTER) { - TabPrinter* tab = static_cast(this); - if (tab->m_initial_extruders_count != tab->m_extruders_count) - dirty_options.emplace_back("extruders_count"); - if (tab->m_sys_extruders_count != tab->m_extruders_count) - nonsys_options.emplace_back("extruders_count"); - } + TabPrinter* tab = static_cast(this); + if (tab->m_initial_extruders_count != tab->m_extruders_count) + dirty_options.emplace_back("extruders_count"); + if (tab->m_sys_extruders_count != tab->m_extruders_count) + nonsys_options.emplace_back("extruders_count"); + } - for (auto& it : m_options_list) - it.second = m_opt_status_value; + for (auto& it : m_options_list) + it.second = m_opt_status_value; - for (auto opt_key : dirty_options) m_options_list[opt_key] &= ~osInitValue; - for (auto opt_key : nonsys_options) m_options_list[opt_key] &= ~osSystemValue; + for (auto opt_key : dirty_options) m_options_list[opt_key] &= ~osInitValue; + for (auto opt_key : nonsys_options) m_options_list[opt_key] &= ~osSystemValue; // Freeze(); - //update options "decoration" - for (const auto opt : m_options_list) - { - bool is_nonsys_value = false; - bool is_modified_value = true; - const ScalableBitmap *sys_icon = &m_bmp_value_lock; - const ScalableBitmap *icon = &m_bmp_value_revert; + //update options "decoration" + for (const auto opt : m_options_list) + { + bool is_nonsys_value = false; + bool is_modified_value = true; + const ScalableBitmap *sys_icon = &m_bmp_value_lock; + const ScalableBitmap *icon = &m_bmp_value_revert; - const wxColour *color = m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr; + const wxColour *color = m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr; - const wxString *sys_tt = &m_tt_value_lock; - const wxString *tt = &m_tt_value_revert; + const wxString *sys_tt = &m_tt_value_lock; + const wxString *tt = &m_tt_value_revert; - // value isn't equal to system value - if ((opt.second & osSystemValue) == 0) { - is_nonsys_value = true; - sys_icon = m_bmp_non_system; - sys_tt = m_tt_non_system; - // value is equal to last saved - if ((opt.second & osInitValue) != 0) - color = &m_default_text_clr; - // value is modified - else - color = &m_modified_label_clr; - } - if ((opt.second & osInitValue) != 0) - { - is_modified_value = false; - icon = &m_bmp_white_bullet; - tt = &m_tt_white_bullet; - } - if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { - if (m_colored_Label != nullptr) { - m_colored_Label->SetForegroundColour(*color); - m_colored_Label->Refresh(true); - } - continue; - } + // value isn't equal to system value + if ((opt.second & osSystemValue) == 0) { + is_nonsys_value = true; + sys_icon = m_bmp_non_system; + sys_tt = m_tt_non_system; + // value is equal to last saved + if ((opt.second & osInitValue) != 0) + color = &m_default_text_clr; + // value is modified + else + color = &m_modified_label_clr; + } + if ((opt.second & osInitValue) != 0) + { + is_modified_value = false; + icon = &m_bmp_white_bullet; + tt = &m_tt_white_bullet; + } + if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { + if (m_colored_Label != nullptr) { + m_colored_Label->SetForegroundColour(*color); + m_colored_Label->Refresh(true); + } + continue; + } - Field* field = get_field(opt.first); - if (field == nullptr) continue; - field->m_is_nonsys_value = is_nonsys_value; - field->m_is_modified_value = is_modified_value; - field->set_undo_bitmap(icon); - field->set_undo_to_sys_bitmap(sys_icon); - field->set_undo_tooltip(tt); - field->set_undo_to_sys_tooltip(sys_tt); - field->set_label_colour(color); - } + Field* field = get_field(opt.first); + if (field == nullptr) continue; + field->m_is_nonsys_value = is_nonsys_value; + field->m_is_modified_value = is_modified_value; + field->set_undo_bitmap(icon); + field->set_undo_to_sys_bitmap(sys_icon); + field->set_undo_tooltip(tt); + field->set_undo_to_sys_tooltip(sys_tt); + field->set_label_colour(color); + } // Thaw(); - wxTheApp->CallAfter([this]() { + wxTheApp->CallAfter([this]() { if (parent()) //To avoid a crash, parent should be exist for a moment of a tree updating - update_changed_tree_ui(); - }); + update_changed_tree_ui(); + }); } void Tab::init_options_list() { - if (!m_options_list.empty()) - m_options_list.clear(); + if (!m_options_list.empty()) + m_options_list.clear(); - for (const auto opt_key : m_config->keys()) - m_options_list.emplace(opt_key, m_opt_status_value); + for (const auto opt_key : m_config->keys()) + m_options_list.emplace(opt_key, m_opt_status_value); } template void add_correct_opts_to_options_list(const std::string &opt_key, std::map& map, Tab *tab, const int& value) { - T *opt_cur = static_cast(tab->m_config->option(opt_key)); - for (int i = 0; i < opt_cur->values.size(); i++) - map.emplace(opt_key + "#" + std::to_string(i), value); + T *opt_cur = static_cast(tab->m_config->option(opt_key)); + for (int i = 0; i < opt_cur->values.size(); i++) + map.emplace(opt_key + "#" + std::to_string(i), value); } void TabPrinter::init_options_list() { - if (!m_options_list.empty()) - m_options_list.clear(); + if (!m_options_list.empty()) + m_options_list.clear(); - for (const auto opt_key : m_config->keys()) - { - if (opt_key == "bed_shape") { - m_options_list.emplace(opt_key, m_opt_status_value); - continue; - } - switch (m_config->option(opt_key)->type()) - { - case coInts: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; - case coBools: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; - case coFloats: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; - case coStrings: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; - case coPercents:add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; - case coPoints: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; - default: m_options_list.emplace(opt_key, m_opt_status_value); break; - } - } - m_options_list.emplace("extruders_count", m_opt_status_value); + for (const auto opt_key : m_config->keys()) + { + if (opt_key == "bed_shape") { + m_options_list.emplace(opt_key, m_opt_status_value); + continue; + } + switch (m_config->option(opt_key)->type()) + { + case coInts: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coBools: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coFloats: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coStrings: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coPercents:add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + case coPoints: add_correct_opts_to_options_list(opt_key, m_options_list, this, m_opt_status_value); break; + default: m_options_list.emplace(opt_key, m_opt_status_value); break; + } + } + m_options_list.emplace("extruders_count", m_opt_status_value); } void TabSLAMaterial::init_options_list() @@ -540,184 +540,184 @@ void TabSLAMaterial::init_options_list() void Tab::get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page) { - auto opt = m_options_list.find(opt_key); - if (sys_page) sys_page = (opt->second & osSystemValue) != 0; - modified_page |= (opt->second & osInitValue) == 0; + auto opt = m_options_list.find(opt_key); + if (sys_page) sys_page = (opt->second & osSystemValue) != 0; + modified_page |= (opt->second & osInitValue) == 0; } void Tab::update_changed_tree_ui() { - if (m_options_list.empty()) + if (m_options_list.empty()) return; - auto cur_item = m_treectrl->GetFirstVisibleItem(); + auto cur_item = m_treectrl->GetFirstVisibleItem(); if (!cur_item || !m_treectrl->IsVisible(cur_item)) return; - auto selected_item = m_treectrl->GetSelection(); - auto selection = selected_item ? m_treectrl->GetItemText(selected_item) : ""; + auto selected_item = m_treectrl->GetSelection(); + auto selection = selected_item ? m_treectrl->GetItemText(selected_item) : ""; - while (cur_item) { - auto title = m_treectrl->GetItemText(cur_item); - for (auto page : m_pages) - { - if (page->title() != title) - continue; - bool sys_page = true; - bool modified_page = false; - if (title == _("General")) { - std::initializer_list optional_keys{ "extruders_count", "bed_shape" }; - for (auto &opt_key : optional_keys) { - get_sys_and_mod_flags(opt_key, sys_page, modified_page); - } - } - if (title == _("Dependencies")) { - if (m_type == Slic3r::Preset::TYPE_PRINTER) { - sys_page = m_presets->get_selected_preset_parent() != nullptr; - modified_page = false; - } else { - if (m_type == Slic3r::Preset::TYPE_FILAMENT || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL) - get_sys_and_mod_flags("compatible_prints", sys_page, modified_page); - get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); - } - } - for (auto group : page->m_optgroups) - { - if (!sys_page && modified_page) - break; - for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { - const std::string& opt_key = it->first; - get_sys_and_mod_flags(opt_key, sys_page, modified_page); - } - } + while (cur_item) { + auto title = m_treectrl->GetItemText(cur_item); + for (auto page : m_pages) + { + if (page->title() != title) + continue; + bool sys_page = true; + bool modified_page = false; + if (title == _("General")) { + std::initializer_list optional_keys{ "extruders_count", "bed_shape" }; + for (auto &opt_key : optional_keys) { + get_sys_and_mod_flags(opt_key, sys_page, modified_page); + } + } + if (title == _("Dependencies")) { + if (m_type == Slic3r::Preset::TYPE_PRINTER) { + sys_page = m_presets->get_selected_preset_parent() != nullptr; + modified_page = false; + } else { + if (m_type == Slic3r::Preset::TYPE_FILAMENT || m_type == Slic3r::Preset::TYPE_SLA_MATERIAL) + get_sys_and_mod_flags("compatible_prints", sys_page, modified_page); + get_sys_and_mod_flags("compatible_printers", sys_page, modified_page); + } + } + for (auto group : page->m_optgroups) + { + if (!sys_page && modified_page) + break; + for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { + const std::string& opt_key = it->first; + get_sys_and_mod_flags(opt_key, sys_page, modified_page); + } + } - const wxColor *clr = sys_page ? (m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr) : - modified_page ? &m_modified_label_clr : - &m_default_text_clr; + const wxColor *clr = sys_page ? (m_is_default_preset ? &m_default_text_clr : &m_sys_label_clr) : + modified_page ? &m_modified_label_clr : + &m_default_text_clr; - if (page->set_item_colour(clr)) - m_treectrl->SetItemTextColour(cur_item, *clr); + if (page->set_item_colour(clr)) + m_treectrl->SetItemTextColour(cur_item, *clr); - page->m_is_nonsys_values = !sys_page; - page->m_is_modified_values = modified_page; + page->m_is_nonsys_values = !sys_page; + page->m_is_modified_values = modified_page; - if (selection == title) { - m_is_nonsys_values = page->m_is_nonsys_values; - m_is_modified_values = page->m_is_modified_values; - } - break; - } + if (selection == title) { + m_is_nonsys_values = page->m_is_nonsys_values; + m_is_modified_values = page->m_is_modified_values; + } + break; + } auto next_item = m_treectrl->GetNextVisible(cur_item); cur_item = next_item; - } - update_undo_buttons(); + } + update_undo_buttons(); } void Tab::update_undo_buttons() { - m_undo_btn-> SetBitmap_(m_is_modified_values ? m_bmp_value_revert: m_bmp_white_bullet); - m_undo_to_sys_btn-> SetBitmap_(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); + m_undo_btn-> SetBitmap_(m_is_modified_values ? m_bmp_value_revert: m_bmp_white_bullet); + m_undo_to_sys_btn-> SetBitmap_(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); - m_undo_btn->SetToolTip(m_is_modified_values ? m_ttg_value_revert : m_ttg_white_bullet); - m_undo_to_sys_btn->SetToolTip(m_is_nonsys_values ? *m_ttg_non_system : m_ttg_value_lock); + m_undo_btn->SetToolTip(m_is_modified_values ? m_ttg_value_revert : m_ttg_white_bullet); + m_undo_to_sys_btn->SetToolTip(m_is_nonsys_values ? *m_ttg_non_system : m_ttg_value_lock); } void Tab::on_roll_back_value(const bool to_sys /*= true*/) { - int os; - if (to_sys) { - if (!m_is_nonsys_values) return; - os = osSystemValue; - } - else { - if (!m_is_modified_values) return; - os = osInitValue; - } + int os; + if (to_sys) { + if (!m_is_nonsys_values) return; + os = osSystemValue; + } + else { + if (!m_is_modified_values) return; + os = osInitValue; + } - m_postpone_update_ui = true; + m_postpone_update_ui = true; - auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); - for (auto page : m_pages) - if (page->title() == selection) { - for (auto group : page->m_optgroups) { - if (group->title == _("Capabilities")) { - if ((m_options_list["extruders_count"] & os) == 0) - to_sys ? group->back_to_sys_value("extruders_count") : group->back_to_initial_value("extruders_count"); - } - if (group->title == _("Size and coordinates")) { - if ((m_options_list["bed_shape"] & os) == 0) { - to_sys ? group->back_to_sys_value("bed_shape") : group->back_to_initial_value("bed_shape"); - load_key_value("bed_shape", true/*some value*/, true); - } + auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); + for (auto page : m_pages) + if (page->title() == selection) { + for (auto group : page->m_optgroups) { + if (group->title == _("Capabilities")) { + if ((m_options_list["extruders_count"] & os) == 0) + to_sys ? group->back_to_sys_value("extruders_count") : group->back_to_initial_value("extruders_count"); + } + if (group->title == _("Size and coordinates")) { + if ((m_options_list["bed_shape"] & os) == 0) { + to_sys ? group->back_to_sys_value("bed_shape") : group->back_to_initial_value("bed_shape"); + load_key_value("bed_shape", true/*some value*/, true); + } - } - if (group->title == _("Profile dependencies")) { - if (m_type != Slic3r::Preset::TYPE_PRINTER && (m_options_list["compatible_printers"] & os) == 0) { - to_sys ? group->back_to_sys_value("compatible_printers") : group->back_to_initial_value("compatible_printers"); - load_key_value("compatible_printers", true/*some value*/, true); + } + if (group->title == _("Profile dependencies")) { + if (m_type != Slic3r::Preset::TYPE_PRINTER && (m_options_list["compatible_printers"] & os) == 0) { + to_sys ? group->back_to_sys_value("compatible_printers") : group->back_to_initial_value("compatible_printers"); + load_key_value("compatible_printers", true/*some value*/, true); - bool is_empty = m_config->option("compatible_printers")->values.empty(); - m_compatible_printers.checkbox->SetValue(is_empty); - is_empty ? m_compatible_printers.btn->Disable() : m_compatible_printers.btn->Enable(); - } - if ((m_type == Slic3r::Preset::TYPE_PRINT || m_type == Slic3r::Preset::TYPE_SLA_PRINT) && (m_options_list["compatible_prints"] & os) == 0) { - to_sys ? group->back_to_sys_value("compatible_prints") : group->back_to_initial_value("compatible_prints"); - load_key_value("compatible_prints", true/*some value*/, true); + bool is_empty = m_config->option("compatible_printers")->values.empty(); + m_compatible_printers.checkbox->SetValue(is_empty); + is_empty ? m_compatible_printers.btn->Disable() : m_compatible_printers.btn->Enable(); + } + if ((m_type == Slic3r::Preset::TYPE_PRINT || m_type == Slic3r::Preset::TYPE_SLA_PRINT) && (m_options_list["compatible_prints"] & os) == 0) { + to_sys ? group->back_to_sys_value("compatible_prints") : group->back_to_initial_value("compatible_prints"); + load_key_value("compatible_prints", true/*some value*/, true); - bool is_empty = m_config->option("compatible_prints")->values.empty(); - m_compatible_prints.checkbox->SetValue(is_empty); - is_empty ? m_compatible_prints.btn->Disable() : m_compatible_prints.btn->Enable(); - } - } - for (auto kvp : group->m_opt_map) { - const std::string& opt_key = kvp.first; - if ((m_options_list[opt_key] & os) == 0) - to_sys ? group->back_to_sys_value(opt_key) : group->back_to_initial_value(opt_key); - } - } - break; - } + bool is_empty = m_config->option("compatible_prints")->values.empty(); + m_compatible_prints.checkbox->SetValue(is_empty); + is_empty ? m_compatible_prints.btn->Disable() : m_compatible_prints.btn->Enable(); + } + } + for (auto kvp : group->m_opt_map) { + const std::string& opt_key = kvp.first; + if ((m_options_list[opt_key] & os) == 0) + to_sys ? group->back_to_sys_value(opt_key) : group->back_to_initial_value(opt_key); + } + } + break; + } - m_postpone_update_ui = false; - update_changed_ui(); + m_postpone_update_ui = false; + update_changed_ui(); } // Update the combo box label of the selected preset based on its "dirty" state, // comparing the selected preset config with $self->{config}. void Tab::update_dirty() { - m_presets->update_dirty_ui(m_presets_choice); - on_presets_changed(); - update_changed_ui(); + m_presets->update_dirty_ui(m_presets_choice); + on_presets_changed(); + update_changed_ui(); } void Tab::update_tab_ui() { - m_selected_preset_item = m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets, m_em_unit); + m_selected_preset_item = m_presets->update_tab_ui(m_presets_choice, m_show_incompatible_presets, m_em_unit); } // Load a provied DynamicConfig into the tab, modifying the active preset. // This could be used for example by setting a Wipe Tower position by interactive manipulation in the 3D view. void Tab::load_config(const DynamicPrintConfig& config) { - bool modified = 0; - for(auto opt_key : m_config->diff(config)) { - m_config->set_key_value(opt_key, config.option(opt_key)->clone()); - modified = 1; - } - if (modified) { - update_dirty(); - //# Initialize UI components with the config values. - reload_config(); - update(); - } + bool modified = 0; + for(auto opt_key : m_config->diff(config)) { + m_config->set_key_value(opt_key, config.option(opt_key)->clone()); + modified = 1; + } + if (modified) { + update_dirty(); + //# Initialize UI components with the config values. + reload_config(); + update(); + } } // Reload current $self->{config} (aka $self->{presets}->edited_preset->config) into the UI fields. void Tab::reload_config() { // Freeze(); - for (auto page : m_pages) - page->reload_config(); + for (auto page : m_pages) + page->reload_config(); // Thaw(); } @@ -735,12 +735,12 @@ void Tab::update_visibility() { Freeze(); // There is needed Freeze/Thaw to avoid a flashing after Show/Layout - for (auto page : m_pages) + for (auto page : m_pages) page->update_visibility(m_mode); update_page_tree_visibility(); Layout(); - Thaw(); + Thaw(); update_changed_tree_ui(); } @@ -783,25 +783,25 @@ void Tab::msw_rescale() Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const { - Field* field = nullptr; - for (auto page : m_pages) { - field = page->get_field(opt_key, opt_index); - if (field != nullptr) - return field; - } - return field; + Field* field = nullptr; + for (auto page : m_pages) { + field = page->get_field(opt_key, opt_index); + if (field != nullptr) + return field; + } + return field; } // Set a key/value pair on this page. Return true if the value has been modified. // Currently used for distributing extruders_count over preset pages of Slic3r::GUI::Tab::Printer // after a preset is loaded. bool Tab::set_value(const t_config_option_key& opt_key, const boost::any& value) { - bool changed = false; - for(auto page: m_pages) { - if (page->set_value(opt_key, value)) - changed = true; - } - return changed; + bool changed = false; + for(auto page: m_pages) { + if (page->set_value(opt_key, value)) + changed = true; + } + return changed; } // To be called by custom widgets, load a value into a config, @@ -810,54 +810,54 @@ bool Tab::set_value(const t_config_option_key& opt_key, const boost::any& value) // and value can be some random value because in this case it will not been used void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value /*= false*/) { - if (!saved_value) change_opt_value(*m_config, opt_key, value); - // Mark the print & filament enabled if they are compatible with the currently selected preset. - if (opt_key == "compatible_printers" || opt_key == "compatible_prints") { - // Don't select another profile if this profile happens to become incompatible. - m_preset_bundle->update_compatible(false); - } - m_presets->update_dirty_ui(m_presets_choice); - on_presets_changed(); - update(); + if (!saved_value) change_opt_value(*m_config, opt_key, value); + // Mark the print & filament enabled if they are compatible with the currently selected preset. + if (opt_key == "compatible_printers" || opt_key == "compatible_prints") { + // Don't select another profile if this profile happens to become incompatible. + m_preset_bundle->update_compatible(false); + } + m_presets->update_dirty_ui(m_presets_choice); + on_presets_changed(); + update(); } static wxString support_combo_value_for_config(const DynamicPrintConfig &config, bool is_fff) { const std::string support = is_fff ? "support_material" : "supports_enable"; const std::string buildplate_only = is_fff ? "support_material_buildplate_only" : "support_buildplate_only"; - return - ! config.opt_bool(support) ? - _("None") : - (is_fff && !config.opt_bool("support_material_auto")) ? - _("For support enforcers only") : + return + ! config.opt_bool(support) ? + _("None") : + (is_fff && !config.opt_bool("support_material_auto")) ? + _("For support enforcers only") : (config.opt_bool(buildplate_only) ? _("Support on build plate only") : - _("Everywhere")); + _("Everywhere")); } void Tab::on_value_change(const std::string& opt_key, const boost::any& value) { - if (wxGetApp().plater() == nullptr) { - return; - } + if (wxGetApp().plater() == nullptr) { + return; + } const bool is_fff = supports_printer_technology(ptFFF); - ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); + ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); if (opt_key == "fill_density" || opt_key == "pad_enable") - { + { boost::any val = og_freq_chng_params->get_config_value(*m_config, opt_key); og_freq_chng_params->set_value(opt_key, val); - } + } - if (is_fff ? - (opt_key == "support_material" || opt_key == "support_material_auto" || opt_key == "support_material_buildplate_only") : - (opt_key == "supports_enable" || opt_key == "support_buildplate_only")) - og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); + if (is_fff ? + (opt_key == "support_material" || opt_key == "support_material_auto" || opt_key == "support_material_buildplate_only") : + (opt_key == "supports_enable" || opt_key == "support_buildplate_only")) + og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); - if (opt_key == "brim_width") - { - bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; + if (opt_key == "brim_width") + { + bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; og_freq_chng_params->set_value("brim", val); - } + } if (opt_key == "wipe_tower" || opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" ) update_wiping_button_visibility(); @@ -865,7 +865,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) if (opt_key == "extruders_count") wxGetApp().plater()->on_extruders_change(boost::any_cast(value)); - update(); + update(); } // Show/hide the 'purging volumes' button @@ -890,13 +890,13 @@ void Tab::update_wiping_button_visibility() { // to update number of "filament" selection boxes when the number of extruders change. void Tab::on_presets_changed() { - if (wxGetApp().plater() == nullptr) { - return; - } + if (wxGetApp().plater() == nullptr) { + return; + } // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets wxGetApp().plater()->sidebar().update_presets(m_type); - update_preset_description_line(); + update_preset_description_line(); // Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. for (auto t: m_dependent_tabs) @@ -912,81 +912,81 @@ void Tab::on_presets_changed() void Tab::update_preset_description_line() { - const Preset* parent = m_presets->get_selected_preset_parent(); - const Preset& preset = m_presets->get_edited_preset(); + const Preset* parent = m_presets->get_selected_preset_parent(); + const Preset& preset = m_presets->get_edited_preset(); - wxString description_line; + wxString description_line; - if (preset.is_default) { - description_line = _(L("This is a default preset.")); - } else if (preset.is_system) { - description_line = _(L("This is a system preset.")); - } else if (parent == nullptr) { - description_line = _(L("Current preset is inherited from the default preset.")); - } else { - description_line = wxString::Format( - _(L("Current preset is inherited from:\n\t%s")), GUI::from_u8(parent->name)); - } + if (preset.is_default) { + description_line = _(L("This is a default preset.")); + } else if (preset.is_system) { + description_line = _(L("This is a system preset.")); + } else if (parent == nullptr) { + description_line = _(L("Current preset is inherited from the default preset.")); + } else { + description_line = wxString::Format( + _(L("Current preset is inherited from:\n\t%s")), GUI::from_u8(parent->name)); + } - if (preset.is_default || preset.is_system) - description_line += "\n\t" + _(L("It can't be deleted or modified.")) + - "\n\t" + _(L("Any modifications should be saved as a new preset inherited from this one.")) + - "\n\t" + _(L("To do that please specify a new name for the preset.")); - - if (parent && parent->vendor) - { - description_line += "\n\n" + _(L("Additional information:")) + "\n"; - description_line += "\t" + _(L("vendor")) + ": " + (m_type == Slic3r::Preset::TYPE_PRINTER ? "\n\t\t" : "") + parent->vendor->name + - ", ver: " + parent->vendor->config_version.to_string(); - if (m_type == Slic3r::Preset::TYPE_PRINTER) { - const std::string &printer_model = preset.config.opt_string("printer_model"); - if (! printer_model.empty()) - description_line += "\n\n\t" + _(L("printer model")) + ": \n\t\t" + printer_model; - switch (preset.printer_technology()) { - case ptFFF: - { - //FIXME add prefered_sla_material_profile for SLA - const std::string &default_print_profile = preset.config.opt_string("default_print_profile"); - const std::vector &default_filament_profiles = preset.config.option("default_filament_profile")->values; - if (!default_print_profile.empty()) - description_line += "\n\n\t" + _(L("default print profile")) + ": \n\t\t" + default_print_profile; - if (!default_filament_profiles.empty()) - { - description_line += "\n\n\t" + _(L("default filament profile")) + ": \n\t\t"; - for (auto& profile : default_filament_profiles) { - if (&profile != &*default_filament_profiles.begin()) - description_line += ", "; - description_line += profile; - } - } - break; - } - case ptSLA: - { - //FIXME add prefered_sla_material_profile for SLA - const std::string &default_sla_material_profile = preset.config.opt_string("default_sla_material_profile"); - if (!default_sla_material_profile.empty()) - description_line += "\n\n\t" + _(L("default SLA material profile")) + ": \n\t\t" + default_sla_material_profile; + if (preset.is_default || preset.is_system) + description_line += "\n\t" + _(L("It can't be deleted or modified.")) + + "\n\t" + _(L("Any modifications should be saved as a new preset inherited from this one.")) + + "\n\t" + _(L("To do that please specify a new name for the preset.")); - const std::string &default_sla_print_profile = preset.config.opt_string("default_sla_print_profile"); - if (!default_sla_print_profile.empty()) - description_line += "\n\n\t" + _(L("default SLA print profile")) + ": \n\t\t" + default_sla_print_profile; - break; - } - } - } - } + if (parent && parent->vendor) + { + description_line += "\n\n" + _(L("Additional information:")) + "\n"; + description_line += "\t" + _(L("vendor")) + ": " + (m_type == Slic3r::Preset::TYPE_PRINTER ? "\n\t\t" : "") + parent->vendor->name + + ", ver: " + parent->vendor->config_version.to_string(); + if (m_type == Slic3r::Preset::TYPE_PRINTER) { + const std::string &printer_model = preset.config.opt_string("printer_model"); + if (! printer_model.empty()) + description_line += "\n\n\t" + _(L("printer model")) + ": \n\t\t" + printer_model; + switch (preset.printer_technology()) { + case ptFFF: + { + //FIXME add prefered_sla_material_profile for SLA + const std::string &default_print_profile = preset.config.opt_string("default_print_profile"); + const std::vector &default_filament_profiles = preset.config.option("default_filament_profile")->values; + if (!default_print_profile.empty()) + description_line += "\n\n\t" + _(L("default print profile")) + ": \n\t\t" + default_print_profile; + if (!default_filament_profiles.empty()) + { + description_line += "\n\n\t" + _(L("default filament profile")) + ": \n\t\t"; + for (auto& profile : default_filament_profiles) { + if (&profile != &*default_filament_profiles.begin()) + description_line += ", "; + description_line += profile; + } + } + break; + } + case ptSLA: + { + //FIXME add prefered_sla_material_profile for SLA + const std::string &default_sla_material_profile = preset.config.opt_string("default_sla_material_profile"); + if (!default_sla_material_profile.empty()) + description_line += "\n\n\t" + _(L("default SLA material profile")) + ": \n\t\t" + default_sla_material_profile; - m_parent_preset_description_line->SetText(description_line, false); + const std::string &default_sla_print_profile = preset.config.opt_string("default_sla_print_profile"); + if (!default_sla_print_profile.empty()) + description_line += "\n\n\t" + _(L("default SLA print profile")) + ": \n\t\t" + default_sla_print_profile; + break; + } + } + } + } + + m_parent_preset_description_line->SetText(description_line, false); } void Tab::update_frequently_changed_parameters() { - const bool is_fff = supports_printer_technology(ptFFF); - auto og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); + const bool is_fff = supports_printer_technology(ptFFF); + auto og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); if (!og_freq_chng_params) return; - og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); + og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); const std::string updated_value_key = is_fff ? "fill_density" : "pad_enable"; @@ -1002,236 +1002,236 @@ void Tab::update_frequently_changed_parameters() void TabPrint::build() { - m_presets = &m_preset_bundle->prints; - load_initial_data(); + m_presets = &m_preset_bundle->prints; + load_initial_data(); - auto page = add_options_page(_(L("Layers and perimeters")), "layers"); - auto optgroup = page->new_optgroup(_(L("Layer height"))); - optgroup->append_single_option_line("layer_height"); - optgroup->append_single_option_line("first_layer_height"); + auto page = add_options_page(_(L("Layers and perimeters")), "layers"); + auto optgroup = page->new_optgroup(_(L("Layer height"))); + optgroup->append_single_option_line("layer_height"); + optgroup->append_single_option_line("first_layer_height"); - optgroup = page->new_optgroup(_(L("Vertical shells"))); - optgroup->append_single_option_line("perimeters"); - optgroup->append_single_option_line("spiral_vase"); + optgroup = page->new_optgroup(_(L("Vertical shells"))); + optgroup->append_single_option_line("perimeters"); + optgroup->append_single_option_line("spiral_vase"); - Line line { "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_recommended_thin_wall_thickness_description_line); - }; - optgroup->append_line(line); + Line line { "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_recommended_thin_wall_thickness_description_line); + }; + optgroup->append_line(line); - optgroup = page->new_optgroup(_(L("Horizontal shells"))); - line = { _(L("Solid layers")), "" }; - line.append_option(optgroup->get_option("top_solid_layers")); - line.append_option(optgroup->get_option("bottom_solid_layers")); - optgroup->append_line(line); + optgroup = page->new_optgroup(_(L("Horizontal shells"))); + line = { _(L("Solid layers")), "" }; + line.append_option(optgroup->get_option("top_solid_layers")); + line.append_option(optgroup->get_option("bottom_solid_layers")); + optgroup->append_line(line); - optgroup = page->new_optgroup(_(L("Quality (slower slicing)"))); - optgroup->append_single_option_line("extra_perimeters"); - optgroup->append_single_option_line("ensure_vertical_shell_thickness"); - optgroup->append_single_option_line("avoid_crossing_perimeters"); - optgroup->append_single_option_line("thin_walls"); - optgroup->append_single_option_line("overhangs"); + optgroup = page->new_optgroup(_(L("Quality (slower slicing)"))); + optgroup->append_single_option_line("extra_perimeters"); + optgroup->append_single_option_line("ensure_vertical_shell_thickness"); + optgroup->append_single_option_line("avoid_crossing_perimeters"); + optgroup->append_single_option_line("thin_walls"); + optgroup->append_single_option_line("overhangs"); - optgroup = page->new_optgroup(_(L("Advanced"))); - optgroup->append_single_option_line("seam_position"); - optgroup->append_single_option_line("external_perimeters_first"); + optgroup = page->new_optgroup(_(L("Advanced"))); + optgroup->append_single_option_line("seam_position"); + optgroup->append_single_option_line("external_perimeters_first"); - page = add_options_page(_(L("Infill")), "infill"); - optgroup = page->new_optgroup(_(L("Infill"))); - optgroup->append_single_option_line("fill_density"); - optgroup->append_single_option_line("fill_pattern"); - optgroup->append_single_option_line("top_fill_pattern"); - optgroup->append_single_option_line("bottom_fill_pattern"); + page = add_options_page(_(L("Infill")), "infill"); + optgroup = page->new_optgroup(_(L("Infill"))); + optgroup->append_single_option_line("fill_density"); + optgroup->append_single_option_line("fill_pattern"); + optgroup->append_single_option_line("top_fill_pattern"); + optgroup->append_single_option_line("bottom_fill_pattern"); - optgroup = page->new_optgroup(_(L("Reducing printing time"))); - optgroup->append_single_option_line("infill_every_layers"); - optgroup->append_single_option_line("infill_only_where_needed"); + optgroup = page->new_optgroup(_(L("Reducing printing time"))); + optgroup->append_single_option_line("infill_every_layers"); + optgroup->append_single_option_line("infill_only_where_needed"); - optgroup = page->new_optgroup(_(L("Advanced"))); - optgroup->append_single_option_line("solid_infill_every_layers"); - optgroup->append_single_option_line("fill_angle"); - optgroup->append_single_option_line("solid_infill_below_area"); - optgroup->append_single_option_line("bridge_angle"); - optgroup->append_single_option_line("only_retract_when_crossing_perimeters"); - optgroup->append_single_option_line("infill_first"); + optgroup = page->new_optgroup(_(L("Advanced"))); + optgroup->append_single_option_line("solid_infill_every_layers"); + optgroup->append_single_option_line("fill_angle"); + optgroup->append_single_option_line("solid_infill_below_area"); + optgroup->append_single_option_line("bridge_angle"); + optgroup->append_single_option_line("only_retract_when_crossing_perimeters"); + optgroup->append_single_option_line("infill_first"); - page = add_options_page(_(L("Skirt and brim")), "skirt+brim"); - optgroup = page->new_optgroup(_(L("Skirt"))); - optgroup->append_single_option_line("skirts"); - optgroup->append_single_option_line("skirt_distance"); - optgroup->append_single_option_line("skirt_height"); - optgroup->append_single_option_line("min_skirt_length"); + page = add_options_page(_(L("Skirt and brim")), "skirt+brim"); + optgroup = page->new_optgroup(_(L("Skirt"))); + optgroup->append_single_option_line("skirts"); + optgroup->append_single_option_line("skirt_distance"); + optgroup->append_single_option_line("skirt_height"); + optgroup->append_single_option_line("min_skirt_length"); - optgroup = page->new_optgroup(_(L("Brim"))); - optgroup->append_single_option_line("brim_width"); + optgroup = page->new_optgroup(_(L("Brim"))); + optgroup->append_single_option_line("brim_width"); - page = add_options_page(_(L("Support material")), "support"); - optgroup = page->new_optgroup(_(L("Support material"))); - optgroup->append_single_option_line("support_material"); - optgroup->append_single_option_line("support_material_auto"); - optgroup->append_single_option_line("support_material_threshold"); - optgroup->append_single_option_line("support_material_enforce_layers"); + page = add_options_page(_(L("Support material")), "support"); + optgroup = page->new_optgroup(_(L("Support material"))); + optgroup->append_single_option_line("support_material"); + optgroup->append_single_option_line("support_material_auto"); + optgroup->append_single_option_line("support_material_threshold"); + optgroup->append_single_option_line("support_material_enforce_layers"); - optgroup = page->new_optgroup(_(L("Raft"))); - optgroup->append_single_option_line("raft_layers"); + optgroup = page->new_optgroup(_(L("Raft"))); + optgroup->append_single_option_line("raft_layers"); // # optgroup->append_single_option_line(get_option_("raft_contact_distance"); - optgroup = page->new_optgroup(_(L("Options for support material and raft"))); - optgroup->append_single_option_line("support_material_contact_distance"); - optgroup->append_single_option_line("support_material_pattern"); - optgroup->append_single_option_line("support_material_with_sheath"); - optgroup->append_single_option_line("support_material_spacing"); - optgroup->append_single_option_line("support_material_angle"); - optgroup->append_single_option_line("support_material_interface_layers"); - optgroup->append_single_option_line("support_material_interface_spacing"); - optgroup->append_single_option_line("support_material_interface_contact_loops"); - optgroup->append_single_option_line("support_material_buildplate_only"); - optgroup->append_single_option_line("support_material_xy_spacing"); - optgroup->append_single_option_line("dont_support_bridges"); - optgroup->append_single_option_line("support_material_synchronize_layers"); + optgroup = page->new_optgroup(_(L("Options for support material and raft"))); + optgroup->append_single_option_line("support_material_contact_distance"); + optgroup->append_single_option_line("support_material_pattern"); + optgroup->append_single_option_line("support_material_with_sheath"); + optgroup->append_single_option_line("support_material_spacing"); + optgroup->append_single_option_line("support_material_angle"); + optgroup->append_single_option_line("support_material_interface_layers"); + optgroup->append_single_option_line("support_material_interface_spacing"); + optgroup->append_single_option_line("support_material_interface_contact_loops"); + optgroup->append_single_option_line("support_material_buildplate_only"); + optgroup->append_single_option_line("support_material_xy_spacing"); + optgroup->append_single_option_line("dont_support_bridges"); + optgroup->append_single_option_line("support_material_synchronize_layers"); - page = add_options_page(_(L("Speed")), "time"); - optgroup = page->new_optgroup(_(L("Speed for print moves"))); - optgroup->append_single_option_line("perimeter_speed"); - optgroup->append_single_option_line("small_perimeter_speed"); - optgroup->append_single_option_line("external_perimeter_speed"); - optgroup->append_single_option_line("infill_speed"); - optgroup->append_single_option_line("solid_infill_speed"); - optgroup->append_single_option_line("top_solid_infill_speed"); - optgroup->append_single_option_line("support_material_speed"); - optgroup->append_single_option_line("support_material_interface_speed"); - optgroup->append_single_option_line("bridge_speed"); - optgroup->append_single_option_line("gap_fill_speed"); + page = add_options_page(_(L("Speed")), "time"); + optgroup = page->new_optgroup(_(L("Speed for print moves"))); + optgroup->append_single_option_line("perimeter_speed"); + optgroup->append_single_option_line("small_perimeter_speed"); + optgroup->append_single_option_line("external_perimeter_speed"); + optgroup->append_single_option_line("infill_speed"); + optgroup->append_single_option_line("solid_infill_speed"); + optgroup->append_single_option_line("top_solid_infill_speed"); + optgroup->append_single_option_line("support_material_speed"); + optgroup->append_single_option_line("support_material_interface_speed"); + optgroup->append_single_option_line("bridge_speed"); + optgroup->append_single_option_line("gap_fill_speed"); - optgroup = page->new_optgroup(_(L("Speed for non-print moves"))); - optgroup->append_single_option_line("travel_speed"); + optgroup = page->new_optgroup(_(L("Speed for non-print moves"))); + optgroup->append_single_option_line("travel_speed"); - optgroup = page->new_optgroup(_(L("Modifiers"))); - optgroup->append_single_option_line("first_layer_speed"); + optgroup = page->new_optgroup(_(L("Modifiers"))); + optgroup->append_single_option_line("first_layer_speed"); - optgroup = page->new_optgroup(_(L("Acceleration control (advanced)"))); - optgroup->append_single_option_line("perimeter_acceleration"); - optgroup->append_single_option_line("infill_acceleration"); - optgroup->append_single_option_line("bridge_acceleration"); - optgroup->append_single_option_line("first_layer_acceleration"); - optgroup->append_single_option_line("default_acceleration"); + optgroup = page->new_optgroup(_(L("Acceleration control (advanced)"))); + optgroup->append_single_option_line("perimeter_acceleration"); + optgroup->append_single_option_line("infill_acceleration"); + optgroup->append_single_option_line("bridge_acceleration"); + optgroup->append_single_option_line("first_layer_acceleration"); + optgroup->append_single_option_line("default_acceleration"); - optgroup = page->new_optgroup(_(L("Autospeed (advanced)"))); - optgroup->append_single_option_line("max_print_speed"); - optgroup->append_single_option_line("max_volumetric_speed"); + optgroup = page->new_optgroup(_(L("Autospeed (advanced)"))); + optgroup->append_single_option_line("max_print_speed"); + optgroup->append_single_option_line("max_volumetric_speed"); #ifdef HAS_PRESSURE_EQUALIZER - optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_positive"); - optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_negative"); + optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_positive"); + optgroup->append_single_option_line("max_volumetric_extrusion_rate_slope_negative"); #endif /* HAS_PRESSURE_EQUALIZER */ - page = add_options_page(_(L("Multiple Extruders")), "funnel"); - optgroup = page->new_optgroup(_(L("Extruders"))); - optgroup->append_single_option_line("perimeter_extruder"); - optgroup->append_single_option_line("infill_extruder"); - optgroup->append_single_option_line("solid_infill_extruder"); - optgroup->append_single_option_line("support_material_extruder"); - optgroup->append_single_option_line("support_material_interface_extruder"); + page = add_options_page(_(L("Multiple Extruders")), "funnel"); + optgroup = page->new_optgroup(_(L("Extruders"))); + optgroup->append_single_option_line("perimeter_extruder"); + optgroup->append_single_option_line("infill_extruder"); + optgroup->append_single_option_line("solid_infill_extruder"); + optgroup->append_single_option_line("support_material_extruder"); + optgroup->append_single_option_line("support_material_interface_extruder"); - optgroup = page->new_optgroup(_(L("Ooze prevention"))); - optgroup->append_single_option_line("ooze_prevention"); - optgroup->append_single_option_line("standby_temperature_delta"); + optgroup = page->new_optgroup(_(L("Ooze prevention"))); + optgroup->append_single_option_line("ooze_prevention"); + optgroup->append_single_option_line("standby_temperature_delta"); - optgroup = page->new_optgroup(_(L("Wipe tower"))); - optgroup->append_single_option_line("wipe_tower"); - optgroup->append_single_option_line("wipe_tower_x"); - optgroup->append_single_option_line("wipe_tower_y"); - optgroup->append_single_option_line("wipe_tower_width"); - optgroup->append_single_option_line("wipe_tower_rotation_angle"); + optgroup = page->new_optgroup(_(L("Wipe tower"))); + optgroup->append_single_option_line("wipe_tower"); + optgroup->append_single_option_line("wipe_tower_x"); + optgroup->append_single_option_line("wipe_tower_y"); + optgroup->append_single_option_line("wipe_tower_width"); + optgroup->append_single_option_line("wipe_tower_rotation_angle"); optgroup->append_single_option_line("wipe_tower_bridging"); optgroup->append_single_option_line("single_extruder_multi_material_priming"); - optgroup = page->new_optgroup(_(L("Advanced"))); - optgroup->append_single_option_line("interface_shells"); + optgroup = page->new_optgroup(_(L("Advanced"))); + optgroup->append_single_option_line("interface_shells"); - page = add_options_page(_(L("Advanced")), "wrench"); - optgroup = page->new_optgroup(_(L("Extrusion width"))); - optgroup->append_single_option_line("extrusion_width"); - optgroup->append_single_option_line("first_layer_extrusion_width"); - optgroup->append_single_option_line("perimeter_extrusion_width"); - optgroup->append_single_option_line("external_perimeter_extrusion_width"); - optgroup->append_single_option_line("infill_extrusion_width"); - optgroup->append_single_option_line("solid_infill_extrusion_width"); - optgroup->append_single_option_line("top_infill_extrusion_width"); - optgroup->append_single_option_line("support_material_extrusion_width"); + page = add_options_page(_(L("Advanced")), "wrench"); + optgroup = page->new_optgroup(_(L("Extrusion width"))); + optgroup->append_single_option_line("extrusion_width"); + optgroup->append_single_option_line("first_layer_extrusion_width"); + optgroup->append_single_option_line("perimeter_extrusion_width"); + optgroup->append_single_option_line("external_perimeter_extrusion_width"); + optgroup->append_single_option_line("infill_extrusion_width"); + optgroup->append_single_option_line("solid_infill_extrusion_width"); + optgroup->append_single_option_line("top_infill_extrusion_width"); + optgroup->append_single_option_line("support_material_extrusion_width"); - optgroup = page->new_optgroup(_(L("Overlap"))); - optgroup->append_single_option_line("infill_overlap"); + optgroup = page->new_optgroup(_(L("Overlap"))); + optgroup->append_single_option_line("infill_overlap"); - optgroup = page->new_optgroup(_(L("Flow"))); - optgroup->append_single_option_line("bridge_flow_ratio"); + optgroup = page->new_optgroup(_(L("Flow"))); + optgroup->append_single_option_line("bridge_flow_ratio"); - optgroup = page->new_optgroup(_(L("Slicing"))); - optgroup->append_single_option_line("slice_closing_radius"); - optgroup->append_single_option_line("resolution"); - optgroup->append_single_option_line("xy_size_compensation"); - optgroup->append_single_option_line("elefant_foot_compensation"); + optgroup = page->new_optgroup(_(L("Slicing"))); + optgroup->append_single_option_line("slice_closing_radius"); + optgroup->append_single_option_line("resolution"); + optgroup->append_single_option_line("xy_size_compensation"); + optgroup->append_single_option_line("elefant_foot_compensation"); - optgroup = page->new_optgroup(_(L("Other"))); - optgroup->append_single_option_line("clip_multipart_objects"); + optgroup = page->new_optgroup(_(L("Other"))); + optgroup->append_single_option_line("clip_multipart_objects"); - page = add_options_page(_(L("Output options")), "output+page_white"); - optgroup = page->new_optgroup(_(L("Sequential printing"))); - optgroup->append_single_option_line("complete_objects"); - line = { _(L("Extruder clearance (mm)")), "" }; - Option option = optgroup->get_option("extruder_clearance_radius"); - option.opt.width = 6; - line.append_option(option); - option = optgroup->get_option("extruder_clearance_height"); - option.opt.width = 6; - line.append_option(option); - optgroup->append_line(line); + page = add_options_page(_(L("Output options")), "output+page_white"); + optgroup = page->new_optgroup(_(L("Sequential printing"))); + optgroup->append_single_option_line("complete_objects"); + line = { _(L("Extruder clearance (mm)")), "" }; + Option option = optgroup->get_option("extruder_clearance_radius"); + option.opt.width = 6; + line.append_option(option); + option = optgroup->get_option("extruder_clearance_height"); + option.opt.width = 6; + line.append_option(option); + optgroup->append_line(line); - optgroup = page->new_optgroup(_(L("Output file"))); - optgroup->append_single_option_line("gcode_comments"); - optgroup->append_single_option_line("gcode_label_objects"); - option = optgroup->get_option("output_filename_format"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(_(L("Output file"))); + optgroup->append_single_option_line("gcode_comments"); + optgroup->append_single_option_line("gcode_label_objects"); + option = optgroup->get_option("output_filename_format"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("Post-processing scripts")), 0); - option = optgroup->get_option("post_process"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("Post-processing scripts")), 0); + option = optgroup->get_option("post_process"); + option.opt.full_width = true; option.opt.height = 5;//50; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - page = add_options_page(_(L("Notes")), "note.png"); - optgroup = page->new_optgroup(_(L("Notes")), 0); - option = optgroup->get_option("notes"); - option.opt.full_width = true; + page = add_options_page(_(L("Notes")), "note.png"); + optgroup = page->new_optgroup(_(L("Notes")), 0); + option = optgroup->get_option("notes"); + option.opt.full_width = true; option.opt.height = 25;//250; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - page = add_options_page(_(L("Dependencies")), "wrench.png"); - optgroup = page->new_optgroup(_(L("Profile dependencies"))); + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); line = optgroup->create_single_option_line("compatible_printers"); line.widget = [this](wxWindow* parent) { - return compatible_widget_create(parent, m_compatible_printers); - }; - optgroup->append_line(line, &m_colored_Label); - option = optgroup->get_option("compatible_printers_condition"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + return compatible_widget_create(parent, m_compatible_printers); + }; + optgroup->append_line(line, &m_colored_Label); + option = optgroup->get_option("compatible_printers_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); - line = Line{ "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_parent_preset_description_line); - }; - optgroup->append_line(line); + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); } // Reload current config (aka presets->edited_preset->config) into the UI fields. void TabPrint::reload_config() { - this->compatible_widget_reload(m_compatible_printers); - Tab::reload_config(); + this->compatible_widget_reload(m_compatible_printers); + Tab::reload_config(); } void TabPrint::update() @@ -1275,212 +1275,212 @@ void TabPrint::update() is_msg_dlg_already_exist = false; } - double fill_density = m_config->option("fill_density")->value; + double fill_density = m_config->option("fill_density")->value; - if (m_config->opt_bool("spiral_vase") && - !(m_config->opt_int("perimeters") == 1 && m_config->opt_int("top_solid_layers") == 0 && - fill_density == 0)) { - wxString msg_text = _(L("The Spiral Vase mode requires:\n" - "- one perimeter\n" - "- no top solid layers\n" - "- 0% fill density\n" - "- no support material\n" - "- no ensure_vertical_shell_thickness\n" - "\nShall I adjust those settings in order to enable Spiral Vase?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Spiral Vase")), wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { - new_conf.set_key_value("perimeters", new ConfigOptionInt(1)); - new_conf.set_key_value("top_solid_layers", new ConfigOptionInt(0)); - new_conf.set_key_value("fill_density", new ConfigOptionPercent(0)); - new_conf.set_key_value("support_material", new ConfigOptionBool(false)); - new_conf.set_key_value("support_material_enforce_layers", new ConfigOptionInt(0)); - new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionBool(false)); - fill_density = 0; - } - else { - new_conf.set_key_value("spiral_vase", new ConfigOptionBool(false)); - } - load_config(new_conf); - on_value_change("fill_density", fill_density); - } + if (m_config->opt_bool("spiral_vase") && + !(m_config->opt_int("perimeters") == 1 && m_config->opt_int("top_solid_layers") == 0 && + fill_density == 0)) { + wxString msg_text = _(L("The Spiral Vase mode requires:\n" + "- one perimeter\n" + "- no top solid layers\n" + "- 0% fill density\n" + "- no support material\n" + "- no ensure_vertical_shell_thickness\n" + "\nShall I adjust those settings in order to enable Spiral Vase?")); + auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Spiral Vase")), wxICON_WARNING | wxYES | wxNO); + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_YES) { + new_conf.set_key_value("perimeters", new ConfigOptionInt(1)); + new_conf.set_key_value("top_solid_layers", new ConfigOptionInt(0)); + new_conf.set_key_value("fill_density", new ConfigOptionPercent(0)); + new_conf.set_key_value("support_material", new ConfigOptionBool(false)); + new_conf.set_key_value("support_material_enforce_layers", new ConfigOptionInt(0)); + new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionBool(false)); + fill_density = 0; + } + else { + new_conf.set_key_value("spiral_vase", new ConfigOptionBool(false)); + } + load_config(new_conf); + on_value_change("fill_density", fill_density); + } - if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") && - m_config->opt_float("support_material_contact_distance") > 0. && - (m_config->opt_int("support_material_extruder") != 0 || m_config->opt_int("support_material_interface_extruder") != 0)) { - wxString msg_text = _(L("The Wipe Tower currently supports the non-soluble supports only\n" - "if they are printed with the current extruder without triggering a tool change.\n" - "(both support_material_extruder and support_material_interface_extruder need to be set to 0).\n" - "\nShall I adjust those settings in order to enable the Wipe Tower?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { - new_conf.set_key_value("support_material_extruder", new ConfigOptionInt(0)); - new_conf.set_key_value("support_material_interface_extruder", new ConfigOptionInt(0)); - } - else - new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false)); - load_config(new_conf); - } + if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") && + m_config->opt_float("support_material_contact_distance") > 0. && + (m_config->opt_int("support_material_extruder") != 0 || m_config->opt_int("support_material_interface_extruder") != 0)) { + wxString msg_text = _(L("The Wipe Tower currently supports the non-soluble supports only\n" + "if they are printed with the current extruder without triggering a tool change.\n" + "(both support_material_extruder and support_material_interface_extruder need to be set to 0).\n" + "\nShall I adjust those settings in order to enable the Wipe Tower?")); + auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_YES) { + new_conf.set_key_value("support_material_extruder", new ConfigOptionInt(0)); + new_conf.set_key_value("support_material_interface_extruder", new ConfigOptionInt(0)); + } + else + new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false)); + load_config(new_conf); + } - if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") && - m_config->opt_float("support_material_contact_distance") == 0 && - !m_config->opt_bool("support_material_synchronize_layers")) { - wxString msg_text = _(L("For the Wipe Tower to work with the soluble supports, the support layers\n" - "need to be synchronized with the object layers.\n" - "\nShall I synchronize support layers in order to enable the Wipe Tower?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { - new_conf.set_key_value("support_material_synchronize_layers", new ConfigOptionBool(true)); - } - else - new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false)); - load_config(new_conf); - } + if (m_config->opt_bool("wipe_tower") && m_config->opt_bool("support_material") && + m_config->opt_float("support_material_contact_distance") == 0 && + !m_config->opt_bool("support_material_synchronize_layers")) { + wxString msg_text = _(L("For the Wipe Tower to work with the soluble supports, the support layers\n" + "need to be synchronized with the object layers.\n" + "\nShall I synchronize support layers in order to enable the Wipe Tower?")); + auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_YES) { + new_conf.set_key_value("support_material_synchronize_layers", new ConfigOptionBool(true)); + } + else + new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false)); + load_config(new_conf); + } - if (m_config->opt_bool("support_material")) { - // Ask only once. - if (!m_support_material_overhangs_queried) { - m_support_material_overhangs_queried = true; - if (!m_config->opt_bool("overhangs")/* != 1*/) { - wxString msg_text = _(L("Supports work better, if the following feature is enabled:\n" - "- Detect bridging perimeters\n" - "\nShall I adjust those settings for supports?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Support Generator")), wxICON_WARNING | wxYES | wxNO | wxCANCEL); - DynamicPrintConfig new_conf = *m_config; - auto answer = dialog->ShowModal(); - if (answer == wxID_YES) { - // Enable "detect bridging perimeters". - new_conf.set_key_value("overhangs", new ConfigOptionBool(true)); - } else if (answer == wxID_NO) { - // Do nothing, leave supports on and "detect bridging perimeters" off. - } else if (answer == wxID_CANCEL) { - // Disable supports. - new_conf.set_key_value("support_material", new ConfigOptionBool(false)); - m_support_material_overhangs_queried = false; - } - load_config(new_conf); - } - } - } - else { - m_support_material_overhangs_queried = false; - } + if (m_config->opt_bool("support_material")) { + // Ask only once. + if (!m_support_material_overhangs_queried) { + m_support_material_overhangs_queried = true; + if (!m_config->opt_bool("overhangs")/* != 1*/) { + wxString msg_text = _(L("Supports work better, if the following feature is enabled:\n" + "- Detect bridging perimeters\n" + "\nShall I adjust those settings for supports?")); + auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Support Generator")), wxICON_WARNING | wxYES | wxNO | wxCANCEL); + DynamicPrintConfig new_conf = *m_config; + auto answer = dialog->ShowModal(); + if (answer == wxID_YES) { + // Enable "detect bridging perimeters". + new_conf.set_key_value("overhangs", new ConfigOptionBool(true)); + } else if (answer == wxID_NO) { + // Do nothing, leave supports on and "detect bridging perimeters" off. + } else if (answer == wxID_CANCEL) { + // Disable supports. + new_conf.set_key_value("support_material", new ConfigOptionBool(false)); + m_support_material_overhangs_queried = false; + } + load_config(new_conf); + } + } + } + else { + m_support_material_overhangs_queried = false; + } - if (m_config->option("fill_density")->value == 100) { - auto fill_pattern = m_config->option>("fill_pattern")->value; - std::string str_fill_pattern = ""; - t_config_enum_values map_names = m_config->option>("fill_pattern")->get_enum_values(); - for (auto it : map_names) { - if (fill_pattern == it.second) { - str_fill_pattern = it.first; - break; - } - } - if (!str_fill_pattern.empty()) { - const std::vector &external_fill_pattern = m_config->def()->get("top_fill_pattern")->enum_values; - bool correct_100p_fill = false; - for (const std::string &fill : external_fill_pattern) - { - if (str_fill_pattern == fill) - correct_100p_fill = true; - } - // get fill_pattern name from enum_labels for using this one at dialog_msg - str_fill_pattern = _utf8(m_config->def()->get("fill_pattern")->enum_labels[fill_pattern]); - if (!correct_100p_fill) { - wxString msg_text = GUI::from_u8((boost::format(_utf8(L("The %1% infill pattern is not supposed to work at 100%% density.\n\n" - "Shall I switch to rectilinear fill pattern?"))) % str_fill_pattern).str()); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Infill")), wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { - new_conf.set_key_value("fill_pattern", new ConfigOptionEnum(ipRectilinear)); - fill_density = 100; - } - else - fill_density = m_presets->get_selected_preset().config.option("fill_density")->value; - new_conf.set_key_value("fill_density", new ConfigOptionPercent(fill_density)); - load_config(new_conf); - on_value_change("fill_density", fill_density); - } - } - } + if (m_config->option("fill_density")->value == 100) { + auto fill_pattern = m_config->option>("fill_pattern")->value; + std::string str_fill_pattern = ""; + t_config_enum_values map_names = m_config->option>("fill_pattern")->get_enum_values(); + for (auto it : map_names) { + if (fill_pattern == it.second) { + str_fill_pattern = it.first; + break; + } + } + if (!str_fill_pattern.empty()) { + const std::vector &external_fill_pattern = m_config->def()->get("top_fill_pattern")->enum_values; + bool correct_100p_fill = false; + for (const std::string &fill : external_fill_pattern) + { + if (str_fill_pattern == fill) + correct_100p_fill = true; + } + // get fill_pattern name from enum_labels for using this one at dialog_msg + str_fill_pattern = _utf8(m_config->def()->get("fill_pattern")->enum_labels[fill_pattern]); + if (!correct_100p_fill) { + wxString msg_text = GUI::from_u8((boost::format(_utf8(L("The %1% infill pattern is not supposed to work at 100%% density.\n\n" + "Shall I switch to rectilinear fill pattern?"))) % str_fill_pattern).str()); + auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Infill")), wxICON_WARNING | wxYES | wxNO); + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_YES) { + new_conf.set_key_value("fill_pattern", new ConfigOptionEnum(ipRectilinear)); + fill_density = 100; + } + else + fill_density = m_presets->get_selected_preset().config.option("fill_density")->value; + new_conf.set_key_value("fill_density", new ConfigOptionPercent(fill_density)); + load_config(new_conf); + on_value_change("fill_density", fill_density); + } + } + } - bool have_perimeters = m_config->opt_int("perimeters") > 0; - for (auto el : {"extra_perimeters", "ensure_vertical_shell_thickness", "thin_walls", "overhangs", - "seam_position", "external_perimeters_first", "external_perimeter_extrusion_width", - "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" }) - get_field(el)->toggle(have_perimeters); + bool have_perimeters = m_config->opt_int("perimeters") > 0; + for (auto el : {"extra_perimeters", "ensure_vertical_shell_thickness", "thin_walls", "overhangs", + "seam_position", "external_perimeters_first", "external_perimeter_extrusion_width", + "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed" }) + get_field(el)->toggle(have_perimeters); - bool have_infill = m_config->option("fill_density")->value > 0; - // infill_extruder uses the same logic as in Print::extruders() - for (auto el : {"fill_pattern", "infill_every_layers", "infill_only_where_needed", - "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder" }) - get_field(el)->toggle(have_infill); + bool have_infill = m_config->option("fill_density")->value > 0; + // infill_extruder uses the same logic as in Print::extruders() + for (auto el : {"fill_pattern", "infill_every_layers", "infill_only_where_needed", + "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder" }) + get_field(el)->toggle(have_infill); - bool have_solid_infill = m_config->opt_int("top_solid_layers") > 0 || m_config->opt_int("bottom_solid_layers") > 0; - // solid_infill_extruder uses the same logic as in Print::extruders() - for (auto el : {"top_fill_pattern", "bottom_fill_pattern", "infill_first", "solid_infill_extruder", - "solid_infill_extrusion_width", "solid_infill_speed" }) - get_field(el)->toggle(have_solid_infill); + bool have_solid_infill = m_config->opt_int("top_solid_layers") > 0 || m_config->opt_int("bottom_solid_layers") > 0; + // solid_infill_extruder uses the same logic as in Print::extruders() + for (auto el : {"top_fill_pattern", "bottom_fill_pattern", "infill_first", "solid_infill_extruder", + "solid_infill_extrusion_width", "solid_infill_speed" }) + get_field(el)->toggle(have_solid_infill); - for (auto el : {"fill_angle", "bridge_angle", "infill_extrusion_width", - "infill_speed", "bridge_speed" }) - get_field(el)->toggle(have_infill || have_solid_infill); + for (auto el : {"fill_angle", "bridge_angle", "infill_extrusion_width", + "infill_speed", "bridge_speed" }) + get_field(el)->toggle(have_infill || have_solid_infill); - get_field("gap_fill_speed")->toggle(have_perimeters && have_infill); + get_field("gap_fill_speed")->toggle(have_perimeters && have_infill); - bool have_top_solid_infill = m_config->opt_int("top_solid_layers") > 0; - for (auto el : { "top_infill_extrusion_width", "top_solid_infill_speed" }) - get_field(el)->toggle(have_top_solid_infill); + bool have_top_solid_infill = m_config->opt_int("top_solid_layers") > 0; + for (auto el : { "top_infill_extrusion_width", "top_solid_infill_speed" }) + get_field(el)->toggle(have_top_solid_infill); - bool have_default_acceleration = m_config->opt_float("default_acceleration") > 0; - for (auto el : {"perimeter_acceleration", "infill_acceleration", - "bridge_acceleration", "first_layer_acceleration" }) - get_field(el)->toggle(have_default_acceleration); + bool have_default_acceleration = m_config->opt_float("default_acceleration") > 0; + for (auto el : {"perimeter_acceleration", "infill_acceleration", + "bridge_acceleration", "first_layer_acceleration" }) + get_field(el)->toggle(have_default_acceleration); - bool have_skirt = m_config->opt_int("skirts") > 0 || m_config->opt_float("min_skirt_length") > 0; - for (auto el : { "skirt_distance", "skirt_height" }) - get_field(el)->toggle(have_skirt); + bool have_skirt = m_config->opt_int("skirts") > 0 || m_config->opt_float("min_skirt_length") > 0; + for (auto el : { "skirt_distance", "skirt_height" }) + get_field(el)->toggle(have_skirt); - bool have_brim = m_config->opt_float("brim_width") > 0; - // perimeter_extruder uses the same logic as in Print::extruders() - get_field("perimeter_extruder")->toggle(have_perimeters || have_brim); + bool have_brim = m_config->opt_float("brim_width") > 0; + // perimeter_extruder uses the same logic as in Print::extruders() + get_field("perimeter_extruder")->toggle(have_perimeters || have_brim); - bool have_raft = m_config->opt_int("raft_layers") > 0; - bool have_support_material = m_config->opt_bool("support_material") || have_raft; - bool have_support_material_auto = have_support_material && m_config->opt_bool("support_material_auto"); - bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0; - bool have_support_soluble = have_support_material && m_config->opt_float("support_material_contact_distance") == 0; - for (auto el : {"support_material_pattern", "support_material_with_sheath", - "support_material_spacing", "support_material_angle", "support_material_interface_layers", - "dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance", - "support_material_xy_spacing" }) - get_field(el)->toggle(have_support_material); - get_field("support_material_threshold")->toggle(have_support_material_auto); + bool have_raft = m_config->opt_int("raft_layers") > 0; + bool have_support_material = m_config->opt_bool("support_material") || have_raft; + bool have_support_material_auto = have_support_material && m_config->opt_bool("support_material_auto"); + bool have_support_interface = m_config->opt_int("support_material_interface_layers") > 0; + bool have_support_soluble = have_support_material && m_config->opt_float("support_material_contact_distance") == 0; + for (auto el : {"support_material_pattern", "support_material_with_sheath", + "support_material_spacing", "support_material_angle", "support_material_interface_layers", + "dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance", + "support_material_xy_spacing" }) + get_field(el)->toggle(have_support_material); + get_field("support_material_threshold")->toggle(have_support_material_auto); - for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder", - "support_material_interface_speed", "support_material_interface_contact_loops" }) - get_field(el)->toggle(have_support_material && have_support_interface); - get_field("support_material_synchronize_layers")->toggle(have_support_soluble); + for (auto el : {"support_material_interface_spacing", "support_material_interface_extruder", + "support_material_interface_speed", "support_material_interface_contact_loops" }) + get_field(el)->toggle(have_support_material && have_support_interface); + get_field("support_material_synchronize_layers")->toggle(have_support_soluble); - get_field("perimeter_extrusion_width")->toggle(have_perimeters || have_skirt || have_brim); - get_field("support_material_extruder")->toggle(have_support_material || have_skirt); - get_field("support_material_speed")->toggle(have_support_material || have_brim || have_skirt); + get_field("perimeter_extrusion_width")->toggle(have_perimeters || have_skirt || have_brim); + get_field("support_material_extruder")->toggle(have_support_material || have_skirt); + get_field("support_material_speed")->toggle(have_support_material || have_brim || have_skirt); - bool have_sequential_printing = m_config->opt_bool("complete_objects"); - for (auto el : { "extruder_clearance_radius", "extruder_clearance_height" }) - get_field(el)->toggle(have_sequential_printing); + bool have_sequential_printing = m_config->opt_bool("complete_objects"); + for (auto el : { "extruder_clearance_radius", "extruder_clearance_height" }) + get_field(el)->toggle(have_sequential_printing); - bool have_ooze_prevention = m_config->opt_bool("ooze_prevention"); - get_field("standby_temperature_delta")->toggle(have_ooze_prevention); + bool have_ooze_prevention = m_config->opt_bool("ooze_prevention"); + get_field("standby_temperature_delta")->toggle(have_ooze_prevention); - bool have_wipe_tower = m_config->opt_bool("wipe_tower"); - for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging"}) - get_field(el)->toggle(have_wipe_tower); + bool have_wipe_tower = m_config->opt_bool("wipe_tower"); + for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging"}) + get_field(el)->toggle(have_wipe_tower); - m_recommended_thin_wall_thickness_description_line->SetText( - from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); + m_recommended_thin_wall_thickness_description_line->SetText( + from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); Layout(); // Thaw(); @@ -1492,9 +1492,9 @@ void TabPrint::update() void TabPrint::OnActivate() { - m_recommended_thin_wall_thickness_description_line->SetText( - from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); - Tab::OnActivate(); + m_recommended_thin_wall_thickness_description_line->SetText( + from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); + Tab::OnActivate(); } void TabFilament::add_filament_overrides_page() @@ -1564,9 +1564,9 @@ void TabFilament::update_filament_overrides_page() return; ConfigOptionsGroupShp optgroup = *og_it; - std::vector opt_keys = { "filament_retract_length", - "filament_retract_lift", - "filament_retract_lift_above", + std::vector opt_keys = { "filament_retract_length", + "filament_retract_lift", + "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", @@ -1598,79 +1598,79 @@ void TabFilament::update_filament_overrides_page() void TabFilament::build() { - m_presets = &m_preset_bundle->filaments; - load_initial_data(); + m_presets = &m_preset_bundle->filaments; + load_initial_data(); - auto page = add_options_page(_(L("Filament")), "spool.png"); - auto optgroup = page->new_optgroup(_(L("Filament"))); - optgroup->append_single_option_line("filament_colour"); - optgroup->append_single_option_line("filament_diameter"); - optgroup->append_single_option_line("extrusion_multiplier"); - optgroup->append_single_option_line("filament_density"); - optgroup->append_single_option_line("filament_cost"); + auto page = add_options_page(_(L("Filament")), "spool.png"); + auto optgroup = page->new_optgroup(_(L("Filament"))); + optgroup->append_single_option_line("filament_colour"); + optgroup->append_single_option_line("filament_diameter"); + optgroup->append_single_option_line("extrusion_multiplier"); + optgroup->append_single_option_line("filament_density"); + optgroup->append_single_option_line("filament_cost"); - optgroup = page->new_optgroup(_(L("Temperature")) + wxString(" °C", wxConvUTF8)); - Line line = { _(L("Extruder")), "" }; - line.append_option(optgroup->get_option("first_layer_temperature")); - line.append_option(optgroup->get_option("temperature")); - optgroup->append_line(line); + optgroup = page->new_optgroup(_(L("Temperature")) + wxString(" °C", wxConvUTF8)); + Line line = { _(L("Extruder")), "" }; + line.append_option(optgroup->get_option("first_layer_temperature")); + line.append_option(optgroup->get_option("temperature")); + optgroup->append_line(line); - line = { _(L("Bed")), "" }; - line.append_option(optgroup->get_option("first_layer_bed_temperature")); - line.append_option(optgroup->get_option("bed_temperature")); - optgroup->append_line(line); + line = { _(L("Bed")), "" }; + line.append_option(optgroup->get_option("first_layer_bed_temperature")); + line.append_option(optgroup->get_option("bed_temperature")); + optgroup->append_line(line); - page = add_options_page(_(L("Cooling")), "cooling"); - optgroup = page->new_optgroup(_(L("Enable"))); - optgroup->append_single_option_line("fan_always_on"); - optgroup->append_single_option_line("cooling"); + page = add_options_page(_(L("Cooling")), "cooling"); + optgroup = page->new_optgroup(_(L("Enable"))); + optgroup->append_single_option_line("fan_always_on"); + optgroup->append_single_option_line("cooling"); - line = { "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_cooling_description_line); - }; - optgroup->append_line(line); + line = { "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_cooling_description_line); + }; + optgroup->append_line(line); - optgroup = page->new_optgroup(_(L("Fan settings"))); - line = { _(L("Fan speed")), "" }; - line.append_option(optgroup->get_option("min_fan_speed")); - line.append_option(optgroup->get_option("max_fan_speed")); - optgroup->append_line(line); + optgroup = page->new_optgroup(_(L("Fan settings"))); + line = { _(L("Fan speed")), "" }; + line.append_option(optgroup->get_option("min_fan_speed")); + line.append_option(optgroup->get_option("max_fan_speed")); + optgroup->append_line(line); - optgroup->append_single_option_line("bridge_fan_speed"); - optgroup->append_single_option_line("disable_fan_first_layers"); + optgroup->append_single_option_line("bridge_fan_speed"); + optgroup->append_single_option_line("disable_fan_first_layers"); - optgroup = page->new_optgroup(_(L("Cooling thresholds")), 25); - optgroup->append_single_option_line("fan_below_layer_time"); - optgroup->append_single_option_line("slowdown_below_layer_time"); - optgroup->append_single_option_line("min_print_speed"); + optgroup = page->new_optgroup(_(L("Cooling thresholds")), 25); + optgroup->append_single_option_line("fan_below_layer_time"); + optgroup->append_single_option_line("slowdown_below_layer_time"); + optgroup->append_single_option_line("min_print_speed"); - page = add_options_page(_(L("Advanced")), "wrench"); - optgroup = page->new_optgroup(_(L("Filament properties"))); - optgroup->append_single_option_line("filament_type"); - optgroup->append_single_option_line("filament_soluble"); + page = add_options_page(_(L("Advanced")), "wrench"); + optgroup = page->new_optgroup(_(L("Filament properties"))); + optgroup->append_single_option_line("filament_type"); + optgroup->append_single_option_line("filament_soluble"); - optgroup = page->new_optgroup(_(L("Print speed override"))); - optgroup->append_single_option_line("filament_max_volumetric_speed"); + optgroup = page->new_optgroup(_(L("Print speed override"))); + optgroup->append_single_option_line("filament_max_volumetric_speed"); - line = { "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_volumetric_speed_description_line); - }; - optgroup->append_line(line); + line = { "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_volumetric_speed_description_line); + }; + optgroup->append_line(line); optgroup = page->new_optgroup(_(L("Wipe tower parameters"))); optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower"); optgroup = page->new_optgroup(_(L("Toolchange parameters with single extruder MM printers"))); - optgroup->append_single_option_line("filament_loading_speed_start"); + optgroup->append_single_option_line("filament_loading_speed_start"); optgroup->append_single_option_line("filament_loading_speed"); optgroup->append_single_option_line("filament_unloading_speed_start"); optgroup->append_single_option_line("filament_unloading_speed"); - optgroup->append_single_option_line("filament_load_time"); - optgroup->append_single_option_line("filament_unload_time"); + optgroup->append_single_option_line("filament_load_time"); + optgroup->append_single_option_line("filament_unload_time"); optgroup->append_single_option_line("filament_toolchange_delay"); optgroup->append_single_option_line("filament_cooling_moves"); optgroup->append_single_option_line("filament_cooling_initial_speed"); @@ -1678,20 +1678,20 @@ void TabFilament::build() line = optgroup->create_single_option_line("filament_ramming_parameters");// { _(L("Ramming")), "" }; line.widget = [this](wxWindow* parent) { - auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); - ramming_dialog_btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + ramming_dialog_btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(ramming_dialog_btn); - + sizer->Add(ramming_dialog_btn); + ramming_dialog_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { + { RammingDialog dlg(this,(m_config->option("filament_ramming_parameters"))->get_at(0)); if (dlg.ShowModal() == wxID_OK) (m_config->option("filament_ramming_parameters"))->get_at(0) = dlg.get_parameters(); - })); - return sizer; - }; - optgroup->append_line(line); + })); + return sizer; + }; + optgroup->append_line(line); add_filament_overrides_page(); @@ -1701,61 +1701,61 @@ void TabFilament::build() const int notes_field_height = 25; // 250 page = add_options_page(_(L("Custom G-code")), "cog"); - optgroup = page->new_optgroup(_(L("Start G-code")), 0); - Option option = optgroup->get_option("start_filament_gcode"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("Start G-code")), 0); + Option option = optgroup->get_option("start_filament_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;// 150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("End G-code")), 0); - option = optgroup->get_option("end_filament_gcode"); - option.opt.full_width = true; - option.opt.height = gcode_field_height;// 150; - optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(_(L("End G-code")), 0); + option = optgroup->get_option("end_filament_gcode"); + option.opt.full_width = true; + option.opt.height = gcode_field_height;// 150; + optgroup->append_single_option_line(option); - page = add_options_page(_(L("Notes")), "note.png"); - optgroup = page->new_optgroup(_(L("Notes")), 0); - optgroup->label_width = 0; - option = optgroup->get_option("filament_notes"); - option.opt.full_width = true; - option.opt.height = notes_field_height;// 250; - optgroup->append_single_option_line(option); + page = add_options_page(_(L("Notes")), "note.png"); + optgroup = page->new_optgroup(_(L("Notes")), 0); + optgroup->label_width = 0; + option = optgroup->get_option("filament_notes"); + option.opt.full_width = true; + option.opt.height = notes_field_height;// 250; + optgroup->append_single_option_line(option); + + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); - page = add_options_page(_(L("Dependencies")), "wrench.png"); - optgroup = page->new_optgroup(_(L("Profile dependencies"))); - line = optgroup->create_single_option_line("compatible_printers"); line.widget = [this](wxWindow* parent) { - return compatible_widget_create(parent, m_compatible_printers); - }; - optgroup->append_line(line, &m_colored_Label); - option = optgroup->get_option("compatible_printers_condition"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + return compatible_widget_create(parent, m_compatible_printers); + }; + optgroup->append_line(line, &m_colored_Label); + option = optgroup->get_option("compatible_printers_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); line = optgroup->create_single_option_line("compatible_prints"); line.widget = [this](wxWindow* parent) { - return compatible_widget_create(parent, m_compatible_prints); - }; - optgroup->append_line(line, &m_colored_Label); - option = optgroup->get_option("compatible_prints_condition"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + return compatible_widget_create(parent, m_compatible_prints); + }; + optgroup->append_line(line, &m_colored_Label); + option = optgroup->get_option("compatible_prints_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); - line = Line{ "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_parent_preset_description_line); - }; - optgroup->append_line(line); + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); } // Reload current config (aka presets->edited_preset->config) into the UI fields. void TabFilament::reload_config() { - this->compatible_widget_reload(m_compatible_printers); - this->compatible_widget_reload(m_compatible_prints); - Tab::reload_config(); + this->compatible_widget_reload(m_compatible_printers); + this->compatible_widget_reload(m_compatible_prints); + Tab::reload_config(); } void TabFilament::update() @@ -1765,20 +1765,20 @@ void TabFilament::update() m_update_cnt++; - wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); - m_cooling_description_line->SetText(text); - text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); - m_volumetric_speed_description_line->SetText(text); + wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); + m_cooling_description_line->SetText(text); + text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); + m_volumetric_speed_description_line->SetText(text); Layout(); - bool cooling = m_config->opt_bool("cooling", 0); - bool fan_always_on = cooling || m_config->opt_bool("fan_always_on", 0); + bool cooling = m_config->opt_bool("cooling", 0); + bool fan_always_on = cooling || m_config->opt_bool("fan_always_on", 0); - for (auto el : { "max_fan_speed", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed" }) - get_field(el)->toggle(cooling); + for (auto el : { "max_fan_speed", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed" }) + get_field(el)->toggle(cooling); - for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) - get_field(el)->toggle(fan_always_on); + for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) + get_field(el)->toggle(fan_always_on); update_filament_overrides_page(); @@ -1790,147 +1790,147 @@ void TabFilament::update() void TabFilament::OnActivate() { - m_volumetric_speed_description_line->SetText(from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle))); - Tab::OnActivate(); + m_volumetric_speed_description_line->SetText(from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle))); + Tab::OnActivate(); } wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticText) { - *StaticText = new ogStaticText(parent, ""); + *StaticText = new ogStaticText(parent, ""); // auto font = (new wxSystemSettings)->GetFont(wxSYS_DEFAULT_GUI_FONT); - (*StaticText)->SetFont(wxGetApp().normal_font()); + (*StaticText)->SetFont(wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(*StaticText, 1, wxEXPAND|wxALL, 0); - return sizer; + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(*StaticText, 1, wxEXPAND|wxALL, 0); + return sizer; } bool Tab::current_preset_is_dirty() { - return m_presets->current_is_dirty(); + return m_presets->current_is_dirty(); } void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup) { - const PrinterTechnology tech = m_presets->get_selected_preset().printer_technology(); + const PrinterTechnology tech = m_presets->get_selected_preset().printer_technology(); - // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) - if (tech == ptFFF) { - optgroup->append_single_option_line("host_type"); - } + // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) + if (tech == ptFFF) { + optgroup->append_single_option_line("host_type"); + } - auto printhost_browse = [=](wxWindow* parent) { + auto printhost_browse = [=](wxWindow* parent) { add_scaled_button(parent, &m_printhost_browse_btn, "browse", _(L("Browse")) + " "+ dots, wxBU_LEFT | wxBU_EXACTFIT); ScalableButton* btn = m_printhost_browse_btn; - btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { - BonjourDialog dialog(parent, tech); - if (dialog.show_and_lookup()) { - optgroup->set_value("print_host", std::move(dialog.get_selected()), true); - optgroup->get_field("print_host")->field_changed(); - } - }); + btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { + BonjourDialog dialog(parent, tech); + if (dialog.show_and_lookup()) { + optgroup->set_value("print_host", std::move(dialog.get_selected()), true); + optgroup->get_field("print_host")->field_changed(); + } + }); - return sizer; - }; + return sizer; + }; - auto print_host_test = [this](wxWindow* parent) { + auto print_host_test = [this](wxWindow* parent) { add_scaled_button(parent, &m_print_host_test_btn, "test", _(L("Test")), wxBU_LEFT | wxBU_EXACTFIT); ScalableButton* btn = m_print_host_test_btn; btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { - std::unique_ptr host(PrintHost::get_print_host(m_config)); - if (! host) { - const auto text = wxString::Format("%s", - _(L("Could not get a valid Printer Host reference"))); - show_error(this, text); - return; - } - wxString msg; - if (host->test(msg)) { - show_info(this, host->get_test_ok_msg(), _(L("Success!"))); - } else { - show_error(this, host->get_test_failed_msg(msg)); - } - }); + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { + std::unique_ptr host(PrintHost::get_print_host(m_config)); + if (! host) { + const auto text = wxString::Format("%s", + _(L("Could not get a valid Printer Host reference"))); + show_error(this, text); + return; + } + wxString msg; + if (host->test(msg)) { + show_info(this, host->get_test_ok_msg(), _(L("Success!"))); + } else { + show_error(this, host->get_test_failed_msg(msg)); + } + }); - return sizer; - }; + return sizer; + }; - Line host_line = optgroup->create_single_option_line("print_host"); - host_line.append_widget(printhost_browse); - host_line.append_widget(print_host_test); - optgroup->append_line(host_line); - optgroup->append_single_option_line("printhost_apikey"); + Line host_line = optgroup->create_single_option_line("print_host"); + host_line.append_widget(printhost_browse); + host_line.append_widget(print_host_test); + optgroup->append_line(host_line); + optgroup->append_single_option_line("printhost_apikey"); - const auto ca_file_hint = _(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.")); + const auto ca_file_hint = _(L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.")); - if (Http::ca_file_supported()) { - Line cafile_line = optgroup->create_single_option_line("printhost_cafile"); + if (Http::ca_file_supported()) { + Line cafile_line = optgroup->create_single_option_line("printhost_cafile"); - auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { - auto btn = new wxButton(parent, wxID_ANY, " " + _(L("Browse"))+" " +dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - btn->SetBitmap(create_scaled_bitmap(this, "browse")); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) { + auto btn = new wxButton(parent, wxID_ANY, " " + _(L("Browse"))+" " +dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + btn->SetBitmap(create_scaled_bitmap(this, "browse")); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [this, optgroup] (wxCommandEvent e) { - static const auto filemasks = _(L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*")); - wxFileDialog openFileDialog(this, _(L("Open CA certificate file")), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (openFileDialog.ShowModal() != wxID_CANCEL) { - optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true); - optgroup->get_field("printhost_cafile")->field_changed(); - } - }); + btn->Bind(wxEVT_BUTTON, [this, optgroup] (wxCommandEvent e) { + static const auto filemasks = _(L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*")); + wxFileDialog openFileDialog(this, _(L("Open CA certificate file")), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_CANCEL) { + optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true); + optgroup->get_field("printhost_cafile")->field_changed(); + } + }); - return sizer; - }; + return sizer; + }; - cafile_line.append_widget(printhost_cafile_browse); - optgroup->append_line(cafile_line); + cafile_line.append_widget(printhost_cafile_browse); + optgroup->append_line(cafile_line); - Line cafile_hint { "", "" }; - cafile_hint.full_width = 1; - cafile_hint.widget = [this, ca_file_hint](wxWindow* parent) { - auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(txt); - return sizer; - }; - optgroup->append_line(cafile_hint); - } else { - Line line { "", "" }; - line.full_width = 1; + Line cafile_hint { "", "" }; + cafile_hint.full_width = 1; + cafile_hint.widget = [this, ca_file_hint](wxWindow* parent) { + auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt); + return sizer; + }; + optgroup->append_line(cafile_hint); + } else { + Line line { "", "" }; + line.full_width = 1; - line.widget = [this, ca_file_hint] (wxWindow* parent) { - auto txt = new wxStaticText(parent, wxID_ANY, wxString::Format("%s\n\n\t%s", - wxString::Format(_(L("HTTPS CA File:\n\ + line.widget = [this, ca_file_hint] (wxWindow* parent) { + auto txt = new wxStaticText(parent, wxID_ANY, wxString::Format("%s\n\n\t%s", + wxString::Format(_(L("HTTPS CA File:\n\ \tOn this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.\n\ \tTo use a custom CA file, please import your CA file into Certificate Store / Keychain.")), SLIC3R_APP_NAME), - ca_file_hint)); - txt->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(txt); - return sizer; - }; + ca_file_hint)); + txt->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(txt); + return sizer; + }; - optgroup->append_line(line); - } + optgroup->append_line(line); + } } void TabPrinter::build() { - m_presets = &m_preset_bundle->printers; - load_initial_data(); + m_presets = &m_preset_bundle->printers; + load_initial_data(); m_printer_technology = m_presets->get_selected_preset().printer_technology(); @@ -1941,31 +1941,31 @@ void TabPrinter::build_fff() { if (!m_pages.empty()) m_pages.resize(0); - // to avoid redundant memory allocation / deallocation during extruders count changing - m_pages.reserve(30); + // to avoid redundant memory allocation / deallocation during extruders count changing + m_pages.reserve(30); - auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); - m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); + auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); + m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); wxGetApp().sidebar().update_objects_list_extruder_column(m_initial_extruders_count); - const Preset* parent_preset = m_presets->get_selected_preset_parent(); - m_sys_extruders_count = parent_preset == nullptr ? 0 : - static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); + const Preset* parent_preset = m_presets->get_selected_preset_parent(); + m_sys_extruders_count = parent_preset == nullptr ? 0 : + static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); - auto page = add_options_page(_(L("General")), "printer"); - auto optgroup = page->new_optgroup(_(L("Size and coordinates"))); + auto page = add_options_page(_(L("General")), "printer"); + auto optgroup = page->new_optgroup(_(L("Size and coordinates"))); Line line = optgroup->create_single_option_line("bed_shape");//{ _(L("Bed shape")), "" }; - line.widget = [this](wxWindow* parent) { + line.widget = [this](wxWindow* parent) { ScalableButton* btn; add_scaled_button(parent, &btn, "printer_white", " " + _(L("Set")) + " " + dots, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) - { + btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) + { BedShapeDialog dlg(this); dlg.build_dialog(*m_config->option("bed_shape"), *m_config->option("bed_custom_texture"), @@ -1984,31 +1984,31 @@ void TabPrinter::build_fff() } })); - return sizer; - }; - optgroup->append_line(line, &m_colored_Label); + return sizer; + }; + optgroup->append_line(line, &m_colored_Label); optgroup->append_single_option_line("max_print_height"); optgroup->append_single_option_line("z_offset"); - optgroup = page->new_optgroup(_(L("Capabilities"))); - ConfigOptionDef def; - def.type = coInt, - def.set_default_value(new ConfigOptionInt(1)); - def.label = L("Extruders"); - def.tooltip = L("Number of extruders of the printer."); - def.min = 1; + optgroup = page->new_optgroup(_(L("Capabilities"))); + ConfigOptionDef def; + def.type = coInt, + def.set_default_value(new ConfigOptionInt(1)); + def.label = L("Extruders"); + def.tooltip = L("Number of extruders of the printer."); + def.min = 1; def.mode = comExpert; - Option option(def, "extruders_count"); - optgroup->append_single_option_line(option); - optgroup->append_single_option_line("single_extruder_multi_material"); + Option option(def, "extruders_count"); + optgroup->append_single_option_line(option); + optgroup->append_single_option_line("single_extruder_multi_material"); - optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { - size_t extruders_count = boost::any_cast(optgroup->get_value("extruders_count")); - wxTheApp->CallAfter([this, opt_key, value, extruders_count]() { - if (opt_key == "extruders_count" || opt_key == "single_extruder_multi_material") { - extruders_count_changed(extruders_count); + optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { + size_t extruders_count = boost::any_cast(optgroup->get_value("extruders_count")); + wxTheApp->CallAfter([this, opt_key, value, extruders_count]() { + if (opt_key == "extruders_count" || opt_key == "single_extruder_multi_material") { + extruders_count_changed(extruders_count); init_options_list(); // m_options_list should be updated before UI updating - update_dirty(); + update_dirty(); if (opt_key == "single_extruder_multi_material") { // the single_extruder_multimaterial was added to force pages on_value_change(opt_key, value); // rebuild - let's make sure the on_value_change is not skipped @@ -2024,7 +2024,7 @@ void TabPrinter::build_fff() "and all extruders must have the same diameter.\n" "Do you want to change the diameter for all extruders to first extruder nozzle diameter value?")); auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); - + if (dialog->ShowModal() == wxID_YES) { DynamicPrintConfig new_conf = *m_config; for (size_t i = 1; i < nozzle_diameters.size(); i++) @@ -2035,154 +2035,154 @@ void TabPrinter::build_fff() } break; } - } + } } } - } - else { - update_dirty(); - on_value_change(opt_key, value); - } - }); - }; + } + else { + update_dirty(); + on_value_change(opt_key, value); + } + }); + }; #if 0 - if (!m_no_controller) - { - optgroup = page->new_optgroup(_(L("USB/Serial connection"))); - line = {_(L("Serial port")), ""}; - Option serial_port = optgroup->get_option("serial_port"); - serial_port.side_widget = ([this](wxWindow* parent) { - auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(Slic3r::var("arrow_rotate_clockwise.png")), wxBITMAP_TYPE_PNG), - wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); - btn->SetToolTip(_(L("Rescan serial ports"))); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + if (!m_no_controller) + { + optgroup = page->new_optgroup(_(L("USB/Serial connection"))); + line = {_(L("Serial port")), ""}; + Option serial_port = optgroup->get_option("serial_port"); + serial_port.side_widget = ([this](wxWindow* parent) { + auto btn = new wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(Slic3r::var("arrow_rotate_clockwise.png")), wxBITMAP_TYPE_PNG), + wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); + btn->SetToolTip(_(L("Rescan serial ports"))); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {update_serial_ports(); }); - return sizer; - }); - auto serial_test = [this](wxWindow* parent) { - auto btn = m_serial_test_btn = new wxButton(parent, wxID_ANY, - _(L("Test")), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); - btn->SetFont(Slic3r::GUI::small_font()); - btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG)); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(btn); + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent e) {update_serial_ports(); }); + return sizer; + }); + auto serial_test = [this](wxWindow* parent) { + auto btn = m_serial_test_btn = new wxButton(parent, wxID_ANY, + _(L("Test")), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT); + btn->SetFont(Slic3r::GUI::small_font()); + btn->SetBitmap(wxBitmap(from_u8(Slic3r::var("wrench.png")), wxBITMAP_TYPE_PNG)); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); - btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e) { - auto sender = Slic3r::make_unique(); - auto res = sender->connect( - m_config->opt_string("serial_port"), - m_config->opt_int("serial_speed") - ); - if (res && sender->wait_connected()) { - show_info(parent, _(L("Connection to printer works correctly.")), _(L("Success!"))); - } - else { - show_error(parent, _(L("Connection failed."))); - } - }); - return sizer; - }; + btn->Bind(wxEVT_BUTTON, [this, parent](wxCommandEvent e) { + auto sender = Slic3r::make_unique(); + auto res = sender->connect( + m_config->opt_string("serial_port"), + m_config->opt_int("serial_speed") + ); + if (res && sender->wait_connected()) { + show_info(parent, _(L("Connection to printer works correctly.")), _(L("Success!"))); + } + else { + show_error(parent, _(L("Connection failed."))); + } + }); + return sizer; + }; - line.append_option(serial_port); - line.append_option(optgroup->get_option("serial_speed")); - line.append_widget(serial_test); - optgroup->append_line(line); - } + line.append_option(serial_port); + line.append_option(optgroup->get_option("serial_speed")); + line.append_widget(serial_test); + optgroup->append_line(line); + } #endif - optgroup = page->new_optgroup(_(L("Print Host upload"))); - build_printhost(optgroup.get()); + optgroup = page->new_optgroup(_(L("Print Host upload"))); + build_printhost(optgroup.get()); - optgroup = page->new_optgroup(_(L("Firmware"))); - optgroup->append_single_option_line("gcode_flavor"); - optgroup->append_single_option_line("silent_mode"); - optgroup->append_single_option_line("remaining_times"); + optgroup = page->new_optgroup(_(L("Firmware"))); + optgroup->append_single_option_line("gcode_flavor"); + optgroup->append_single_option_line("silent_mode"); + optgroup->append_single_option_line("remaining_times"); - optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { - wxTheApp->CallAfter([this, opt_key, value]() { - if (opt_key == "silent_mode") { - bool val = boost::any_cast(value); - if (m_use_silent_mode != val) { - m_rebuild_kinematics_page = true; - m_use_silent_mode = val; - } - } - build_unregular_pages(); - update_dirty(); - on_value_change(opt_key, value); - }); - }; + optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value) { + wxTheApp->CallAfter([this, opt_key, value]() { + if (opt_key == "silent_mode") { + bool val = boost::any_cast(value); + if (m_use_silent_mode != val) { + m_rebuild_kinematics_page = true; + m_use_silent_mode = val; + } + } + build_unregular_pages(); + update_dirty(); + on_value_change(opt_key, value); + }); + }; - optgroup = page->new_optgroup(_(L("Advanced"))); - optgroup->append_single_option_line("use_relative_e_distances"); - optgroup->append_single_option_line("use_firmware_retraction"); - optgroup->append_single_option_line("use_volumetric_e"); - optgroup->append_single_option_line("variable_layer_height"); + optgroup = page->new_optgroup(_(L("Advanced"))); + optgroup->append_single_option_line("use_relative_e_distances"); + optgroup->append_single_option_line("use_firmware_retraction"); + optgroup->append_single_option_line("use_volumetric_e"); + optgroup->append_single_option_line("variable_layer_height"); const int gcode_field_height = 15; // 150 const int notes_field_height = 25; // 250 - page = add_options_page(_(L("Custom G-code")), "cog"); - optgroup = page->new_optgroup(_(L("Start G-code")), 0); - option = optgroup->get_option("start_gcode"); - option.opt.full_width = true; + page = add_options_page(_(L("Custom G-code")), "cog"); + optgroup = page->new_optgroup(_(L("Start G-code")), 0); + option = optgroup->get_option("start_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("End G-code")), 0); - option = optgroup->get_option("end_gcode"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("End G-code")), 0); + option = optgroup->get_option("end_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("Before layer change G-code")), 0); - option = optgroup->get_option("before_layer_gcode"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("Before layer change G-code")), 0); + option = optgroup->get_option("before_layer_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("After layer change G-code")), 0); - option = optgroup->get_option("layer_gcode"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("After layer change G-code")), 0); + option = optgroup->get_option("layer_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("Tool change G-code")), 0); - option = optgroup->get_option("toolchange_gcode"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("Tool change G-code")), 0); + option = optgroup->get_option("toolchange_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - optgroup = page->new_optgroup(_(L("Between objects G-code (for sequential printing)")), 0); - option = optgroup->get_option("between_objects_gcode"); - option.opt.full_width = true; + optgroup = page->new_optgroup(_(L("Between objects G-code (for sequential printing)")), 0); + option = optgroup->get_option("between_objects_gcode"); + option.opt.full_width = true; option.opt.height = gcode_field_height;//150; - optgroup->append_single_option_line(option); - - page = add_options_page(_(L("Notes")), "note.png"); - optgroup = page->new_optgroup(_(L("Notes")), 0); - option = optgroup->get_option("printer_notes"); - option.opt.full_width = true; + optgroup->append_single_option_line(option); + + page = add_options_page(_(L("Notes")), "note.png"); + optgroup = page->new_optgroup(_(L("Notes")), 0); + option = optgroup->get_option("printer_notes"); + option.opt.full_width = true; option.opt.height = notes_field_height;//250; - optgroup->append_single_option_line(option); + optgroup->append_single_option_line(option); - page = add_options_page(_(L("Dependencies")), "wrench.png"); - optgroup = page->new_optgroup(_(L("Profile dependencies"))); - line = Line{ "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - return description_line_widget(parent, &m_parent_preset_description_line); - }; - optgroup->append_line(line); + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); - build_unregular_pages(); + build_unregular_pages(); #if 0 - if (!m_no_controller) - update_serial_ports(); + if (!m_no_controller) + update_serial_ports(); #endif } @@ -2238,7 +2238,7 @@ void TabPrinter::build_sla() line.append_option(optgroup->get_option("display_pixels_y")); optgroup->append_line(line); optgroup->append_single_option_line("display_orientation"); - + // FIXME: This should be on one line in the UI optgroup->append_single_option_line("display_mirror_x"); optgroup->append_single_option_line("display_mirror_y"); @@ -2289,28 +2289,28 @@ void TabPrinter::build_sla() void TabPrinter::update_serial_ports() { - Field *field = get_field("serial_port"); - Choice *choice = static_cast(field); - choice->set_values(Utils::scan_serial_ports()); + Field *field = get_field("serial_port"); + Choice *choice = static_cast(field); + choice->set_values(Utils::scan_serial_ports()); } void TabPrinter::extruders_count_changed(size_t extruders_count) { bool is_count_changed = false; if (m_extruders_count != extruders_count) { - m_extruders_count = extruders_count; - m_preset_bundle->printers.get_edited_preset().set_num_extruders(extruders_count); - m_preset_bundle->update_multi_material_filament_presets(); + m_extruders_count = extruders_count; + m_preset_bundle->printers.get_edited_preset().set_num_extruders(extruders_count); + m_preset_bundle->update_multi_material_filament_presets(); is_count_changed = true; } - else if (m_extruders_count == 1 && + else if (m_extruders_count == 1 && m_preset_bundle->project_config.option("wiping_volumes_matrix")->values.size()>1) m_preset_bundle->update_multi_material_filament_presets(); - /* This function should be call in any case because of correct updating/rebuilding + /* This function should be call in any case because of correct updating/rebuilding * of unregular pages of a Printer Settings */ - build_unregular_pages(); + build_unregular_pages(); if (is_count_changed) { on_value_change("extruders_count", extruders_count); @@ -2320,81 +2320,81 @@ void TabPrinter::extruders_count_changed(size_t extruders_count) void TabPrinter::append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key) { - auto option = optgroup->get_option(opt_key, 0); - auto line = Line{ _(option.opt.full_label), "" }; - line.append_option(option); - if (m_use_silent_mode) - line.append_option(optgroup->get_option(opt_key, 1)); - optgroup->append_line(line); + auto option = optgroup->get_option(opt_key, 0); + auto line = Line{ _(option.opt.full_label), "" }; + line.append_option(option); + if (m_use_silent_mode) + line.append_option(optgroup->get_option(opt_key, 1)); + optgroup->append_line(line); } PageShp TabPrinter::build_kinematics_page() { - auto page = add_options_page(_(L("Machine limits")), "cog", true); + auto page = add_options_page(_(L("Machine limits")), "cog", true); - if (m_use_silent_mode) { - // Legend for OptionsGroups - auto optgroup = page->new_optgroup(""); - optgroup->set_show_modified_btns_val(false); + if (m_use_silent_mode) { + // Legend for OptionsGroups + auto optgroup = page->new_optgroup(""); + optgroup->set_show_modified_btns_val(false); optgroup->label_width = 23;// 230; - auto line = Line{ "", "" }; + auto line = Line{ "", "" }; - ConfigOptionDef def; - def.type = coString; - def.width = 15; - def.gui_type = "legend"; + ConfigOptionDef def; + def.type = coString; + def.width = 15; + def.gui_type = "legend"; def.mode = comAdvanced; - def.tooltip = L("Values in this column are for Normal mode"); - def.set_default_value(new ConfigOptionString{ _(L("Normal")).ToUTF8().data() }); + def.tooltip = L("Values in this column are for Normal mode"); + def.set_default_value(new ConfigOptionString{ _(L("Normal")).ToUTF8().data() }); - auto option = Option(def, "full_power_legend"); - line.append_option(option); + auto option = Option(def, "full_power_legend"); + line.append_option(option); - def.tooltip = L("Values in this column are for Stealth mode"); - def.set_default_value(new ConfigOptionString{ _(L("Stealth")).ToUTF8().data() }); - option = Option(def, "silent_legend"); - line.append_option(option); + def.tooltip = L("Values in this column are for Stealth mode"); + def.set_default_value(new ConfigOptionString{ _(L("Stealth")).ToUTF8().data() }); + option = Option(def, "silent_legend"); + line.append_option(option); - optgroup->append_line(line); - } + optgroup->append_line(line); + } - std::vector axes{ "x", "y", "z", "e" }; - auto optgroup = page->new_optgroup(_(L("Maximum feedrates"))); - for (const std::string &axis : axes) { - append_option_line(optgroup, "machine_max_feedrate_" + axis); - } + std::vector axes{ "x", "y", "z", "e" }; + auto optgroup = page->new_optgroup(_(L("Maximum feedrates"))); + for (const std::string &axis : axes) { + append_option_line(optgroup, "machine_max_feedrate_" + axis); + } - optgroup = page->new_optgroup(_(L("Maximum accelerations"))); - for (const std::string &axis : axes) { - append_option_line(optgroup, "machine_max_acceleration_" + axis); - } - append_option_line(optgroup, "machine_max_acceleration_extruding"); - append_option_line(optgroup, "machine_max_acceleration_retracting"); + optgroup = page->new_optgroup(_(L("Maximum accelerations"))); + for (const std::string &axis : axes) { + append_option_line(optgroup, "machine_max_acceleration_" + axis); + } + append_option_line(optgroup, "machine_max_acceleration_extruding"); + append_option_line(optgroup, "machine_max_acceleration_retracting"); - optgroup = page->new_optgroup(_(L("Jerk limits"))); - for (const std::string &axis : axes) { - append_option_line(optgroup, "machine_max_jerk_" + axis); - } + optgroup = page->new_optgroup(_(L("Jerk limits"))); + for (const std::string &axis : axes) { + append_option_line(optgroup, "machine_max_jerk_" + axis); + } - optgroup = page->new_optgroup(_(L("Minimum feedrates"))); - append_option_line(optgroup, "machine_min_extruding_rate"); - append_option_line(optgroup, "machine_min_travel_rate"); + optgroup = page->new_optgroup(_(L("Minimum feedrates"))); + append_option_line(optgroup, "machine_min_extruding_rate"); + append_option_line(optgroup, "machine_min_travel_rate"); - return page; + return page; } /* Previous name build_extruder_pages(). - * - * This function was renamed because of now it implements not just an extruder pages building, - * but "Machine limits" and "Single extruder MM setup" too + * + * This function was renamed because of now it implements not just an extruder pages building, + * but "Machine limits" and "Single extruder MM setup" too * (These pages can changes according to the another values of a current preset) * */ void TabPrinter::build_unregular_pages() { - size_t n_before_extruders = 2; // Count of pages before Extruder pages - bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; + size_t n_before_extruders = 2; // Count of pages before Extruder pages + bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; - /* ! Freeze/Thaw in this function is needed to avoid call OnPaint() for erased pages + /* ! Freeze/Thaw in this function is needed to avoid call OnPaint() for erased pages * and be cause of application crash, when try to change Preset in moment, * when one of unregular pages is selected. * */ @@ -2412,62 +2412,62 @@ void TabPrinter::build_unregular_pages() }; #endif //__WXMSW__ - // Add/delete Kinematics page according to is_marlin_flavor - size_t existed_page = 0; - for (int i = n_before_extruders; i < m_pages.size(); ++i) // first make sure it's not there already - if (m_pages[i]->title().find(_(L("Machine limits"))) != std::string::npos) { - if (!is_marlin_flavor || m_rebuild_kinematics_page) - m_pages.erase(m_pages.begin() + i); - else - existed_page = i; - break; - } + // Add/delete Kinematics page according to is_marlin_flavor + size_t existed_page = 0; + for (int i = n_before_extruders; i < m_pages.size(); ++i) // first make sure it's not there already + if (m_pages[i]->title().find(_(L("Machine limits"))) != std::string::npos) { + if (!is_marlin_flavor || m_rebuild_kinematics_page) + m_pages.erase(m_pages.begin() + i); + else + existed_page = i; + break; + } - if (existed_page < n_before_extruders && is_marlin_flavor) { - auto page = build_kinematics_page(); + if (existed_page < n_before_extruders && is_marlin_flavor) { + auto page = build_kinematics_page(); #ifdef __WXMSW__ - layout_page(page); + layout_page(page); #endif - m_pages.insert(m_pages.begin() + n_before_extruders, page); - } + m_pages.insert(m_pages.begin() + n_before_extruders, page); + } - if (is_marlin_flavor) - n_before_extruders++; - size_t n_after_single_extruder_MM = 2; // Count of pages after single_extruder_multi_material page + if (is_marlin_flavor) + n_before_extruders++; + size_t n_after_single_extruder_MM = 2; // Count of pages after single_extruder_multi_material page - if (m_extruders_count_old == m_extruders_count || - (m_has_single_extruder_MM_page && m_extruders_count == 1)) - { - // if we have a single extruder MM setup, add a page with configuration options: - for (int i = 0; i < m_pages.size(); ++i) // first make sure it's not there already - if (m_pages[i]->title().find(_(L("Single extruder MM setup"))) != std::string::npos) { - m_pages.erase(m_pages.begin() + i); - break; - } - m_has_single_extruder_MM_page = false; - } - if (m_extruders_count > 1 && m_config->opt_bool("single_extruder_multi_material") && !m_has_single_extruder_MM_page) { - // create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves - auto page = add_options_page(_(L("Single extruder MM setup")), "printer", true); - auto optgroup = page->new_optgroup(_(L("Single extruder multimaterial parameters"))); - optgroup->append_single_option_line("cooling_tube_retraction"); - optgroup->append_single_option_line("cooling_tube_length"); - optgroup->append_single_option_line("parking_pos_retraction"); + if (m_extruders_count_old == m_extruders_count || + (m_has_single_extruder_MM_page && m_extruders_count == 1)) + { + // if we have a single extruder MM setup, add a page with configuration options: + for (int i = 0; i < m_pages.size(); ++i) // first make sure it's not there already + if (m_pages[i]->title().find(_(L("Single extruder MM setup"))) != std::string::npos) { + m_pages.erase(m_pages.begin() + i); + break; + } + m_has_single_extruder_MM_page = false; + } + if (m_extruders_count > 1 && m_config->opt_bool("single_extruder_multi_material") && !m_has_single_extruder_MM_page) { + // create a page, but pretend it's an extruder page, so we can add it to m_pages ourselves + auto page = add_options_page(_(L("Single extruder MM setup")), "printer", true); + auto optgroup = page->new_optgroup(_(L("Single extruder multimaterial parameters"))); + optgroup->append_single_option_line("cooling_tube_retraction"); + optgroup->append_single_option_line("cooling_tube_length"); + optgroup->append_single_option_line("parking_pos_retraction"); optgroup->append_single_option_line("extra_loading_move"); optgroup->append_single_option_line("high_current_on_filament_swap"); - m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page); - m_has_single_extruder_MM_page = true; - } - + m_pages.insert(m_pages.end() - n_after_single_extruder_MM, page); + m_has_single_extruder_MM_page = true; + } + // Build missed extruder pages - for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx) { - //# build page + for (auto extruder_idx = m_extruders_count_old; extruder_idx < m_extruders_count; ++extruder_idx) { + //# build page const wxString& page_name = wxString::Format(_(L("Extruder %d")), int(extruder_idx + 1)); auto page = add_options_page(page_name, "funnel", true); - m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page); - - auto optgroup = page->new_optgroup(_(L("Size"))); - optgroup->append_single_option_line("nozzle_diameter", extruder_idx); + m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page); + + auto optgroup = page->new_optgroup(_(L("Size"))); + optgroup->append_single_option_line("nozzle_diameter", extruder_idx); optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value) { @@ -2478,7 +2478,7 @@ void TabPrinter::build_unregular_pages() std::vector nozzle_diameters = static_cast(m_config->option("nozzle_diameter"))->values; // if value was changed - if (fabs(nozzle_diameters[extruder_idx == 0 ? 1 : 0] - new_nd) > EPSILON) + if (fabs(nozzle_diameters[extruder_idx == 0 ? 1 : 0] - new_nd) > EPSILON) { const wxString msg_text = _(L("Do you want to change the diameter for all extruders?")); auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); @@ -2491,7 +2491,7 @@ void TabPrinter::build_unregular_pages() nozzle_diameters[i] = new_nd; } } - else + else nozzle_diameters[extruder_idx] = nozzle_diameters[extruder_idx == 0 ? 1 : 0]; new_conf.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameters)); @@ -2502,52 +2502,52 @@ void TabPrinter::build_unregular_pages() update_dirty(); update(); }; - - optgroup = page->new_optgroup(_(L("Layer height limits"))); - optgroup->append_single_option_line("min_layer_height", extruder_idx); - optgroup->append_single_option_line("max_layer_height", extruder_idx); - - - optgroup = page->new_optgroup(_(L("Position (for multi-extruder printers)"))); - optgroup->append_single_option_line("extruder_offset", extruder_idx); - - optgroup = page->new_optgroup(_(L("Retraction"))); - optgroup->append_single_option_line("retract_length", extruder_idx); - optgroup->append_single_option_line("retract_lift", extruder_idx); - Line line = { _(L("Only lift Z")), "" }; - line.append_option(optgroup->get_option("retract_lift_above", extruder_idx)); - line.append_option(optgroup->get_option("retract_lift_below", extruder_idx)); - optgroup->append_line(line); - - optgroup->append_single_option_line("retract_speed", extruder_idx); - optgroup->append_single_option_line("deretract_speed", extruder_idx); - optgroup->append_single_option_line("retract_restart_extra", extruder_idx); - optgroup->append_single_option_line("retract_before_travel", extruder_idx); - optgroup->append_single_option_line("retract_layer_change", extruder_idx); - optgroup->append_single_option_line("wipe", extruder_idx); - optgroup->append_single_option_line("retract_before_wipe", extruder_idx); - - optgroup = page->new_optgroup(_(L("Retraction when tool is disabled (advanced settings for multi-extruder setups)"))); - optgroup->append_single_option_line("retract_length_toolchange", extruder_idx); - optgroup->append_single_option_line("retract_restart_extra_toolchange", extruder_idx); - optgroup = page->new_optgroup(_(L("Preview"))); - optgroup->append_single_option_line("extruder_colour", extruder_idx); + optgroup = page->new_optgroup(_(L("Layer height limits"))); + optgroup->append_single_option_line("min_layer_height", extruder_idx); + optgroup->append_single_option_line("max_layer_height", extruder_idx); + + + optgroup = page->new_optgroup(_(L("Position (for multi-extruder printers)"))); + optgroup->append_single_option_line("extruder_offset", extruder_idx); + + optgroup = page->new_optgroup(_(L("Retraction"))); + optgroup->append_single_option_line("retract_length", extruder_idx); + optgroup->append_single_option_line("retract_lift", extruder_idx); + Line line = { _(L("Only lift Z")), "" }; + line.append_option(optgroup->get_option("retract_lift_above", extruder_idx)); + line.append_option(optgroup->get_option("retract_lift_below", extruder_idx)); + optgroup->append_line(line); + + optgroup->append_single_option_line("retract_speed", extruder_idx); + optgroup->append_single_option_line("deretract_speed", extruder_idx); + optgroup->append_single_option_line("retract_restart_extra", extruder_idx); + optgroup->append_single_option_line("retract_before_travel", extruder_idx); + optgroup->append_single_option_line("retract_layer_change", extruder_idx); + optgroup->append_single_option_line("wipe", extruder_idx); + optgroup->append_single_option_line("retract_before_wipe", extruder_idx); + + optgroup = page->new_optgroup(_(L("Retraction when tool is disabled (advanced settings for multi-extruder setups)"))); + optgroup->append_single_option_line("retract_length_toolchange", extruder_idx); + optgroup->append_single_option_line("retract_restart_extra_toolchange", extruder_idx); + + optgroup = page->new_optgroup(_(L("Preview"))); + optgroup->append_single_option_line("extruder_colour", extruder_idx); #ifdef __WXMSW__ - layout_page(page); + layout_page(page); #endif - } - - // # remove extra pages - if (m_extruders_count < m_extruders_count_old) - m_pages.erase( m_pages.begin() + n_before_extruders + m_extruders_count, - m_pages.begin() + n_before_extruders + m_extruders_count_old); + } + + // # remove extra pages + if (m_extruders_count < m_extruders_count_old) + m_pages.erase( m_pages.begin() + n_before_extruders + m_extruders_count, + m_pages.begin() + n_before_extruders + m_extruders_count_old); Thaw(); - m_extruders_count_old = m_extruders_count; - rebuild_page_tree(); + m_extruders_count_old = m_extruders_count; + rebuild_page_tree(); // Reload preset pages with current configuration values reload_config(); @@ -2556,12 +2556,12 @@ void TabPrinter::build_unregular_pages() // this gets executed after preset is loaded and before GUI fields are updated void TabPrinter::on_preset_loaded() { - // update the extruders count field - auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); - int extruders_count = nozzle_diameter->values.size(); - set_value("extruders_count", extruders_count); - // update the GUI field according to the number of nozzle diameters supplied - extruders_count_changed(extruders_count); + // update the extruders count field + auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); + int extruders_count = nozzle_diameter->values.size(); + set_value("extruders_count", extruders_count); + // update the GUI field according to the number of nozzle diameters supplied + extruders_count_changed(extruders_count); } void TabPrinter::update_pages() @@ -2597,7 +2597,7 @@ void TabPrinter::update_pages() wxGetApp().sidebar().update_objects_list_extruder_column(m_extruders_count); } - else + else m_pages_sla.empty() ? build_sla() : m_pages.swap(m_pages_sla); rebuild_page_tree(); @@ -2617,102 +2617,102 @@ void TabPrinter::update_fff() { // Freeze(); - bool en; - auto serial_speed = get_field("serial_speed"); - if (serial_speed != nullptr) { - en = !m_config->opt_string("serial_port").empty(); - get_field("serial_speed")->toggle(en); - if (m_config->opt_int("serial_speed") != 0 && en) - m_serial_test_btn->Enable(); - else - m_serial_test_btn->Disable(); - } + bool en; + auto serial_speed = get_field("serial_speed"); + if (serial_speed != nullptr) { + en = !m_config->opt_string("serial_port").empty(); + get_field("serial_speed")->toggle(en); + if (m_config->opt_int("serial_speed") != 0 && en) + m_serial_test_btn->Enable(); + else + m_serial_test_btn->Disable(); + } - { - std::unique_ptr host(PrintHost::get_print_host(m_config)); - m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test()); - m_printhost_browse_btn->Enable(host->has_auto_discovery()); - } + { + std::unique_ptr host(PrintHost::get_print_host(m_config)); + m_print_host_test_btn->Enable(!m_config->opt_string("print_host").empty() && host->can_test()); + m_printhost_browse_btn->Enable(host->has_auto_discovery()); + } - bool have_multiple_extruders = m_extruders_count > 1; - get_field("toolchange_gcode")->toggle(have_multiple_extruders); - get_field("single_extruder_multi_material")->toggle(have_multiple_extruders); + bool have_multiple_extruders = m_extruders_count > 1; + get_field("toolchange_gcode")->toggle(have_multiple_extruders); + get_field("single_extruder_multi_material")->toggle(have_multiple_extruders); - bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; + bool is_marlin_flavor = m_config->option>("gcode_flavor")->value == gcfMarlin; - { - Field *sm = get_field("silent_mode"); - if (! is_marlin_flavor) - // Disable silent mode for non-marlin firmwares. - get_field("silent_mode")->toggle(false); - if (is_marlin_flavor) - sm->enable(); - else - sm->disable(); - } + { + Field *sm = get_field("silent_mode"); + if (! is_marlin_flavor) + // Disable silent mode for non-marlin firmwares. + get_field("silent_mode")->toggle(false); + if (is_marlin_flavor) + sm->enable(); + else + sm->disable(); + } - if (m_use_silent_mode != m_config->opt_bool("silent_mode")) { - m_rebuild_kinematics_page = true; - m_use_silent_mode = m_config->opt_bool("silent_mode"); - } + if (m_use_silent_mode != m_config->opt_bool("silent_mode")) { + m_rebuild_kinematics_page = true; + m_use_silent_mode = m_config->opt_bool("silent_mode"); + } - for (size_t i = 0; i < m_extruders_count; ++i) { - bool have_retract_length = m_config->opt_float("retract_length", i) > 0; + for (size_t i = 0; i < m_extruders_count; ++i) { + bool have_retract_length = m_config->opt_float("retract_length", i) > 0; - // when using firmware retraction, firmware decides retraction length - bool use_firmware_retraction = m_config->opt_bool("use_firmware_retraction"); - get_field("retract_length", i)->toggle(!use_firmware_retraction); + // when using firmware retraction, firmware decides retraction length + bool use_firmware_retraction = m_config->opt_bool("use_firmware_retraction"); + get_field("retract_length", i)->toggle(!use_firmware_retraction); - // user can customize travel length if we have retraction length or we"re using - // firmware retraction - get_field("retract_before_travel", i)->toggle(have_retract_length || use_firmware_retraction); + // user can customize travel length if we have retraction length or we"re using + // firmware retraction + get_field("retract_before_travel", i)->toggle(have_retract_length || use_firmware_retraction); - // user can customize other retraction options if retraction is enabled - bool retraction = (have_retract_length || use_firmware_retraction); - std::vector vec = { "retract_lift", "retract_layer_change" }; - for (auto el : vec) - get_field(el, i)->toggle(retraction); + // user can customize other retraction options if retraction is enabled + bool retraction = (have_retract_length || use_firmware_retraction); + std::vector vec = { "retract_lift", "retract_layer_change" }; + for (auto el : vec) + get_field(el, i)->toggle(retraction); - // retract lift above / below only applies if using retract lift - vec.resize(0); - vec = { "retract_lift_above", "retract_lift_below" }; - for (auto el : vec) - get_field(el, i)->toggle(retraction && m_config->opt_float("retract_lift", i) > 0); + // retract lift above / below only applies if using retract lift + vec.resize(0); + vec = { "retract_lift_above", "retract_lift_below" }; + for (auto el : vec) + get_field(el, i)->toggle(retraction && m_config->opt_float("retract_lift", i) > 0); - // some options only apply when not using firmware retraction - vec.resize(0); - vec = { "retract_speed", "deretract_speed", "retract_before_wipe", "retract_restart_extra", "wipe" }; - for (auto el : vec) - get_field(el, i)->toggle(retraction && !use_firmware_retraction); + // some options only apply when not using firmware retraction + vec.resize(0); + vec = { "retract_speed", "deretract_speed", "retract_before_wipe", "retract_restart_extra", "wipe" }; + for (auto el : vec) + get_field(el, i)->toggle(retraction && !use_firmware_retraction); - bool wipe = m_config->opt_bool("wipe", i); - get_field("retract_before_wipe", i)->toggle(wipe); + bool wipe = m_config->opt_bool("wipe", i); + get_field("retract_before_wipe", i)->toggle(wipe); - if (use_firmware_retraction && wipe) { - auto dialog = new wxMessageDialog(parent(), - _(L("The Wipe option is not available when using the Firmware Retraction mode.\n" - "\nShall I disable it in order to enable Firmware Retraction?")), - _(L("Firmware Retraction")), wxICON_WARNING | wxYES | wxNO); + if (use_firmware_retraction && wipe) { + auto dialog = new wxMessageDialog(parent(), + _(L("The Wipe option is not available when using the Firmware Retraction mode.\n" + "\nShall I disable it in order to enable Firmware Retraction?")), + _(L("Firmware Retraction")), wxICON_WARNING | wxYES | wxNO); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { - auto wipe = static_cast(m_config->option("wipe")->clone()); - for (int w = 0; w < wipe->values.size(); w++) - wipe->values[w] = false; - new_conf.set_key_value("wipe", wipe); - } - else { - new_conf.set_key_value("use_firmware_retraction", new ConfigOptionBool(false)); - } - load_config(new_conf); - } + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_YES) { + auto wipe = static_cast(m_config->option("wipe")->clone()); + for (int w = 0; w < wipe->values.size(); w++) + wipe->values[w] = false; + new_conf.set_key_value("wipe", wipe); + } + else { + new_conf.set_key_value("use_firmware_retraction", new ConfigOptionBool(false)); + } + load_config(new_conf); + } - get_field("retract_length_toolchange", i)->toggle(have_multiple_extruders); + get_field("retract_length_toolchange", i)->toggle(have_multiple_extruders); - bool toolchange_retraction = m_config->opt_float("retract_length_toolchange", i) > 0; - get_field("retract_restart_extra_toolchange", i)->toggle - (have_multiple_extruders && toolchange_retraction); - } + bool toolchange_retraction = m_config->opt_float("retract_length_toolchange", i) > 0; + get_field("retract_restart_extra_toolchange", i)->toggle + (have_multiple_extruders && toolchange_retraction); + } // Thaw(); } @@ -2723,45 +2723,45 @@ void TabPrinter::update_sla() // Initialize the UI from the current preset void Tab::load_current_preset() { - const Preset& preset = m_presets->get_edited_preset(); + const Preset& preset = m_presets->get_edited_preset(); - (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); + (preset.is_default || preset.is_system) ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); update(); - if (m_type == Slic3r::Preset::TYPE_PRINTER) { - // For the printer profile, generate the extruder pages. - if (preset.printer_technology() == ptFFF) - on_preset_loaded(); - else - wxGetApp().sidebar().update_objects_list_extruder_column(1); - } + if (m_type == Slic3r::Preset::TYPE_PRINTER) { + // For the printer profile, generate the extruder pages. + if (preset.printer_technology() == ptFFF) + on_preset_loaded(); + else + wxGetApp().sidebar().update_objects_list_extruder_column(1); + } // Reload preset pages with the new configuration values. reload_config(); const Preset* selected_preset_parent = m_presets->get_selected_preset_parent(); m_is_default_preset = selected_preset_parent != nullptr && selected_preset_parent->is_default; - m_bmp_non_system = selected_preset_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; - m_ttg_non_system = selected_preset_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; - m_tt_non_system = selected_preset_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; + m_bmp_non_system = selected_preset_parent ? &m_bmp_value_unlock : &m_bmp_white_bullet; + m_ttg_non_system = selected_preset_parent ? &m_ttg_value_unlock : &m_ttg_white_bullet_ns; + m_tt_non_system = selected_preset_parent ? &m_tt_value_unlock : &m_ttg_white_bullet_ns; // m_undo_to_sys_btn->Enable(!preset.is_default); #if 0 - // use CallAfter because some field triggers schedule on_change calls using CallAfter, - // and we don't want them to be called after this update_dirty() as they would mark the - // preset dirty again - // (not sure this is true anymore now that update_dirty is idempotent) - wxTheApp->CallAfter([this] + // use CallAfter because some field triggers schedule on_change calls using CallAfter, + // and we don't want them to be called after this update_dirty() as they would mark the + // preset dirty again + // (not sure this is true anymore now that update_dirty is idempotent) + wxTheApp->CallAfter([this] #endif - { - // checking out if this Tab exists till this moment - if (!wxGetApp().checked_tab(this)) - return; - update_tab_ui(); + { + // checking out if this Tab exists till this moment + if (!wxGetApp().checked_tab(this)) + return; + update_tab_ui(); // update show/hide tabs - if (m_type == Slic3r::Preset::TYPE_PRINTER) { + if (m_type == Slic3r::Preset::TYPE_PRINTER) { const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); if (printer_technology != static_cast(this)->m_printer_technology) { @@ -2780,61 +2780,61 @@ void Tab::load_current_preset() int page_id = wxGetApp().tab_panel()->FindPage(tab); wxGetApp().tab_panel()->GetPage(page_id)->Show(false); wxGetApp().tab_panel()->RemovePage(page_id); - } + } } static_cast(this)->m_printer_technology = printer_technology; } - on_presets_changed(); - if (printer_technology == ptFFF) { - static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; - const Preset* parent_preset = m_presets->get_selected_preset_parent(); - static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : - static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); - } - } - else { - on_presets_changed(); + on_presets_changed(); + if (printer_technology == ptFFF) { + static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; + const Preset* parent_preset = m_presets->get_selected_preset_parent(); + static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : + static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); + } + } + else { + on_presets_changed(); if (m_type == Preset::TYPE_SLA_PRINT || m_type == Preset::TYPE_PRINT) - update_frequently_changed_parameters(); - } + update_frequently_changed_parameters(); + } - m_opt_status_value = (m_presets->get_selected_preset_parent() ? osSystemValue : 0) | osInitValue; - init_options_list(); + m_opt_status_value = (m_presets->get_selected_preset_parent() ? osSystemValue : 0) | osInitValue; + init_options_list(); update_visibility(); - update_changed_ui(); - } + update_changed_ui(); + } #if 0 - ); + ); #endif } //Regerenerate content of the page tree. void Tab::rebuild_page_tree() { - // get label of the currently selected item + // get label of the currently selected item const auto sel_item = m_treectrl->GetSelection(); - const auto selected = sel_item ? m_treectrl->GetItemText(sel_item) : ""; - const auto rootItem = m_treectrl->GetRootItem(); + const auto selected = sel_item ? m_treectrl->GetItemText(sel_item) : ""; + const auto rootItem = m_treectrl->GetRootItem(); - auto have_selection = 0; - m_treectrl->DeleteChildren(rootItem); - for (auto p : m_pages) - { - auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); - m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); - if (p->title() == selected) { - m_treectrl->SelectItem(itemId); - have_selection = 1; - } - } + auto have_selection = 0; + m_treectrl->DeleteChildren(rootItem); + for (auto p : m_pages) + { + auto itemId = m_treectrl->AppendItem(rootItem, p->title(), p->iconID()); + m_treectrl->SetItemTextColour(itemId, p->get_item_colour()); + if (p->title() == selected) { + m_treectrl->SelectItem(itemId); + have_selection = 1; + } + } - if (!have_selection) { - // this is triggered on first load, so we don't disable the sel change event - auto item = m_treectrl->GetFirstVisibleItem(); - if (item) { - m_treectrl->SelectItem(item); - } - } + if (!have_selection) { + // this is triggered on first load, so we don't disable the sel change event + auto item = m_treectrl->GetFirstVisibleItem(); + if (item) { + m_treectrl->SelectItem(item); + } + } } void Tab::update_page_tree_visibility() @@ -2872,38 +2872,38 @@ void Tab::update_page_tree_visibility() // If the current profile is modified, user is asked to save the changes. void Tab::select_preset(std::string preset_name, bool delete_current) { - if (preset_name.empty()) { - if (delete_current) { - // Find an alternate preset to be selected after the current preset is deleted. - const std::deque &presets = this->m_presets->get_presets(); - size_t idx_current = this->m_presets->get_idx_selected(); - // Find the next visible preset. - size_t idx_new = idx_current + 1; - if (idx_new < presets.size()) - for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; - if (idx_new == presets.size()) - for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); - preset_name = presets[idx_new].name; - } else { - // If no name is provided, select the "-- default --" preset. - preset_name = m_presets->default_preset().name; - } - } - assert(! delete_current || (m_presets->get_edited_preset().name != preset_name && m_presets->get_edited_preset().is_user())); - bool current_dirty = ! delete_current && m_presets->current_is_dirty(); - bool print_tab = m_presets->type() == Preset::TYPE_PRINT || m_presets->type() == Preset::TYPE_SLA_PRINT; - bool printer_tab = m_presets->type() == Preset::TYPE_PRINTER; - bool canceled = false; - m_dependent_tabs = {}; - if (current_dirty && ! may_discard_current_dirty_preset()) { - canceled = true; - } else if (print_tab) { - // Before switching the print profile to a new one, verify, whether the currently active filament or SLA material - // are compatible with the new print. - // If it is not compatible and the current filament or SLA material are dirty, let user decide - // whether to discard the changes or keep the current print selection. - PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology(); - PresetCollection &dependent = (printer_technology == ptFFF) ? m_preset_bundle->filaments : m_preset_bundle->sla_materials; + if (preset_name.empty()) { + if (delete_current) { + // Find an alternate preset to be selected after the current preset is deleted. + const std::deque &presets = this->m_presets->get_presets(); + size_t idx_current = this->m_presets->get_idx_selected(); + // Find the next visible preset. + size_t idx_new = idx_current + 1; + if (idx_new < presets.size()) + for (; idx_new < presets.size() && ! presets[idx_new].is_visible; ++ idx_new) ; + if (idx_new == presets.size()) + for (idx_new = idx_current - 1; idx_new > 0 && ! presets[idx_new].is_visible; -- idx_new); + preset_name = presets[idx_new].name; + } else { + // If no name is provided, select the "-- default --" preset. + preset_name = m_presets->default_preset().name; + } + } + assert(! delete_current || (m_presets->get_edited_preset().name != preset_name && m_presets->get_edited_preset().is_user())); + bool current_dirty = ! delete_current && m_presets->current_is_dirty(); + bool print_tab = m_presets->type() == Preset::TYPE_PRINT || m_presets->type() == Preset::TYPE_SLA_PRINT; + bool printer_tab = m_presets->type() == Preset::TYPE_PRINTER; + bool canceled = false; + m_dependent_tabs = {}; + if (current_dirty && ! may_discard_current_dirty_preset()) { + canceled = true; + } else if (print_tab) { + // Before switching the print profile to a new one, verify, whether the currently active filament or SLA material + // are compatible with the new print. + // If it is not compatible and the current filament or SLA material are dirty, let user decide + // whether to discard the changes or keep the current print selection. + PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology(); + PresetCollection &dependent = (printer_technology == ptFFF) ? m_preset_bundle->filaments : m_preset_bundle->sla_materials; bool old_preset_dirty = dependent.current_is_dirty(); bool new_preset_compatible = dependent.get_edited_preset().is_compatible_with_print(*m_presets->find_preset(preset_name, true)); if (! canceled) @@ -2914,17 +2914,17 @@ void Tab::select_preset(std::string preset_name, bool delete_current) if (old_preset_dirty) dependent.discard_current_changes(); } - } else if (printer_tab) { - // Before switching the printer to a new one, verify, whether the currently active print and filament - // are compatible with the new printer. - // If they are not compatible and the current print or filament are dirty, let user decide - // whether to discard the changes or keep the current printer selection. - // - // With the introduction of the SLA printer types, we need to support switching between - // the FFF and SLA printers. - const Preset &new_printer_preset = *m_presets->find_preset(preset_name, true); - PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); - PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); + } else if (printer_tab) { + // Before switching the printer to a new one, verify, whether the currently active print and filament + // are compatible with the new printer. + // If they are not compatible and the current print or filament are dirty, let user decide + // whether to discard the changes or keep the current printer selection. + // + // With the introduction of the SLA printer types, we need to support switching between + // the FFF and SLA printers. + const Preset &new_printer_preset = *m_presets->find_preset(preset_name, true); + PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology(); + PrinterTechnology new_printer_technology = new_printer_preset.printer_technology(); if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !may_switch_to_SLA_preset()) canceled = true; else { @@ -2957,100 +2957,100 @@ void Tab::select_preset(std::string preset_name, bool delete_current) } } } - } + } - if (! canceled && delete_current) { - // Delete the file and select some other reasonable preset. - // It does not matter which preset will be made active as the preset will be re-selected from the preset_name variable. - // The 'external' presets will only be removed from the preset list, their files will not be deleted. - try { - m_presets->delete_current_preset(); - } catch (const std::exception & /* e */) { - //FIXME add some error reporting! - canceled = true; - } - } + if (! canceled && delete_current) { + // Delete the file and select some other reasonable preset. + // It does not matter which preset will be made active as the preset will be re-selected from the preset_name variable. + // The 'external' presets will only be removed from the preset list, their files will not be deleted. + try { + m_presets->delete_current_preset(); + } catch (const std::exception & /* e */) { + //FIXME add some error reporting! + canceled = true; + } + } - if (canceled) { - update_tab_ui(); - // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, - // if this action was initiated from the platter. - on_presets_changed(); - } else { - if (current_dirty) - m_presets->discard_current_changes(); + if (canceled) { + update_tab_ui(); + // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, + // if this action was initiated from the platter. + on_presets_changed(); + } else { + if (current_dirty) + m_presets->discard_current_changes(); - const bool is_selected = m_presets->select_preset_by_name(preset_name, false) || delete_current; - assert(m_presets->get_edited_preset().name == preset_name || ! is_selected); - // Mark the print & filament enabled if they are compatible with the currently selected preset. - // The following method should not discard changes of current print or filament presets on change of a printer profile, - // if they are compatible with the current printer. - if (current_dirty || delete_current || print_tab || printer_tab) - m_preset_bundle->update_compatible(true); - // Initialize the UI from the current preset. + const bool is_selected = m_presets->select_preset_by_name(preset_name, false) || delete_current; + assert(m_presets->get_edited_preset().name == preset_name || ! is_selected); + // Mark the print & filament enabled if they are compatible with the currently selected preset. + // The following method should not discard changes of current print or filament presets on change of a printer profile, + // if they are compatible with the current printer. + if (current_dirty || delete_current || print_tab || printer_tab) + m_preset_bundle->update_compatible(true); + // Initialize the UI from the current preset. if (printer_tab) static_cast(this)->update_pages(); if (! is_selected && printer_tab) { /* There is a case, when : - * after Config Wizard applying we try to select previously selected preset, but + * after Config Wizard applying we try to select previously selected preset, but * in a current configuration this one: * 1. doesn't exist now, * 2. have another printer_technology - * So, it is necessary to update list of dependent tabs + * So, it is necessary to update list of dependent tabs * to the corresponding printer_technology */ const PrinterTechnology printer_technology = m_presets->get_edited_preset().printer_technology(); if (printer_technology == ptFFF && m_dependent_tabs.front() != Preset::Type::TYPE_PRINT) - m_dependent_tabs = { Preset::Type::TYPE_PRINT, Preset::Type::TYPE_FILAMENT }; + m_dependent_tabs = { Preset::Type::TYPE_PRINT, Preset::Type::TYPE_FILAMENT }; else if (printer_technology == ptSLA && m_dependent_tabs.front() != Preset::Type::TYPE_SLA_PRINT) m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; } - load_current_preset(); - } + load_current_preset(); + } } // If the current preset is dirty, the user is asked whether the changes may be discarded. // if the current preset was not dirty, or the user agreed to discard the changes, 1 is returned. bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr*/, const std::string& new_printer_name /*= ""*/) { - if (presets == nullptr) presets = m_presets; - // Display a dialog showing the dirty options in a human readable form. - const Preset& old_preset = presets->get_edited_preset(); - std::string type_name = presets->name(); - wxString tab = " "; - wxString name = old_preset.is_default ? - wxString::Format(_(L("Default preset (%s)")), _(type_name)) : - wxString::Format(_(L("Preset (%s)")), _(type_name)) + "\n" + tab + old_preset.name; + if (presets == nullptr) presets = m_presets; + // Display a dialog showing the dirty options in a human readable form. + const Preset& old_preset = presets->get_edited_preset(); + std::string type_name = presets->name(); + wxString tab = " "; + wxString name = old_preset.is_default ? + wxString::Format(_(L("Default preset (%s)")), _(type_name)) : + wxString::Format(_(L("Preset (%s)")), _(type_name)) + "\n" + tab + old_preset.name; - // Collect descriptions of the dirty options. - wxString changes; - for (const std::string &opt_key : presets->current_dirty_options()) { - const ConfigOptionDef &opt = m_config->def()->options.at(opt_key); - /*std::string*/wxString name = ""; - if (! opt.category.empty()) - name += _(opt.category) + " > "; - name += !opt.full_label.empty() ? - _(opt.full_label) : - _(opt.label); - changes += tab + /*from_u8*/(name) + "\n"; - } - // Show a confirmation dialog with the list of dirty options. - wxString message = name + "\n\n"; - if (new_printer_name.empty()) - message += _(L("has the following unsaved changes:")); - else { - message += (m_type == Slic3r::Preset::TYPE_PRINTER) ? - _(L("is not compatible with printer")) : - _(L("is not compatible with print profile")); - message += wxString("\n") + tab + from_u8(new_printer_name) + "\n\n"; - message += _(L("and it has the following unsaved changes:")); - } - auto confirm = new wxMessageDialog(parent(), - message + "\n" + changes + "\n\n" + _(L("Discard changes and continue anyway?")), - _(L("Unsaved Changes")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); - return confirm->ShowModal() == wxID_YES; + // Collect descriptions of the dirty options. + wxString changes; + for (const std::string &opt_key : presets->current_dirty_options()) { + const ConfigOptionDef &opt = m_config->def()->options.at(opt_key); + /*std::string*/wxString name = ""; + if (! opt.category.empty()) + name += _(opt.category) + " > "; + name += !opt.full_label.empty() ? + _(opt.full_label) : + _(opt.label); + changes += tab + /*from_u8*/(name) + "\n"; + } + // Show a confirmation dialog with the list of dirty options. + wxString message = name + "\n\n"; + if (new_printer_name.empty()) + message += _(L("has the following unsaved changes:")); + else { + message += (m_type == Slic3r::Preset::TYPE_PRINTER) ? + _(L("is not compatible with printer")) : + _(L("is not compatible with print profile")); + message += wxString("\n") + tab + from_u8(new_printer_name) + "\n\n"; + message += _(L("and it has the following unsaved changes:")); + } + auto confirm = new wxMessageDialog(parent(), + message + "\n" + changes + "\n\n" + _(L("Discard changes and continue anyway?")), + _(L("Unsaved Changes")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); + return confirm->ShowModal() == wxID_YES; } // If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). @@ -3059,7 +3059,7 @@ bool Tab::may_switch_to_SLA_preset() { if (model_has_multi_part_objects(wxGetApp().model())) { - show_info( parent(), + show_info( parent(), _(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" + _(L("Please check your object list before preset changing.")), _(L("Attention!")) ); @@ -3070,14 +3070,14 @@ bool Tab::may_switch_to_SLA_preset() void Tab::OnTreeSelChange(wxTreeEvent& event) { - if (m_disable_tree_sel_changed_event) + if (m_disable_tree_sel_changed_event) return; // There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/PrusaSlicer/issues/898 and https://github.com/prusa3d/PrusaSlicer/issues/952. // The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason, // we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely. -#ifdef __linux__ - std::unique_ptr no_updates(new wxWindowUpdateLocker(this)); +#ifdef __linux__ + std::unique_ptr no_updates(new wxWindowUpdateLocker(this)); #else // wxWindowUpdateLocker noUpdates(this); #endif @@ -3085,44 +3085,44 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) if (m_pages.empty()) return; - Page* page = nullptr; + Page* page = nullptr; const auto sel_item = m_treectrl->GetSelection(); const auto selection = sel_item ? m_treectrl->GetItemText(sel_item) : ""; for (auto p : m_pages) - if (p->title() == selection) - { - page = p.get(); - m_is_nonsys_values = page->m_is_nonsys_values; - m_is_modified_values = page->m_is_modified_values; - break; - } - if (page == nullptr) return; + if (p->title() == selection) + { + page = p.get(); + m_is_nonsys_values = page->m_is_nonsys_values; + m_is_modified_values = page->m_is_modified_values; + break; + } + if (page == nullptr) return; - for (auto& el : m_pages) + for (auto& el : m_pages) // if (el.get()->IsShown()) { - el.get()->Hide(); + el.get()->Hide(); // break; // } - #ifdef __linux__ - no_updates.reset(nullptr); - #endif + #ifdef __linux__ + no_updates.reset(nullptr); + #endif - update_undo_buttons(); - page->Show(); + update_undo_buttons(); + page->Show(); // if (! page->layout_valid) { - page->layout_valid = true; - m_hsizer->Layout(); - Refresh(); + page->layout_valid = true; + m_hsizer->Layout(); + Refresh(); // } } void Tab::OnKeyDown(wxKeyEvent& event) { - if (event.GetKeyCode() == WXK_TAB) - m_treectrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); - else - event.Skip(); + if (event.GetKeyCode() == WXK_TAB) + m_treectrl->Navigate(event.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); + else + event.Skip(); } // Save the current preset into file. @@ -3132,263 +3132,263 @@ void Tab::OnKeyDown(wxKeyEvent& event) // opens a Slic3r::GUI::SavePresetWindow dialog. void Tab::save_preset(std::string name /*= ""*/) { - // since buttons(and choices too) don't get focus on Mac, we set focus manually - // to the treectrl so that the EVT_* events are fired for the input field having - // focus currently.is there anything better than this ? + // since buttons(and choices too) don't get focus on Mac, we set focus manually + // to the treectrl so that the EVT_* events are fired for the input field having + // focus currently.is there anything better than this ? //! m_treectrl->OnSetFocus(); - if (name.empty()) { - const Preset &preset = m_presets->get_selected_preset(); + if (name.empty()) { + const Preset &preset = m_presets->get_selected_preset(); auto default_name = preset.is_default ? "Untitled" : - preset.is_system ? (boost::format(_utf8(L("%1% - Copy"))) % preset.name).str() : - preset.name; + preset.is_system ? (boost::format(_utf8(L("%1% - Copy"))) % preset.name).str() : + preset.name; - bool have_extention = boost::iends_with(default_name, ".ini"); - if (have_extention) { - size_t len = default_name.length()-4; - default_name.resize(len); - } - //[map $_->name, grep !$_->default && !$_->external, @{$self->{presets}}], - std::vector values; - for (size_t i = 0; i < m_presets->size(); ++i) { - const Preset &preset = m_presets->preset(i); - if (preset.is_default || preset.is_system || preset.is_external) - continue; - values.push_back(preset.name); - } + bool have_extention = boost::iends_with(default_name, ".ini"); + if (have_extention) { + size_t len = default_name.length()-4; + default_name.resize(len); + } + //[map $_->name, grep !$_->default && !$_->external, @{$self->{presets}}], + std::vector values; + for (size_t i = 0; i < m_presets->size(); ++i) { + const Preset &preset = m_presets->preset(i); + if (preset.is_default || preset.is_system || preset.is_external) + continue; + values.push_back(preset.name); + } - auto dlg = new SavePresetWindow(parent()); - dlg->build(title(), default_name, values); - if (dlg->ShowModal() != wxID_OK) - return; - name = dlg->get_name(); - if (name == "") { - show_error(this, _(L("The supplied name is empty. It can't be saved."))); - return; - } - const Preset *existing = m_presets->find_preset(name, false); - if (existing && (existing->is_default || existing->is_system)) { - show_error(this, _(L("Cannot overwrite a system profile."))); - return; - } - if (existing && (existing->is_external)) { - show_error(this, _(L("Cannot overwrite an external profile."))); - return; - } - } + auto dlg = new SavePresetWindow(parent()); + dlg->build(title(), default_name, values); + if (dlg->ShowModal() != wxID_OK) + return; + name = dlg->get_name(); + if (name == "") { + show_error(this, _(L("The supplied name is empty. It can't be saved."))); + return; + } + const Preset *existing = m_presets->find_preset(name, false); + if (existing && (existing->is_default || existing->is_system)) { + show_error(this, _(L("Cannot overwrite a system profile."))); + return; + } + if (existing && (existing->is_external)) { + show_error(this, _(L("Cannot overwrite an external profile."))); + return; + } + } - // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini - m_presets->save_current_preset(name); - // Mark the print & filament enabled if they are compatible with the currently selected preset. - m_preset_bundle->update_compatible(false); - // Add the new item into the UI component, remove dirty flags and activate the saved item. - update_tab_ui(); - // Update the selection boxes at the platter. - on_presets_changed(); - // If current profile is saved, "delete preset" button have to be enabled - m_btn_delete_preset->Enable(true); + // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini + m_presets->save_current_preset(name); + // Mark the print & filament enabled if they are compatible with the currently selected preset. + m_preset_bundle->update_compatible(false); + // Add the new item into the UI component, remove dirty flags and activate the saved item. + update_tab_ui(); + // Update the selection boxes at the platter. + on_presets_changed(); + // If current profile is saved, "delete preset" button have to be enabled + m_btn_delete_preset->Enable(true); - if (m_type == Preset::TYPE_PRINTER) - static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; - update_changed_ui(); + if (m_type == Preset::TYPE_PRINTER) + static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; + update_changed_ui(); } // Called for a currently selected preset. void Tab::delete_preset() { - auto current_preset = m_presets->get_selected_preset(); - // Don't let the user delete the ' - default - ' configuration. + auto current_preset = m_presets->get_selected_preset(); + // Don't let the user delete the ' - default - ' configuration. std::string action = current_preset.is_external ? _utf8(L("remove")) : _utf8(L("delete")); // TRN remove/delete const wxString msg = from_u8((boost::format(_utf8(L("Are you sure you want to %1% the selected preset?"))) % action).str()); - action = current_preset.is_external ? _utf8(L("Remove")) : _utf8(L("Delete")); - // TRN Remove/Delete + action = current_preset.is_external ? _utf8(L("Remove")) : _utf8(L("Delete")); + // TRN Remove/Delete wxString title = from_u8((boost::format(_utf8(L("%1% Preset"))) % action).str()); //action + _(L(" Preset")); - if (current_preset.is_default || - wxID_YES != wxMessageDialog(parent(), msg, title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal()) - return; - // Select will handle of the preset dependencies, of saving & closing the depending profiles, and - // finally of deleting the preset. - this->select_preset("", true); + if (current_preset.is_default || + wxID_YES != wxMessageDialog(parent(), msg, title, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal()) + return; + // Select will handle of the preset dependencies, of saving & closing the depending profiles, and + // finally of deleting the preset. + this->select_preset("", true); } void Tab::toggle_show_hide_incompatible() { - m_show_incompatible_presets = !m_show_incompatible_presets; - update_show_hide_incompatible_button(); - update_tab_ui(); + m_show_incompatible_presets = !m_show_incompatible_presets; + update_show_hide_incompatible_button(); + update_tab_ui(); } void Tab::update_show_hide_incompatible_button() { - m_btn_hide_incompatible_presets->SetBitmap_(m_show_incompatible_presets ? - m_bmp_show_incompatible_presets : m_bmp_hide_incompatible_presets); - m_btn_hide_incompatible_presets->SetToolTip(m_show_incompatible_presets ? - "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : - "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer."); + m_btn_hide_incompatible_presets->SetBitmap_(m_show_incompatible_presets ? + m_bmp_show_incompatible_presets : m_bmp_hide_incompatible_presets); + m_btn_hide_incompatible_presets->SetToolTip(m_show_incompatible_presets ? + "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : + "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer."); } void Tab::update_ui_from_settings() { - // Show the 'show / hide presets' button only for the print and filament tabs, and only if enabled - // in application preferences. - m_show_btn_incompatible_presets = wxGetApp().app_config->get("show_incompatible_presets")[0] == '1' ? true : false; - bool show = m_show_btn_incompatible_presets && m_type != Slic3r::Preset::TYPE_PRINTER; - Layout(); - show ? m_btn_hide_incompatible_presets->Show() : m_btn_hide_incompatible_presets->Hide(); - // If the 'show / hide presets' button is hidden, hide the incompatible presets. - if (show) { - update_show_hide_incompatible_button(); - } - else { - if (m_show_incompatible_presets) { - m_show_incompatible_presets = false; - update_tab_ui(); - } - } + // Show the 'show / hide presets' button only for the print and filament tabs, and only if enabled + // in application preferences. + m_show_btn_incompatible_presets = wxGetApp().app_config->get("show_incompatible_presets")[0] == '1' ? true : false; + bool show = m_show_btn_incompatible_presets && m_type != Slic3r::Preset::TYPE_PRINTER; + Layout(); + show ? m_btn_hide_incompatible_presets->Show() : m_btn_hide_incompatible_presets->Hide(); + // If the 'show / hide presets' button is hidden, hide the incompatible presets. + if (show) { + update_show_hide_incompatible_button(); + } + else { + if (m_show_incompatible_presets) { + m_show_incompatible_presets = false; + update_tab_ui(); + } + } } // Return a callback to create a Tab widget to mark the preferences as compatible / incompatible to the current printer. wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &deps) { - deps.checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); - deps.checkbox->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + deps.checkbox = new wxCheckBox(parent, wxID_ANY, _(L("All"))); + deps.checkbox->SetFont(Slic3r::GUI::wxGetApp().normal_font()); add_scaled_button(parent, &deps.btn, "printer_white", wxString::Format(" %s %s", _(L("Set")), dots), wxBU_LEFT | wxBU_EXACTFIT); deps.btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add((deps.checkbox), 0, wxALIGN_CENTER_VERTICAL); - sizer->Add((deps.btn), 0, wxALIGN_CENTER_VERTICAL); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add((deps.checkbox), 0, wxALIGN_CENTER_VERTICAL); + sizer->Add((deps.btn), 0, wxALIGN_CENTER_VERTICAL); - deps.checkbox->Bind(wxEVT_CHECKBOX, ([this, &deps](wxCommandEvent e) - { - deps.btn->Enable(! deps.checkbox->GetValue()); - // All printers have been made compatible with this preset. - if (deps.checkbox->GetValue()) - this->load_key_value(deps.key_list, std::vector {}); - this->get_field(deps.key_condition)->toggle(deps.checkbox->GetValue()); - this->update_changed_ui(); - }) ); + deps.checkbox->Bind(wxEVT_CHECKBOX, ([this, &deps](wxCommandEvent e) + { + deps.btn->Enable(! deps.checkbox->GetValue()); + // All printers have been made compatible with this preset. + if (deps.checkbox->GetValue()) + this->load_key_value(deps.key_list, std::vector {}); + this->get_field(deps.key_condition)->toggle(deps.checkbox->GetValue()); + this->update_changed_ui(); + }) ); - deps.btn->Bind(wxEVT_BUTTON, ([this, parent, &deps](wxCommandEvent e) - { - // Collect names of non-default non-external profiles. - PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology(); - PresetCollection &depending_presets = (deps.type == Preset::TYPE_PRINTER) ? m_preset_bundle->printers : - (printer_technology == ptFFF) ? m_preset_bundle->prints : m_preset_bundle->sla_prints; - wxArrayString presets; - for (size_t idx = 0; idx < depending_presets.size(); ++ idx) - { - Preset& preset = depending_presets.preset(idx); - bool add = ! preset.is_default && ! preset.is_external; - if (add && deps.type == Preset::TYPE_PRINTER) - // Only add printers with the same technology as the active printer. - add &= preset.printer_technology() == printer_technology; - if (add) - presets.Add(from_u8(preset.name)); - } + deps.btn->Bind(wxEVT_BUTTON, ([this, parent, &deps](wxCommandEvent e) + { + // Collect names of non-default non-external profiles. + PrinterTechnology printer_technology = m_preset_bundle->printers.get_edited_preset().printer_technology(); + PresetCollection &depending_presets = (deps.type == Preset::TYPE_PRINTER) ? m_preset_bundle->printers : + (printer_technology == ptFFF) ? m_preset_bundle->prints : m_preset_bundle->sla_prints; + wxArrayString presets; + for (size_t idx = 0; idx < depending_presets.size(); ++ idx) + { + Preset& preset = depending_presets.preset(idx); + bool add = ! preset.is_default && ! preset.is_external; + if (add && deps.type == Preset::TYPE_PRINTER) + // Only add printers with the same technology as the active printer. + add &= preset.printer_technology() == printer_technology; + if (add) + presets.Add(from_u8(preset.name)); + } - wxMultiChoiceDialog dlg(parent, deps.dialog_title, deps.dialog_label, presets); - // Collect and set indices of depending_presets marked as compatible. - wxArrayInt selections; - auto *compatible_printers = dynamic_cast(m_config->option(deps.key_list)); - if (compatible_printers != nullptr || !compatible_printers->values.empty()) - for (auto preset_name : compatible_printers->values) - for (size_t idx = 0; idx < presets.GetCount(); ++idx) - if (presets[idx] == preset_name) { - selections.Add(idx); - break; - } - dlg.SetSelections(selections); - std::vector value; - // Show the dialog. - if (dlg.ShowModal() == wxID_OK) { - selections.Clear(); - selections = dlg.GetSelections(); - for (auto idx : selections) - value.push_back(presets[idx].ToUTF8().data()); - if (value.empty()) { - deps.checkbox->SetValue(1); - deps.btn->Disable(); - } - // All depending_presets have been made compatible with this preset. - this->load_key_value(deps.key_list, value); - this->update_changed_ui(); - } - })); - return sizer; + wxMultiChoiceDialog dlg(parent, deps.dialog_title, deps.dialog_label, presets); + // Collect and set indices of depending_presets marked as compatible. + wxArrayInt selections; + auto *compatible_printers = dynamic_cast(m_config->option(deps.key_list)); + if (compatible_printers != nullptr || !compatible_printers->values.empty()) + for (auto preset_name : compatible_printers->values) + for (size_t idx = 0; idx < presets.GetCount(); ++idx) + if (presets[idx] == preset_name) { + selections.Add(idx); + break; + } + dlg.SetSelections(selections); + std::vector value; + // Show the dialog. + if (dlg.ShowModal() == wxID_OK) { + selections.Clear(); + selections = dlg.GetSelections(); + for (auto idx : selections) + value.push_back(presets[idx].ToUTF8().data()); + if (value.empty()) { + deps.checkbox->SetValue(1); + deps.btn->Disable(); + } + // All depending_presets have been made compatible with this preset. + this->load_key_value(deps.key_list, value); + this->update_changed_ui(); + } + })); + return sizer; } void Tab::compatible_widget_reload(PresetDependencies &deps) { - bool has_any = ! m_config->option(deps.key_list)->values.empty(); - has_any ? deps.btn->Enable() : deps.btn->Disable(); - deps.checkbox->SetValue(! has_any); - this->get_field(deps.key_condition)->toggle(! has_any); + bool has_any = ! m_config->option(deps.key_list)->values.empty(); + has_any ? deps.btn->Enable() : deps.btn->Disable(); + deps.checkbox->SetValue(! has_any); + this->get_field(deps.key_condition)->toggle(! has_any); } void Tab::fill_icon_descriptions() { - m_icon_descriptions.emplace_back(&m_bmp_value_lock, L("LOCKED LOCK"), + m_icon_descriptions.emplace_back(&m_bmp_value_lock, L("LOCKED LOCK"), // TRN Description for "LOCKED LOCK" - L("indicates that the settings are the same as the system (or default) values for the current option group")); + L("indicates that the settings are the same as the system (or default) values for the current option group")); m_icon_descriptions.emplace_back(&m_bmp_value_unlock, L("UNLOCKED LOCK"), // TRN Description for "UNLOCKED LOCK" - L("indicates that some settings were changed and are not equal to the system (or default) values for " - "the current option group.\n" - "Click the UNLOCKED LOCK icon to reset all settings for current option group to " - "the system (or default) values.")); + L("indicates that some settings were changed and are not equal to the system (or default) values for " + "the current option group.\n" + "Click the UNLOCKED LOCK icon to reset all settings for current option group to " + "the system (or default) values.")); m_icon_descriptions.emplace_back(&m_bmp_white_bullet, L("WHITE BULLET"), // TRN Description for "WHITE BULLET" L("for the left button: \tindicates a non-system (or non-default) preset,\n" - "for the right button: \tindicates that the settings hasn't been modified.")); + "for the right button: \tindicates that the settings hasn't been modified.")); m_icon_descriptions.emplace_back(&m_bmp_value_revert, L("BACK ARROW"), // TRN Description for "BACK ARROW" L("indicates that the settings were changed and are not equal to the last saved preset for " - "the current option group.\n" - "Click the BACK ARROW icon to reset all settings for the current option group to " - "the last saved preset.")); + "the current option group.\n" + "Click the BACK ARROW icon to reset all settings for the current option group to " + "the last saved preset.")); } void Tab::set_tooltips_text() { - // --- Tooltip text for reset buttons (for whole options group) - // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - m_ttg_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system (or default) values " - "for the current option group")); - m_ttg_value_unlock = _(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " - "to the system (or default) values for the current option group.\n" - "Click to reset all settings for current option group to the system (or default) values.")); - m_ttg_white_bullet_ns = _(L("WHITE BULLET icon indicates a non system (or non default) preset.")); - m_ttg_non_system = &m_ttg_white_bullet_ns; - // Text to be shown on the "Undo user changes" button next to each input field. - m_ttg_white_bullet = _(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " - "preset for the current option group.")); - m_ttg_value_revert = _(L("BACK ARROW icon indicates that the settings were changed and are not equal to " - "the last saved preset for the current option group.\n" - "Click to reset all settings for the current option group to the last saved preset.")); + // --- Tooltip text for reset buttons (for whole options group) + // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + m_ttg_value_lock = _(L("LOCKED LOCK icon indicates that the settings are the same as the system (or default) values " + "for the current option group")); + m_ttg_value_unlock = _(L("UNLOCKED LOCK icon indicates that some settings were changed and are not equal " + "to the system (or default) values for the current option group.\n" + "Click to reset all settings for current option group to the system (or default) values.")); + m_ttg_white_bullet_ns = _(L("WHITE BULLET icon indicates a non system (or non default) preset.")); + m_ttg_non_system = &m_ttg_white_bullet_ns; + // Text to be shown on the "Undo user changes" button next to each input field. + m_ttg_white_bullet = _(L("WHITE BULLET icon indicates that the settings are the same as in the last saved " + "preset for the current option group.")); + m_ttg_value_revert = _(L("BACK ARROW icon indicates that the settings were changed and are not equal to " + "the last saved preset for the current option group.\n" + "Click to reset all settings for the current option group to the last saved preset.")); - // --- Tooltip text for reset buttons (for each option in group) - // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the value is the same as the system (or default) value.")); - m_tt_value_unlock = _(L("UNLOCKED LOCK icon indicates that the value was changed and is not equal " - "to the system (or default) value.\n" - "Click to reset current value to the system (or default) value.")); - // m_tt_white_bullet_ns= _(L("WHITE BULLET icon indicates a non system preset.")); - m_tt_non_system = &m_ttg_white_bullet_ns; - // Text to be shown on the "Undo user changes" button next to each input field. - m_tt_white_bullet = _(L("WHITE BULLET icon indicates that the value is the same as in the last saved preset.")); - m_tt_value_revert = _(L("BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\n" - "Click to reset current value to the last saved preset.")); + // --- Tooltip text for reset buttons (for each option in group) + // Text to be shown on the "Revert to system" aka "Lock to system" button next to each input field. + m_tt_value_lock = _(L("LOCKED LOCK icon indicates that the value is the same as the system (or default) value.")); + m_tt_value_unlock = _(L("UNLOCKED LOCK icon indicates that the value was changed and is not equal " + "to the system (or default) value.\n" + "Click to reset current value to the system (or default) value.")); + // m_tt_white_bullet_ns= _(L("WHITE BULLET icon indicates a non system preset.")); + m_tt_non_system = &m_ttg_white_bullet_ns; + // Text to be shown on the "Undo user changes" button next to each input field. + m_tt_white_bullet = _(L("WHITE BULLET icon indicates that the value is the same as in the last saved preset.")); + m_tt_value_revert = _(L("BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\n" + "Click to reset current value to the last saved preset.")); } void Page::reload_config() { - for (auto group : m_optgroups) - group->reload_config(); + for (auto group : m_optgroups) + group->reload_config(); } void Page::update_visibility(ConfigOptionMode mode) @@ -3408,22 +3408,22 @@ void Page::msw_rescale() Field* Page::get_field(const t_config_option_key& opt_key, int opt_index /*= -1*/) const { - Field* field = nullptr; - for (auto opt : m_optgroups) { - field = opt->get_fieldc(opt_key, opt_index); - if (field != nullptr) - return field; - } - return field; + Field* field = nullptr; + for (auto opt : m_optgroups) { + field = opt->get_fieldc(opt_key, opt_index); + if (field != nullptr) + return field; + } + return field; } bool Page::set_value(const t_config_option_key& opt_key, const boost::any& value) { - bool changed = false; - for(auto optgroup: m_optgroups) { - if (optgroup->set_value(opt_key, value)) - changed = 1 ; - } - return changed; + bool changed = false; + for(auto optgroup: m_optgroups) { + if (optgroup->set_value(opt_key, value)) + changed = 1 ; + } + return changed; } // package Slic3r::GUI::Tab::Page; @@ -3443,39 +3443,39 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la return bmp; }; - //! config_ have to be "right" - ConfigOptionsGroupShp optgroup = std::make_shared(this, title, m_config, true, extra_column); - if (noncommon_label_width >= 0) - optgroup->label_width = noncommon_label_width; + //! config_ have to be "right" + ConfigOptionsGroupShp optgroup = std::make_shared(this, title, m_config, true, extra_column); + if (noncommon_label_width >= 0) + optgroup->label_width = noncommon_label_width; #ifdef __WXOSX__ - auto tab = GetParent()->GetParent(); + auto tab = GetParent()->GetParent(); #else - auto tab = GetParent(); + auto tab = GetParent(); #endif - optgroup->m_on_change = [this, tab](t_config_option_key opt_key, boost::any value) { - //! This function will be called from OptionGroup. - //! Using of CallAfter is redundant. - //! And in some cases it causes update() function to be recalled again + optgroup->m_on_change = [this, tab](t_config_option_key opt_key, boost::any value) { + //! This function will be called from OptionGroup. + //! Using of CallAfter is redundant. + //! And in some cases it causes update() function to be recalled again //! wxTheApp->CallAfter([this, opt_key, value]() { - static_cast(tab)->update_dirty(); - static_cast(tab)->on_value_change(opt_key, value); + static_cast(tab)->update_dirty(); + static_cast(tab)->on_value_change(opt_key, value); //! }); - }; + }; - optgroup->m_get_initial_config = [this, tab]() { - DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset().config; - return config; - }; + optgroup->m_get_initial_config = [this, tab]() { + DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset().config; + return config; + }; - optgroup->m_get_sys_config = [this, tab]() { - DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset_parent()->config; - return config; - }; + optgroup->m_get_sys_config = [this, tab]() { + DynamicPrintConfig config = static_cast(tab)->m_presets->get_selected_preset_parent()->config; + return config; + }; - optgroup->have_sys_config = [this, tab]() { - return static_cast(tab)->m_presets->get_selected_preset_parent() != nullptr; - }; + optgroup->have_sys_config = [this, tab]() { + return static_cast(tab)->m_presets->get_selected_preset_parent() != nullptr; + }; optgroup->rescale_extra_column_item = [this](wxWindow* win) { auto *ctrl = dynamic_cast(win); @@ -3485,69 +3485,69 @@ ConfigOptionsGroupShp Page::new_optgroup(const wxString& title, int noncommon_la ctrl->SetBitmap(reinterpret_cast(ctrl->GetClientData())->bmp()); }; - vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); - m_optgroups.push_back(optgroup); + vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); + m_optgroups.push_back(optgroup); - return optgroup; + return optgroup; } void SavePresetWindow::build(const wxString& title, const std::string& default_name, std::vector &values) { // TRN Preset - auto text = new wxStaticText(this, wxID_ANY, wxString::Format(_(L("Save %s as:")), title), - wxDefaultPosition, wxDefaultSize); - m_combo = new wxComboBox(this, wxID_ANY, from_u8(default_name), - wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER); - for (auto value : values) - m_combo->Append(from_u8(value)); - auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); + auto text = new wxStaticText(this, wxID_ANY, wxString::Format(_(L("Save %s as:")), title), + wxDefaultPosition, wxDefaultSize); + m_combo = new wxComboBox(this, wxID_ANY, from_u8(default_name), + wxDefaultPosition, wxDefaultSize, 0, 0, wxTE_PROCESS_ENTER); + for (auto value : values) + m_combo->Append(from_u8(value)); + auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); - auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(text, 0, wxEXPAND | wxALL, 10); - sizer->Add(m_combo, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); - sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10); + auto sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(text, 0, wxEXPAND | wxALL, 10); + sizer->Add(m_combo, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); + sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10); - wxButton* btn = static_cast(FindWindowById(wxID_OK, this)); - btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); - m_combo->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&) { accept(); }); + wxButton* btn = static_cast(FindWindowById(wxID_OK, this)); + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); + m_combo->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent&) { accept(); }); - SetSizer(sizer); - sizer->SetSizeHints(this); + SetSizer(sizer); + sizer->SetSizeHints(this); } void SavePresetWindow::accept() { - m_chosen_name = normalize_utf8_nfc(m_combo->GetValue().ToUTF8()); - if (!m_chosen_name.empty()) { - const char* unusable_symbols = "<>[]:/\\|?*\""; - bool is_unusable_symbol = false; - bool is_unusable_suffix = false; - const std::string unusable_suffix = PresetCollection::get_suffix_modified();//"(modified)"; - for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { - if (m_chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { - is_unusable_symbol = true; - break; - } - } - if (m_chosen_name.find(unusable_suffix) != std::string::npos) - is_unusable_suffix = true; + m_chosen_name = normalize_utf8_nfc(m_combo->GetValue().ToUTF8()); + if (!m_chosen_name.empty()) { + const char* unusable_symbols = "<>[]:/\\|?*\""; + bool is_unusable_symbol = false; + bool is_unusable_suffix = false; + const std::string unusable_suffix = PresetCollection::get_suffix_modified();//"(modified)"; + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (m_chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { + is_unusable_symbol = true; + break; + } + } + if (m_chosen_name.find(unusable_suffix) != std::string::npos) + is_unusable_suffix = true; - if (is_unusable_symbol) { - show_error(this,_(L("The supplied name is not valid;")) + "\n" + - _(L("the following characters are not allowed:")) + " " + unusable_symbols); - } - else if (is_unusable_suffix) { - show_error(this,_(L("The supplied name is not valid;")) + "\n" + - _(L("the following suffix is not allowed:")) + "\n\t" + - wxString::FromUTF8(unusable_suffix.c_str())); - } - else if (m_chosen_name == "- default -") { - show_error(this, _(L("The supplied name is not available."))); - } - else { - EndModal(wxID_OK); - } - } + if (is_unusable_symbol) { + show_error(this,_(L("The supplied name is not valid;")) + "\n" + + _(L("the following characters are not allowed:")) + " " + unusable_symbols); + } + else if (is_unusable_suffix) { + show_error(this,_(L("The supplied name is not valid;")) + "\n" + + _(L("the following suffix is not allowed:")) + "\n\t" + + wxString::FromUTF8(unusable_suffix.c_str())); + } + else if (m_chosen_name == "- default -") { + show_error(this, _(L("The supplied name is not available."))); + } + else { + EndModal(wxID_OK); + } + } } void TabSLAMaterial::build() @@ -3604,12 +3604,12 @@ void TabSLAMaterial::build() line = optgroup->create_single_option_line("compatible_prints"); line.widget = [this](wxWindow* parent) { - return compatible_widget_create(parent, m_compatible_prints); - }; - optgroup->append_line(line, &m_colored_Label); - option = optgroup->get_option("compatible_prints_condition"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + return compatible_widget_create(parent, m_compatible_prints); + }; + optgroup->append_line(line, &m_colored_Label); + option = optgroup->get_option("compatible_prints_condition"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); line = Line{ "", "" }; line.full_width = 1; @@ -3622,21 +3622,21 @@ void TabSLAMaterial::build() // Reload current config (aka presets->edited_preset->config) into the UI fields. void TabSLAMaterial::reload_config() { - this->compatible_widget_reload(m_compatible_printers); - this->compatible_widget_reload(m_compatible_prints); - Tab::reload_config(); + this->compatible_widget_reload(m_compatible_printers); + this->compatible_widget_reload(m_compatible_prints); + Tab::reload_config(); } void TabSLAMaterial::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) return; - + // #ys_FIXME. Just a template for this function // m_update_cnt++; // ! something to update // m_update_cnt--; -// +// // if (m_update_cnt == 0) wxGetApp().mainframe->on_config_changed(m_config); } @@ -3690,21 +3690,22 @@ void TabSLAPrint::build() // TODO: Disabling this parameter for the beta release // optgroup->append_single_option_line("pad_edge_radius"); optgroup->append_single_option_line("pad_wall_slope"); - + + optgroup->append_single_option_line("pad_zero_elevation"); optgroup->append_single_option_line("pad_object_gap"); optgroup->append_single_option_line("pad_object_connector_stride"); optgroup->append_single_option_line("pad_object_connector_width"); optgroup->append_single_option_line("pad_object_connector_penetration"); - - page = add_options_page(_(L("Advanced")), "wrench"); - optgroup = page->new_optgroup(_(L("Slicing"))); - optgroup->append_single_option_line("slice_closing_radius"); - page = add_options_page(_(L("Output options")), "output+page_white"); - optgroup = page->new_optgroup(_(L("Output file"))); - Option option = optgroup->get_option("output_filename_format"); - option.opt.full_width = true; - optgroup->append_single_option_line(option); + page = add_options_page(_(L("Advanced")), "wrench"); + optgroup = page->new_optgroup(_(L("Slicing"))); + optgroup->append_single_option_line("slice_closing_radius"); + + page = add_options_page(_(L("Output options")), "output+page_white"); + optgroup = page->new_optgroup(_(L("Output file"))); + Option option = optgroup->get_option("output_filename_format"); + option.opt.full_width = true; + optgroup->append_single_option_line(option); page = add_options_page(_(L("Dependencies")), "wrench"); optgroup = page->new_optgroup(_(L("Profile dependencies"))); @@ -3729,8 +3730,8 @@ void TabSLAPrint::build() // Reload current config (aka presets->edited_preset->config) into the UI fields. void TabSLAPrint::reload_config() { - this->compatible_widget_reload(m_compatible_printers); - Tab::reload_config(); + this->compatible_widget_reload(m_compatible_printers); + Tab::reload_config(); } void TabSLAPrint::update() @@ -3738,7 +3739,33 @@ void TabSLAPrint::update() if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) return; - m_update_cnt++; + m_update_cnt++; + + bool supports_en = m_config->opt_bool("supports_enable"); + + get_field("support_head_front_diameter")->toggle(supports_en); + get_field("support_head_penetration")->toggle(supports_en); + get_field("support_head_width")->toggle(supports_en); + get_field("support_pillar_diameter")->toggle(supports_en); + get_field("support_pillar_connection_mode")->toggle(supports_en); + get_field("support_buildplate_only")->toggle(supports_en); + get_field("support_base_diameter")->toggle(supports_en); + get_field("support_base_height")->toggle(supports_en); + get_field("support_base_safety_distance")->toggle(supports_en); + get_field("support_critical_angle")->toggle(supports_en); + get_field("support_max_bridge_length")->toggle(supports_en); + get_field("support_max_pillar_link_distance")->toggle(supports_en); + get_field("support_points_density_relative")->toggle(supports_en); + get_field("support_points_minimal_distance")->toggle(supports_en); + + bool pad_en = m_config->opt_bool("pad_enable"); + + get_field("pad_wall_thickness")->toggle(pad_en); + get_field("pad_wall_height")->toggle(pad_en); + get_field("pad_max_merge_distance")->toggle(pad_en); + // get_field("pad_edge_radius")->toggle(supports_en); + get_field("pad_wall_slope")->toggle(pad_en); + get_field("pad_zero_elevation")->toggle(pad_en); double head_penetration = m_config->opt_float("support_head_penetration"); double head_width = m_config->opt_float("support_head_width"); @@ -3779,14 +3806,15 @@ void TabSLAPrint::update() load_config(new_conf); } - - // if(m_config->opt_float("support_object_elevation") < EPSILON && - // m_config->opt_bool("pad_enable")) { - // // TODO: disable editding of: - // // pad_object_connector_stride - // // pad_object_connector_width - // // pad_object_connector_penetration - // } + + bool has_suppad = pad_en && supports_en; + bool zero_elev = m_config->opt_bool("pad_zero_elevation") && has_suppad; + + get_field("support_object_elevation")->toggle(supports_en && !zero_elev); + get_field("pad_object_gap")->toggle(zero_elev); + get_field("pad_object_connector_stride")->toggle(zero_elev); + get_field("pad_object_connector_width")->toggle(zero_elev); + get_field("pad_object_connector_penetration")->toggle(zero_elev); m_update_cnt--; From 09ffbc9d4c7ec0d19a294c1051f31436c9b3cb9b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Aug 2019 16:53:17 +0200 Subject: [PATCH 508/627] Add new entry to keyboard shortcuts for 'arrange selection' --- src/slic3r/GUI/KBShortcutsDialog.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 955c4b60b9..38d02a98a5 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -6,23 +6,23 @@ #include "GUI_App.hpp" #include "wxExtensions.hpp" -namespace Slic3r { +namespace Slic3r { namespace GUI { KBShortcutsDialog::KBShortcutsDialog() - : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Keyboard Shortcuts")), + : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Keyboard Shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - auto main_sizer = new wxBoxSizer(wxVERTICAL); + auto main_sizer = new wxBoxSizer(wxVERTICAL); // logo m_logo_bmp = ScalableBitmap(this, "PrusaSlicer_32px.png", 32); // fonts const wxFont& font = wxGetApp().normal_font(); - const wxFont& bold_font = wxGetApp().bold_font(); + const wxFont& bold_font = wxGetApp().bold_font(); SetFont(font); wxFont head_font = bold_font; @@ -78,17 +78,17 @@ KBShortcutsDialog::KBShortcutsDialog() grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL); } } - + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK); this->SetEscapeId(wxID_OK); this->Bind(wxEVT_BUTTON, &KBShortcutsDialog::onCloseDialog, this, wxID_OK); main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 15); - + this->Bind(wxEVT_LEFT_DOWN, &KBShortcutsDialog::onCloseDialog, this); - SetSizer(main_sizer); - main_sizer->SetSizeHints(this); + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); } void KBShortcutsDialog::fill_shortcuts() @@ -132,6 +132,7 @@ void KBShortcutsDialog::fill_shortcuts() plater_shortcuts.reserve(20); plater_shortcuts.push_back(Shortcut("A", L("Arrange"))); + plater_shortcuts.push_back(Shortcut("Shift+A", L("Arrange selection"))); plater_shortcuts.push_back(Shortcut(ctrl+"A", L("Select All objects"))); plater_shortcuts.push_back(Shortcut("Del", L("Delete selected"))); plater_shortcuts.push_back(Shortcut(ctrl+"Del", L("Delete All"))); @@ -163,10 +164,10 @@ void KBShortcutsDialog::fill_shortcuts() // Shortcuts gizmo_shortcuts; // gizmo_shortcuts.reserve(2); -// +// // gizmo_shortcuts.push_back(Shortcut("Shift+", L("Press to snap by 5% in Gizmo Scale\n or by 1mm in Gizmo Move"))); // gizmo_shortcuts.push_back(Shortcut(alt, L("Press to scale or rotate selected objects around their own center"))); -// +// // m_full_shortcuts.push_back(std::make_pair(_(L("Gizmo Shortcuts")), std::make_pair(gizmo_shortcuts, 1))); From c7962b5c2045e1cb0356e10bec225bccdd011fbe Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 6 Aug 2019 17:10:03 +0200 Subject: [PATCH 509/627] Added missing include --- src/slic3r/GUI/GLTexture.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index 2c8941632c..c4063b93d4 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_GLTexture_hpp_ #define slic3r_GLTexture_hpp_ +#include #include #include #include From 621a552dc0bce535d7b11d47770f593167be39e3 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 6 Aug 2019 17:27:36 +0200 Subject: [PATCH 510/627] Redirect requests for the Prusa web pages with Slovak locale active to Czech web pages. --- src/slic3r/GUI/GUI_App.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 8a785760c6..dd107550ec 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -986,6 +986,7 @@ wxString GUI_App::current_language_code_safe() const language_code = language_code.substr(0, idx_underscore); const std::map mapping { { "cs", "cs_CZ", }, + { "sk", "cs_CZ", }, { "de", "de_DE", }, { "es", "es_ES", }, { "fr", "fr_FR", }, From b8bfe001c02b034e3d8d233e687cde6b90a026c4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 6 Aug 2019 18:16:02 +0200 Subject: [PATCH 511/627] WIP: Pad quick-menu --- src/slic3r/GUI/Plater.cpp | 704 +++++++++++++++++++------------------- 1 file changed, 360 insertions(+), 344 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bb188713ba..a3d7962f5e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -256,7 +256,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * evt.StopPropagation(); if (marker == LABEL_ITEM_CONFIG_WIZARD) wxTheApp->CallAfter([]() { Slic3r::GUI::config_wizard(Slic3r::GUI::ConfigWizard::RR_USER); }); - } else if ( this->last_selected != selected_item || + } else if ( this->last_selected != selected_item || wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) { this->last_selected = selected_item; evt.SetInt(this->preset_type); @@ -265,7 +265,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * evt.StopPropagation(); } }); - + if (preset_type == Slic3r::Preset::TYPE_FILAMENT) { Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) { @@ -282,7 +282,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * event.Skip(); return; } - + // Swallow the mouse click and open the color picker. // get current color @@ -302,7 +302,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * { colors->values[extruder_idx] = dialog->GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX); - DynamicPrintConfig cfg_new = *cfg; + DynamicPrintConfig cfg_new = *cfg; cfg_new.set_key_value("extruder_colour", colors); wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new); @@ -328,14 +328,14 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * wxGetApp().tab_panel()->ChangeSelection(page_id); - /* In a case of a multi-material printing, for editing another Filament Preset - * it's needed to select this preset for the "Filament settings" Tab + /* In a case of a multi-material printing, for editing another Filament Preset + * it's needed to select this preset for the "Filament settings" Tab */ - if (preset_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) + if (preset_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) { const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data(); - // Call select_preset() only if there is new preset and not just modified + // Call select_preset() only if there is new preset and not just modified if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) ) tab->select_preset(selected_preset); } @@ -459,7 +459,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : tab_print->update_dirty(); }; - + Line line = Line { "", "" }; ConfigOptionDef support_def; @@ -491,7 +491,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : m_og->append_line(line); - + line = Line { "", "" }; option = m_og->get_option("fill_density"); @@ -533,7 +533,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : } })); - auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); sizer->Add(btn , 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, int(0.3 * wxGetApp().em_unit())); @@ -556,14 +556,21 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT); if (!tab) return; - if (opt_key == "pad_enable") { - tab->set_value(opt_key, value); - tab->update(); + DynamicPrintConfig new_conf = *config_sla; + if (opt_key == "pad") { + const wxString& selection = boost::any_cast(value); + + const bool pad_enable = selection == _("None") ? false : true; + new_conf.set_key_value("pad_enable", new ConfigOptionBool(pad_enable)); + + if (selection == _("Below object")) + new_conf.set_key_value("pad_zero_elevation", new ConfigOptionBool(false)); + else if (selection == _("Around object")) + new_conf.set_key_value("pad_zero_elevation", new ConfigOptionBool(true)); } else { assert(opt_key == "support"); - DynamicPrintConfig new_conf = *config_sla; const wxString& selection = boost::any_cast(value); const bool supports_enable = selection == _("None") ? false : true; @@ -573,10 +580,9 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(false)); else if (selection == _("Support on build plate only")) new_conf.set_key_value("support_buildplate_only", new ConfigOptionBool(true)); - - tab->load_config(new_conf); } + tab->load_config(new_conf); tab->update_dirty(); }; @@ -588,15 +594,25 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : support_def_sla.enum_labels.erase(support_def_sla.enum_labels.begin() + 2); option = Option(support_def_sla, "support"); option.opt.full_width = true; - line.append_option(option); + line.append_option(option); line.append_widget(empty_widget); m_og_sla->append_line(line); line = Line{ "", "" }; - option = m_og_sla->get_option("pad_enable"); - option.opt.sidetext = " "; + ConfigOptionDef pad_def; + pad_def.label = L("Pad"); + pad_def.type = coStrings; + pad_def.gui_type = "select_open"; + pad_def.tooltip = L("Select what kind of pad do you need"); + pad_def.enum_labels.push_back(L("None")); + pad_def.enum_labels.push_back(L("Below object")); + pad_def.enum_labels.push_back(L("Around object")); + pad_def.set_default_value(new ConfigOptionStrings{ "Below object" }); + option = Option(pad_def, "pad"); + option.opt.full_width = true; line.append_option(option); + line.append_widget(empty_widget); m_og_sla->append_line(line); @@ -617,7 +633,7 @@ void FreqChangedParams::Show(const bool is_fff) m_og->Show(is_fff); m_og_sla->Show(!is_fff); - // correct showing of the FreqChangedParams sizer when m_wiping_dialog_button is hidden + // correct showing of the FreqChangedParams sizer when m_wiping_dialog_button is hidden if (is_fff && !is_wdb_shown) m_wiping_dialog_button->Hide(); } @@ -747,7 +763,7 @@ Sidebar::Sidebar(Plater *parent) auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL); combo_and_btn_sizer->Add(*combo, 1, wxEXPAND); if ((*combo)->edit_btn) - combo_and_btn_sizer->Add((*combo)->edit_btn, 0, wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT, + combo_and_btn_sizer->Add((*combo)->edit_btn, 0, wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT, int(0.3*wxGetApp().em_unit())); auto *sizer_presets = this->p->sizer_presets; @@ -776,11 +792,11 @@ Sidebar::Sidebar(Plater *parent) // Frequently changed parameters p->frequently_changed_parameters = new FreqChangedParams(p->scrolled); p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, wxOSX ? 1 : margin_5); - + // Object List p->object_list = new ObjectList(p->scrolled); p->sizer_params->Add(p->object_list->get_sizer(), 1, wxEXPAND); - + // Object Manipulations p->object_manipulation = new ObjectManipulation(p->scrolled); p->object_manipulation->Hide(); @@ -790,7 +806,7 @@ Sidebar::Sidebar(Plater *parent) p->object_settings = new ObjectSettings(p->scrolled); p->object_settings->Hide(); p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); - + // Object Layers p->object_layers = new ObjectLayers(p->scrolled); p->object_layers->Hide(); @@ -803,7 +819,7 @@ Sidebar::Sidebar(Plater *parent) // Sizer in the scrolled area scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/); is_msw ? - scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) : + scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) : scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5); scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND | wxLEFT, margin_5); scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5); @@ -812,7 +828,7 @@ Sidebar::Sidebar(Plater *parent) // Buttons underneath the scrolled area auto init_btn = [this](wxButton **btn, wxString label) { - *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition, + *btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); (*btn)->SetFont(wxGetApp().bold_font()); }; @@ -842,7 +858,7 @@ Sidebar::Sidebar(Plater *parent) p->plater->export_gcode(); else p->plater->reslice(); - p->plater->select_view_3D("Preview"); + p->plater->select_view_3D("Preview"); }); p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); }); } @@ -902,11 +918,11 @@ void Sidebar::update_all_preset_comboboxes() void Sidebar::update_presets(Preset::Type preset_type) { - PresetBundle &preset_bundle = *wxGetApp().preset_bundle; + PresetBundle &preset_bundle = *wxGetApp().preset_bundle; const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology(); switch (preset_type) { - case Preset::TYPE_FILAMENT: + case Preset::TYPE_FILAMENT: { const int extruder_cnt = print_tech != ptFFF ? 1 : dynamic_cast(preset_bundle.printers.get_edited_preset().config.option("nozzle_diameter"))->values.size(); @@ -926,23 +942,23 @@ void Sidebar::update_presets(Preset::Type preset_type) } case Preset::TYPE_PRINT: - preset_bundle.prints.update_platter_ui(p->combo_print); + preset_bundle.prints.update_platter_ui(p->combo_print); break; case Preset::TYPE_SLA_PRINT: - preset_bundle.sla_prints.update_platter_ui(p->combo_sla_print); + preset_bundle.sla_prints.update_platter_ui(p->combo_sla_print); break; case Preset::TYPE_SLA_MATERIAL: - preset_bundle.sla_materials.update_platter_ui(p->combo_sla_material); + preset_bundle.sla_materials.update_platter_ui(p->combo_sla_material); break; - case Preset::TYPE_PRINTER: - { + case Preset::TYPE_PRINTER: + { update_all_preset_comboboxes(); - p->show_preset_comboboxes(); - break; - } + p->show_preset_comboboxes(); + break; + } default: break; } @@ -960,11 +976,11 @@ void Sidebar::update_reslice_btn_tooltip() const { wxString tooltip = wxString("Slice") + " [" + GUI::shortkey_ctrl_prefix() + "R]"; if (m_mode != comSimple) - tooltip += wxString("\n") + _(L("Hold Shift to Slice & Export G-code")); + tooltip += wxString("\n") + _(L("Hold Shift to Slice & Export G-code")); p->btn_reslice->SetToolTip(tooltip); } -void Sidebar::msw_rescale() +void Sidebar::msw_rescale() { SetMinSize(wxSize(40 * wxGetApp().em_unit(), -1)); @@ -1072,7 +1088,7 @@ void Sidebar::show_info_sizer() if (errors > 0) { wxString tooltip = wxString::Format(_(L("Auto-repaired (%d errors)")), errors); p->object_info->info_manifold->SetLabel(tooltip); - + tooltip += ":\n" + wxString::Format(_(L("%d degenerate facets, %d edges fixed, %d facets removed, " "%d facets added, %d facets reversed, %d backwards edges")), stats.degenerate_facets, stats.edges_fixed, stats.facets_removed, @@ -1081,7 +1097,7 @@ void Sidebar::show_info_sizer() p->object_info->showing_manifold_warning_icon = true; p->object_info->info_manifold->SetToolTip(tooltip); p->object_info->manifold_warning_icon->SetToolTip(tooltip); - } + } else { p->object_info->info_manifold->SetLabel(_(L("Yes"))); p->object_info->showing_manifold_warning_icon = false; @@ -1097,7 +1113,7 @@ void Sidebar::show_info_sizer() } } -void Sidebar::show_sliced_info_sizer(const bool show) +void Sidebar::show_sliced_info_sizer(const bool show) { wxWindowUpdateLocker freeze_guard(this); @@ -1128,7 +1144,7 @@ void Sidebar::show_sliced_info_sizer(const bool show) p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, "N/A"); } else - { + { const PrintStatistics& ps = p->plater->fff_print().print_statistics(); const bool is_wipe_tower = ps.total_wipe_tower_filament > 0; @@ -1138,7 +1154,7 @@ void Sidebar::show_sliced_info_sizer(const bool show) wxString info_text = is_wipe_tower ? wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / 1000, - (ps.total_used_filament - ps.total_wipe_tower_filament) / 1000, + (ps.total_used_filament - ps.total_wipe_tower_filament) / 1000, ps.total_wipe_tower_filament / 1000) : wxString::Format("%.2f", ps.total_used_filament / 1000); p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label); @@ -1150,10 +1166,10 @@ void Sidebar::show_sliced_info_sizer(const bool show) new_label = _(L("Cost")); if (is_wipe_tower) new_label += wxString::Format(" :\n - %s\n - %s", _(L("objects")), _(L("wipe tower"))); - + info_text = is_wipe_tower ? wxString::Format("%.2f \n%.2f \n%.2f", ps.total_cost, - (ps.total_cost - ps.total_wipe_tower_cost), + (ps.total_cost - ps.total_wipe_tower_cost), ps.total_wipe_tower_cost) : wxString::Format("%.2f", ps.total_cost); p->sliced_info->SetTextAndShow(siCost, info_text, new_label); @@ -1189,7 +1205,7 @@ void Sidebar::show_sliced_info_sizer(const bool show) // Hide non-FFF sliced info parameters p->sliced_info->SetTextAndShow(siMateril_unit, "N/A"); - } + } } Layout(); @@ -1227,7 +1243,7 @@ void Sidebar::update_mode() p->object_list->unselect_objects(); p->object_list->update_selections(); p->object_list->update_object_menu(); - + Layout(); } @@ -1241,7 +1257,7 @@ std::vector& Sidebar::combos_filament() class PlaterDropTarget : public wxFileDropTarget { public: - PlaterDropTarget(Plater *plater) : plater(plater) { this->SetDefaultAction(wxDragCopy); } + PlaterDropTarget(Plater *plater) : plater(plater) { this->SetDefaultAction(wxDragCopy); } virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames); @@ -1265,22 +1281,22 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi } } - wxString snapshot_label; - assert(! paths.empty()); - if (paths.size() == 1) { - snapshot_label = _(L("Load File")); - snapshot_label += ": "; - snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); - } else { - snapshot_label = _(L("Load Files")); - snapshot_label += ": "; - snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); - for (size_t i = 1; i < paths.size(); ++ i) { - snapshot_label += ", "; - snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); - } - } - Plater::TakeSnapshot snapshot(plater, snapshot_label); + wxString snapshot_label; + assert(! paths.empty()); + if (paths.size() == 1) { + snapshot_label = _(L("Load File")); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + } else { + snapshot_label = _(L("Load Files")); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + for (size_t i = 1; i < paths.size(); ++ i) { + snapshot_label += ", "; + snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); + } + } + Plater::TakeSnapshot snapshot(plater, snapshot_label); // FIXME: when drag and drop is done on a .3mf or a .amf file we should clear the plater for consistence with the open project command // (the following call to plater->load_files() will load the config data, if present) @@ -1331,7 +1347,7 @@ struct Plater::priv // Data Slic3r::DynamicPrintConfig *config; // FIXME: leak? Slic3r::Print fff_print; - Slic3r::SLAPrint sla_print; + Slic3r::SLAPrint sla_print; Slic3r::Model model; PrinterTechnology printer_technology = ptFFF; Slic3r::GCodePreviewData gcode_preview_data; @@ -1349,19 +1365,19 @@ 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 Vec2crd& tr, double rotation) { m_pos = unscaled(tr); m_rotation = rotation; apply_wipe_tower(); } - + ArrangePolygon get_arrange_polygon() const { Polygon p({ @@ -1371,7 +1387,7 @@ struct Plater::priv {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); @@ -1379,7 +1395,7 @@ struct Plater::priv return ret; } } wipetower; - + WipeTower& updated_wipe_tower() { auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); wipetower.m_pos = wti.pos(); @@ -1387,10 +1403,10 @@ struct Plater::priv 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 + // 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. // @@ -1534,35 +1550,35 @@ struct Plater::priv Arrange, Rotoptimize }; - + class ArrangeJob : public Job { 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; - + // clear m_selected and m_unselected, reserve space for next usage void clear_input() { const Model &model = plater().model; - + size_t count = 0; // To know how much space to reserve for (auto obj : model.objects) count += obj->instances.size(); m_selected.clear(), m_unselected.clear(); m_selected.reserve(count + 1 /* for optional wti */); m_unselected.reserve(count + 1 /* for optional wti */); } - + // Stride between logical beds coord_t bed_stride() const { double bedwidth = plater().bed_shape_bb().size().x(); return scaled((1. + LOGICAL_BED_GAP) * bedwidth); } - + // Set up arrange polygon for a ModelInstance and Wipe tower template ArrangePolygon get_arrange_poly(T *obj) const { ArrangePolygon ap = obj->get_arrange_polygon(); @@ -1576,109 +1592,109 @@ struct Plater::priv }; 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) m_selected.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 obj_sel(model.objects.size(), nullptr); - + for (auto &s : plater().get_selection().get_content()) - if (s.first < int(obj_sel.size())) obj_sel[s.first] = &s.second; - + if (s.first < int(obj_sel.size())) obj_sel[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 inst_sel(mo->instances.size(), false); - + if (instlist) for (auto inst_id : *instlist) inst_sel[inst_id] = true; - + for (size_t i = 0; i < inst_sel.size(); ++i) { ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); - + inst_sel[i] ? m_selected.emplace_back(std::move(ap)) : m_unselected.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 Job::Job; - + int status_range() const override { return int(m_selected.size()); } - + void process() override; - + void finalize() override { // Ignore the arrange result if aborted. if (was_canceled()) return; - + // Apply the arrange result to all selected objects for (ArrangePolygon &ap : m_selected) ap.apply(); plater().update(false /*dont force_full_scene_refresh*/); } }; - + class RotoptimizeJob : public Job { public: using Job::Job; 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}; @@ -1687,7 +1703,7 @@ struct Plater::priv // 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 + // if it cannot run concurrently with other jobs in this group std::vector> m_jobs{arrange_job, rotoptimize_job}; @@ -1700,22 +1716,22 @@ struct Plater::priv 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 aborted(m_jobs.size(), false); - + for (size_t jid = 0; jid < m_jobs.size(); ++jid) aborted[jid] = m_jobs[jid].get().join(wait_ms); if (!all_of(aborted)) BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; } - + void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); } - + const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; } bool is_any_running() const @@ -1724,7 +1740,7 @@ struct Plater::priv m_jobs.end(), [](const Job &j) { return j.is_running(); }); } - + } m_ui_jobs{this}; bool delayed_scene_refresh; @@ -1755,7 +1771,7 @@ struct Plater::priv 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 load_files(const std::vector& input_files, bool load_model, bool load_config); std::vector load_model_objects(const ModelObjectPtrs &model_objects); @@ -1786,8 +1802,8 @@ struct Plater::priv void enter_gizmos_stack(); void leave_gizmos_stack(); - void take_snapshot(const std::string& snapshot_name); - void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } + void take_snapshot(const std::string& snapshot_name); + void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); } int get_active_snapshot_index(); void undo(); @@ -1807,7 +1823,7 @@ struct Plater::priv UPDATE_BACKGROUND_PROCESS_RESTART = 1, // update_background_process() reports, that the Print / SLAPrint was updated in a way, // that a scene needs to be refreshed (you should call _3DScene::reload_scene(canvas3Dwidget, false)) - UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE = 2, + UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE = 2, // update_background_process() reports, that the Print / SLAPrint is invalid, and the error message // was sent to the status line. UPDATE_BACKGROUND_PROCESS_INVALID = 4, @@ -1820,8 +1836,8 @@ struct Plater::priv unsigned int update_background_process(bool force_validation = false); // Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState. bool restart_background_process(unsigned int state); - // returns bit mask of UpdateBackgroundProcessReturnState - unsigned int update_restart_background_process(bool force_scene_update, bool force_preview_update); + // returns bit mask of UpdateBackgroundProcessReturnState + unsigned int update_restart_background_process(bool force_scene_update, bool force_preview_update); void export_gcode(fs::path output_path, PrintHostJob upload_job); void reload_from_disk(); void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); @@ -1887,7 +1903,7 @@ private: void update_fff_scene(); void update_sla_scene(); - void undo_redo_to(std::vector::const_iterator it_snapshot); + void undo_redo_to(std::vector::const_iterator it_snapshot); void update_after_undo_redo(bool temp_snapshot_was_taken = false); // path to project file stored with no extension @@ -1895,9 +1911,9 @@ private: Slic3r::UndoRedo::Stack m_undo_redo_stack_main; Slic3r::UndoRedo::Stack m_undo_redo_stack_gizmos; Slic3r::UndoRedo::Stack *m_undo_redo_stack_active = &m_undo_redo_stack_main; - int m_prevent_snapshots = 0; /* Used for avoid of excess "snapshoting". + int m_prevent_snapshots = 0; /* Used for avoid of excess "snapshoting". * Like for "delete selected" or "set numbers of copies" - * we should call tack_snapshot just ones + * we should call tack_snapshot just ones * instead of calls for each action separately * */ std::string m_last_fff_printer_profile_name; @@ -1929,14 +1945,14 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) , view_toolbar(GLToolbar::Radio, "View") , m_project_filename(wxEmptyString) { - this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); background_process.set_fff_print(&fff_print); - background_process.set_sla_print(&sla_print); + background_process.set_sla_print(&sla_print); background_process.set_gcode_preview_data(&gcode_preview_data); background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED); background_process.set_finished_event(EVT_PROCESS_COMPLETED); - // Default printer technology for default config. + // Default printer technology for default config. background_process.select_technology(this->printer_technology); // Register progress callback from the Print class to the Platter. @@ -1989,7 +2005,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { 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_INCREASE_INSTANCES, [this](Event &evt) + view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event &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(); }); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); @@ -2053,7 +2069,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) camera.set_type(get_config("use_perspective_camera")); // Initialize the Undo / Redo stack with a first snapshot. - this->take_snapshot(_(L("New Project"))); + this->take_snapshot(_(L("New Project"))); } Plater::priv::~priv() @@ -2080,7 +2096,7 @@ void Plater::priv::update(bool force_full_scene_refresh, bool force_background_p // pulls the correct data. update_status = this->update_background_process(false); this->view3D->reload_scene(false, force_full_scene_refresh); - this->preview->reload_print(); + this->preview->reload_print(); if (this->printer_technology == ptSLA) this->restart_background_process(update_status); else @@ -2198,7 +2214,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false); if (load_config && !config_loaded.empty()) { // Based on the printer technology field found in the loaded config, select the base for the config, - PrinterTechnology printer_technology = Preset::printer_technology(config_loaded); + PrinterTechnology printer_technology = Preset::printer_technology(config_loaded); // We can't to load SLA project if there is at least one multi-part object on the bed if (printer_technology == ptSLA) @@ -2215,8 +2231,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } - config.apply(printer_technology == ptFFF ? - static_cast(FullPrintConfig::defaults()) : + config.apply(printer_technology == ptFFF ? + static_cast(FullPrintConfig::defaults()) : static_cast(SLAFullPrintConfig::defaults())); // and place the loaded config over the base. config += std::move(config_loaded); @@ -2283,8 +2299,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ { for (auto obj : model.objects) if ( obj->volumes.size()>1 ) { - Slic3r::GUI::show_error(nullptr, - wxString::Format(_(L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part")), + Slic3r::GUI::show_error(nullptr, + wxString::Format(_(L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part")), from_path(filename))); return obj_idxs; } @@ -2366,9 +2382,9 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode #else /* AUTOPLACEMENT_ON_LOAD */ // if object has no defined position(s) we need to rearrange everything after loading need_arrange = true; - // add a default instance and center object around origin - object->center_around_origin(); // also aligns object to Z = 0 - ModelInstance* instance = object->add_instance(); + // add a default instance and center object around origin + object->center_around_origin(); // also aligns object to Z = 0 + ModelInstance* instance = object->add_instance(); instance->set_offset(Slic3r::to_3d(bed_shape.center().cast(), -object->origin_translation(2))); #endif /* AUTOPLACEMENT_ON_LOAD */ } @@ -2379,7 +2395,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode if (max_ratio > 10000) { // the size of the object is too big -> this could lead to overflow when moving to clipper coordinates, // so scale down the mesh - double inv = 1. / max_ratio; + double inv = 1. / max_ratio; object->scale_mesh_after_creation(Vec3d(inv, inv, inv)); object->origin_translation = Vec3d::Zero(); object->center_around_origin(); @@ -2403,9 +2419,9 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode auto& bedpoints = bed_shape_opt->values; Polyline bed; bed.points.reserve(bedpoints.size()); for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - + std::pair wti = view3D->get_canvas3d()->get_wipe_tower_info(); - + arr::find_new_position(model, new_instances, min_obj_distance, bed, wti); // it remains to move the wipe tower: @@ -2527,7 +2543,7 @@ int Plater::priv::get_selected_volume_idx() const { auto& selection = get_selection(); int idx = selection.get_object_idx(); - if ((0 > idx) || (idx > 1000)) + if ((0 > idx) || (idx > 1000)) return-1; const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); if (model.objects[idx]->volumes.size() > 1) @@ -2589,10 +2605,10 @@ void Plater::priv::remove(size_t obj_idx) void Plater::priv::delete_object_from_model(size_t obj_idx) { - wxString snapshot_label = _(L("Delete Object")); - if (! model.objects[obj_idx]->name.empty()) - snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str()); - Plater::TakeSnapshot snapshot(q, snapshot_label); + wxString snapshot_label = _(L("Delete Object")); + if (! model.objects[obj_idx]->name.empty()) + snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str()); + Plater::TakeSnapshot snapshot(q, snapshot_label); model.delete_object(obj_idx); update(); object_list_changed(); @@ -2600,7 +2616,7 @@ void Plater::priv::delete_object_from_model(size_t obj_idx) void Plater::priv::reset() { - Plater::TakeSnapshot snapshot(q, _(L("Reset Project"))); + Plater::TakeSnapshot snapshot(q, _(L("Reset Project"))); set_project_filename(wxEmptyString); @@ -2645,16 +2661,16 @@ void Plater::priv::sla_optimize_rotation() { } arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { - + const auto *bed_shape_opt = config->opt("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); } @@ -2662,23 +2678,23 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances, coord_t min_d) { arrangement::ArrangePolygons movable, fixed; - + for (const ModelObject *mo : model.objects) for (const ModelInstance *inst : mo->instances) { auto it = std::find(instances.begin(), instances.end(), inst); auto arrpoly = inst->get_arrange_polygon(); - + if (it == instances.end()) fixed.emplace_back(std::move(arrpoly)); 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()); - + for (size_t i = 0; i < instances.size(); ++i) if (movable[i].bed_idx == 0) instances[i]->apply_arrange_result(movable[i].translation, @@ -2687,18 +2703,18 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances, 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()); arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); - + try { arrangement::arrange(m_selected, m_unselected, min_d, bedshape, [this, count](unsigned st) { @@ -2712,7 +2728,7 @@ void Plater::priv::ArrangeJob::process() { _(L("Could not arrange model objects! " "Some geometries may be invalid."))); } - + // finalize just here. update_status(int(count), was_canceled() ? _(L("Arranging canceled.")) @@ -2738,25 +2754,25 @@ void Plater::priv::RotoptimizeJob::process() 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 @@ -2793,7 +2809,7 @@ void Plater::priv::split_object() Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part."))); else { - Plater::TakeSnapshot snapshot(q, _(L("Split to Objects"))); + Plater::TakeSnapshot snapshot(q, _(L("Split to Objects"))); unsigned int counter = 1; for (ModelObject* m : new_objects) @@ -2848,7 +2864,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation) // bitmap of enum UpdateBackgroundProcessReturnState unsigned int return_state = 0; - // If the update_background_process() was not called by the timer, kill the timer, + // If the update_background_process() was not called by the timer, kill the timer, // so the update_restart_background_process() will not be called again in vain. this->background_process_timer.Stop(); // Update the "out of print bed" state of ModelInstances. @@ -2869,21 +2885,21 @@ unsigned int Plater::priv::update_background_process(bool force_validation) this->sidebar->show_sliced_info_sizer(false); // Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared. // Otherwise they will be just refreshed. - if (this->preview != nullptr) - // If the preview is not visible, the following line just invalidates the preview, - // but the G-code paths or SLA preview are calculated first once the preview is made visible. - this->preview->reload_print(); - // In FDM mode, we need to reload the 3D scene because of the wipe tower preview box. - // In SLA mode, we need to reload the 3D scene every time to show the support structures. - if (this->printer_technology == ptSLA || (this->printer_technology == ptFFF && this->config->opt_bool("wipe_tower"))) - return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE; + if (this->preview != nullptr) + // If the preview is not visible, the following line just invalidates the preview, + // but the G-code paths or SLA preview are calculated first once the preview is made visible. + this->preview->reload_print(); + // In FDM mode, we need to reload the 3D scene because of the wipe tower preview box. + // In SLA mode, we need to reload the 3D scene every time to show the support structures. + if (this->printer_technology == ptSLA || (this->printer_technology == ptFFF && this->config->opt_bool("wipe_tower"))) + return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE; } if ((invalidated != Print::APPLY_STATUS_UNCHANGED || force_validation) && ! this->background_process.empty()) { // The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors. std::string err = this->background_process.validate(); if (err.empty()) { - if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled()) + if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled()) return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; } else { // The print is not valid. @@ -2905,12 +2921,12 @@ unsigned int Plater::priv::update_background_process(bool force_validation) if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() && (return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) { - // The background processing was killed and it will not be restarted. - wxCommandEvent evt(EVT_PROCESS_COMPLETED); - evt.SetInt(-1); - // Post the "canceled" callback message, so that it will be processed after any possible pending status bar update messages. - wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); - } + // The background processing was killed and it will not be restarted. + wxCommandEvent evt(EVT_PROCESS_COMPLETED); + evt.SetInt(-1); + // Post the "canceled" callback message, so that it will be processed after any possible pending status bar update messages. + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); + } if ((return_state & UPDATE_BACKGROUND_PROCESS_INVALID) != 0) { @@ -2928,17 +2944,17 @@ unsigned int Plater::priv::update_background_process(bool force_validation) sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export)); sidebar->set_btn_label(ActionButtonType::abSendGCode, _(label_btn_send)); - - const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ? + + const wxString slice_string = background_process.running() && wxGetApp().get_mode() == comSimple ? _(L("Slicing")) + dots : _(L("Slice now")); sidebar->set_btn_label(ActionButtonType::abReslice, slice_string); if (background_process.finished()) show_action_buttons(false); - else if (!background_process.empty() && + else if (!background_process.empty() && !background_process.running()) /* Do not update buttons if background process is running - * This condition is important for SLA mode especially, - * when this function is called several times during calculations + * This condition is important for SLA mode especially, + * when this function is called several times during calculations * */ show_action_buttons(true); } @@ -2954,9 +2970,9 @@ bool Plater::priv::restart_background_process(unsigned int state) return false; } - if ( ! this->background_process.empty() && - (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 && - ( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) || + if ( ! this->background_process.empty() && + (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 && + ( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) || (state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0 || (state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) { // The print is valid and it can be started. @@ -3012,7 +3028,7 @@ unsigned int Plater::priv::update_restart_background_process(bool force_update_s if (force_update_preview) this->preview->reload_print(); this->restart_background_process(state); - return state; + return state; } void Plater::priv::update_fff_scene() @@ -3020,7 +3036,7 @@ void Plater::priv::update_fff_scene() if (this->preview != nullptr) this->preview->reload_print(); // In case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth: - view3D->reload_scene(true); + view3D->reload_scene(true); } void Plater::priv::update_sla_scene() @@ -3033,7 +3049,7 @@ void Plater::priv::update_sla_scene() void Plater::priv::reload_from_disk() { - Plater::TakeSnapshot snapshot(q, _(L("Reload from Disk"))); + Plater::TakeSnapshot snapshot(q, _(L("Reload from Disk"))); const auto &selection = get_selection(); const auto obj_orig_idx = selection.get_object_idx(); @@ -3069,7 +3085,7 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* = if (obj_idx < 0) return; - Plater::TakeSnapshot snapshot(q, _(L("Fix Throught NetFabb"))); + Plater::TakeSnapshot snapshot(q, _(L("Fix Throught NetFabb"))); fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx); this->update(); @@ -3136,7 +3152,7 @@ void Plater::priv::set_current_panel(wxPanel* panel) } else if (current_panel == preview) { - this->q->reslice(); + this->q->reslice(); // keeps current gcode preview, if any preview->reload_print(true); preview->set_canvas_as_dirty(); @@ -3153,12 +3169,12 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) auto idx = combo->get_extruder_idx(); - //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, + //! Because of The MSW and GTK version of wxBitmapComboBox derived from wxComboBox, //! but the OSX version derived from wxOwnerDrawnCombo. - //! So, to get selected string we do - //! combo->GetString(combo->GetSelection()) - //! instead of - //! combo->GetStringSelection().ToUTF8().data()); + //! So, to get selected string we do + //! combo->GetString(combo->GetSelection()) + //! instead of + //! combo->GetStringSelection().ToUTF8().data()); const std::string& selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data(); @@ -3170,7 +3186,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { // Only update the platter UI for the 2nd and other filaments. wxGetApp().preset_bundle->update_platter_filament_ui(idx, combo); - } + } else { wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); wxGetApp().get_tab(preset_type)->select_preset(selected_string); @@ -3180,7 +3196,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) wxGetApp().plater()->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 + * Furthermore, Layers editing is implemented only for FFF printers * and for SLA presets they should be deleted */ if (preset_type == Preset::TYPE_PRINTER) @@ -3241,9 +3257,9 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) this->background_process.stop(); this->statusbar()->reset_cancel_callback(); this->statusbar()->stop_busy(); - + const bool canceled = evt.GetInt() < 0; - const bool error = evt.GetInt() == 0; + const bool error = evt.GetInt() == 0; const bool success = evt.GetInt() > 0; // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. this->background_process.reset_export(); @@ -3255,8 +3271,8 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) show_error(q, message); this->statusbar()->set_status_text(message); } - if (canceled) - this->statusbar()->set_status_text(_(L("Cancelled"))); + if (canceled) + this->statusbar()->set_status_text(_(L("Cancelled"))); this->sidebar->show_sliced_info_sizer(success); @@ -3264,7 +3280,7 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) // Namely, it refreshes the "Out of print bed" property of all the ModelObjects, and it enables // the "Slice now" and "Export G-code" buttons based on their "out of bed" status. this->object_list_changed(); - + // refresh preview switch (this->printer_technology) { case ptFFF: @@ -3399,7 +3415,7 @@ void Plater::priv::on_update_geometry(Vec3dsEvent<2>&) // TODO } -// Update the scene from the background processing, +// Update the scene from the background processing, // if the update message was received during mouse manipulation. void Plater::priv::on_3dcanvas_mouse_dragging_finished(SimpleEvent&) { @@ -3531,7 +3547,7 @@ bool Plater::priv::complit_init_object_menu() append_menu_item(split_menu, wxID_ANY, _(L("To parts")), _(L("Split the selected object into individual sub-parts")), [this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", &object_menu, [this]() { return can_split(); }, q); - append_submenu(&object_menu, split_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object")), "", + append_submenu(&object_menu, split_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object")), "", [this]() { return can_split() && wxGetApp().get_mode() > comSimple; }, q); object_menu.AppendSeparator(); @@ -3709,49 +3725,49 @@ void Plater::priv::update_object_menu() sidebar->obj_list()->append_menu_items_add_volume(&object_menu); } -void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const +void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const { wxWindowUpdateLocker noUpdater(sidebar); const auto prin_host_opt = config->option("print_host"); const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty(); - // when a background processing is ON, export_btn and/or send_btn are showing + // when a background processing is ON, export_btn and/or send_btn are showing if (wxGetApp().app_config->get("background_processing") == "1") { if (sidebar->show_reslice(false) | - sidebar->show_export(true) | - sidebar->show_send(send_gcode_shown)) - sidebar->Layout(); - } + sidebar->show_export(true) | + sidebar->show_send(send_gcode_shown)) + sidebar->Layout(); + } else { - if (sidebar->show_reslice(is_ready_to_slice) | - sidebar->show_export(!is_ready_to_slice) | - sidebar->show_send(send_gcode_shown && !is_ready_to_slice)) - sidebar->Layout(); - } + if (sidebar->show_reslice(is_ready_to_slice) | + sidebar->show_export(!is_ready_to_slice) | + sidebar->show_send(send_gcode_shown && !is_ready_to_slice)) + sidebar->Layout(); + } } void Plater::priv::enter_gizmos_stack() { - assert(m_undo_redo_stack_active == &m_undo_redo_stack_main); - if (m_undo_redo_stack_active == &m_undo_redo_stack_main) { - m_undo_redo_stack_active = &m_undo_redo_stack_gizmos; - assert(m_undo_redo_stack_active->empty()); - // Take the initial snapshot of the gizmos. - // Not localized on purpose, the text will never be shown to the user. - this->take_snapshot(std::string("Gizmos-Initial")); - } + assert(m_undo_redo_stack_active == &m_undo_redo_stack_main); + if (m_undo_redo_stack_active == &m_undo_redo_stack_main) { + m_undo_redo_stack_active = &m_undo_redo_stack_gizmos; + assert(m_undo_redo_stack_active->empty()); + // Take the initial snapshot of the gizmos. + // Not localized on purpose, the text will never be shown to the user. + this->take_snapshot(std::string("Gizmos-Initial")); + } } void Plater::priv::leave_gizmos_stack() { - assert(m_undo_redo_stack_active == &m_undo_redo_stack_gizmos); - if (m_undo_redo_stack_active == &m_undo_redo_stack_gizmos) { - assert(! m_undo_redo_stack_active->empty()); - m_undo_redo_stack_active->clear(); - m_undo_redo_stack_active = &m_undo_redo_stack_main; - } + assert(m_undo_redo_stack_active == &m_undo_redo_stack_gizmos); + if (m_undo_redo_stack_active == &m_undo_redo_stack_gizmos) { + assert(! m_undo_redo_stack_active->empty()); + m_undo_redo_stack_active->clear(); + m_undo_redo_stack_active = &m_undo_redo_stack_main; + } } int Plater::priv::get_active_snapshot_index() @@ -3764,13 +3780,13 @@ int Plater::priv::get_active_snapshot_index() void Plater::priv::take_snapshot(const std::string& snapshot_name) { - if (this->m_prevent_snapshots > 0) + if (this->m_prevent_snapshots > 0) return; assert(this->m_prevent_snapshots >= 0); UndoRedo::SnapshotData snapshot_data; snapshot_data.printer_technology = this->printer_technology; if (this->view3D->is_layers_editing_enabled()) - snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; + snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; if (this->sidebar->obj_list()->is_selected(itSettings)) { snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR; snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx(); @@ -3792,46 +3808,46 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) this->undo_redo_stack().release_least_recently_used(); // Save the last active preset name of a particular printer technology. ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); - BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); } void Plater::priv::undo() { - const std::vector &snapshots = this->undo_redo_stack().snapshots(); - auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); - if (-- it_current != snapshots.begin()) - this->undo_redo_to(it_current); + const std::vector &snapshots = this->undo_redo_stack().snapshots(); + auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); + if (-- it_current != snapshots.begin()) + this->undo_redo_to(it_current); } void Plater::priv::redo() -{ - const std::vector &snapshots = this->undo_redo_stack().snapshots(); - auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); - if (++ it_current != snapshots.end()) - this->undo_redo_to(it_current); +{ + const std::vector &snapshots = this->undo_redo_stack().snapshots(); + auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); + if (++ it_current != snapshots.end()) + this->undo_redo_to(it_current); } void Plater::priv::undo_redo_to(size_t time_to_load) { - const std::vector &snapshots = this->undo_redo_stack().snapshots(); - auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(time_to_load)); - assert(it_current != snapshots.end()); - this->undo_redo_to(it_current); + const std::vector &snapshots = this->undo_redo_stack().snapshots(); + auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(time_to_load)); + assert(it_current != snapshots.end()); + this->undo_redo_to(it_current); } void Plater::priv::undo_redo_to(std::vector::const_iterator it_snapshot) { - bool temp_snapshot_was_taken = this->undo_redo_stack().temp_snapshot_active(); - PrinterTechnology new_printer_technology = it_snapshot->snapshot_data.printer_technology; - bool printer_technology_changed = this->printer_technology != new_printer_technology; - if (printer_technology_changed) { - // Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type. - std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA"; - if (! wxGetApp().check_unsaved_changes(from_u8((boost::format(_utf8( - L("%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."))) % s_pt).str()))) - // Don't switch the profiles. - return; - } + bool temp_snapshot_was_taken = this->undo_redo_stack().temp_snapshot_active(); + PrinterTechnology new_printer_technology = it_snapshot->snapshot_data.printer_technology; + bool printer_technology_changed = this->printer_technology != new_printer_technology; + if (printer_technology_changed) { + // Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type. + std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA"; + if (! wxGetApp().check_unsaved_changes(from_u8((boost::format(_utf8( + L("%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."))) % s_pt).str()))) + // Don't switch the profiles. + return; + } // Save the last active preset name of a particular printer technology. ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config. @@ -3844,12 +3860,12 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator const int layer_range_idx = it_snapshot->snapshot_data.layer_range_idx; // Flags made of Snapshot::Flags enum values. unsigned int new_flags = it_snapshot->snapshot_data.flags; - UndoRedo::SnapshotData top_snapshot_data; + UndoRedo::SnapshotData top_snapshot_data; top_snapshot_data.printer_technology = this->printer_technology; if (this->view3D->is_layers_editing_enabled()) - top_snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; + top_snapshot_data.flags |= UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE; if (this->sidebar->obj_list()->is_selected(itSettings)) { - top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR; + top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR; top_snapshot_data.layer_range_idx = this->sidebar->obj_list()->get_selected_layers_range_idx(); } else if (this->sidebar->obj_list()->is_selected(itLayer)) { @@ -3858,26 +3874,26 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator } else if (this->sidebar->obj_list()->is_selected(itLayerRoot)) top_snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR; - bool new_variable_layer_editing_active = (new_flags & UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE) != 0; + bool new_variable_layer_editing_active = (new_flags & UndoRedo::SnapshotData::VARIABLE_LAYER_EDITING_ACTIVE) != 0; bool new_selected_settings_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_SETTINGS_ON_SIDEBAR) != 0; bool new_selected_layer_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR) != 0; bool new_selected_layerroot_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR) != 0; - // Disable layer editing before the Undo / Redo jump. + // Disable layer editing before the Undo / Redo jump. if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); // Do the jump in time. if (it_snapshot->timestamp < this->undo_redo_stack().active_snapshot_time() ? - this->undo_redo_stack().undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), top_snapshot_data, it_snapshot->timestamp) : - this->undo_redo_stack().redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) { - if (printer_technology_changed) { - // Switch to the other printer technology. Switch to the last printer active for that particular technology. - AppConfig *app_config = wxGetApp().app_config; - app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name); - wxGetApp().preset_bundle->load_presets(*app_config); - // Load the currently selected preset into the GUI, update the preset selection box. - // This also switches the printer technology based on the printer technology of the active printer profile. - wxGetApp().load_current_presets(); + this->undo_redo_stack().undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), top_snapshot_data, it_snapshot->timestamp) : + this->undo_redo_stack().redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) { + if (printer_technology_changed) { + // Switch to the other printer technology. Switch to the last printer active for that particular technology. + AppConfig *app_config = wxGetApp().app_config; + app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name); + wxGetApp().preset_bundle->load_presets(*app_config); + // Load the currently selected preset into the GUI, update the preset selection box. + // This also switches the printer technology based on the printer technology of the active printer profile. + wxGetApp().load_current_presets(); } //FIXME updating the Print config from the Wipe tower config values at the ModelWipeTower. // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. @@ -3898,43 +3914,43 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator // set selection mode for ObjectList on sidebar this->sidebar->obj_list()->set_selection_mode(new_selected_settings_on_sidebar ? ObjectList::SELECTION_MODE::smSettings : new_selected_layer_on_sidebar ? ObjectList::SELECTION_MODE::smLayer : - new_selected_layerroot_on_sidebar ? ObjectList::SELECTION_MODE::smLayerRoot : + new_selected_layerroot_on_sidebar ? ObjectList::SELECTION_MODE::smLayerRoot : ObjectList::SELECTION_MODE::smUndef); if (new_selected_settings_on_sidebar || new_selected_layer_on_sidebar) this->sidebar->obj_list()->set_selected_layers_range_idx(layer_range_idx); this->update_after_undo_redo(temp_snapshot_was_taken); - // Enable layer editing after the Undo / Redo jump. - if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) + // Enable layer editing after the Undo / Redo jump. + if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); } } void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */) { - this->view3D->get_canvas3d()->get_selection().clear(); - // Update volumes from the deserializd model, always stop / update the background processing (for both the SLA and FFF technologies). - this->update(false, true); - // Release old snapshots if the memory allocated is excessive. This may remove the top most snapshot if jumping to the very first snapshot. - //if (temp_snapshot_was_taken) - // Release the old snapshots always, as it may have happened, that some of the triangle meshes got deserialized from the snapshot, while some - // triangle meshes may have gotten released from the scene or the background processing, therefore now being calculated into the Undo / Redo stack size. - this->undo_redo_stack().release_least_recently_used(); - //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time) + this->view3D->get_canvas3d()->get_selection().clear(); + // Update volumes from the deserializd model, always stop / update the background processing (for both the SLA and FFF technologies). + this->update(false, true); + // Release old snapshots if the memory allocated is excessive. This may remove the top most snapshot if jumping to the very first snapshot. + //if (temp_snapshot_was_taken) + // Release the old snapshots always, as it may have happened, that some of the triangle meshes got deserialized from the snapshot, while some + // triangle meshes may have gotten released from the scene or the background processing, therefore now being calculated into the Undo / Redo stack size. + this->undo_redo_stack().release_least_recently_used(); + //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time) this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack().selection_deserialized().mode), this->undo_redo_stack().selection_deserialized().volumes_and_instances); this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo(); wxGetApp().obj_list()->update_after_undo_redo(); if (wxGetApp().get_mode() == comSimple && model_has_advanced_features(this->model)) { - // If the user jumped to a snapshot that require user interface with advanced features, switch to the advanced mode without asking. - // There is a little risk of surprising the user, as he already must have had the advanced or expert mode active for such a snapshot to be taken. + // If the user jumped to a snapshot that require user interface with advanced features, switch to the advanced mode without asking. + // There is a little risk of surprising the user, as he already must have had the advanced or expert mode active for such a snapshot to be taken. Slic3r::GUI::wxGetApp().save_mode(comAdvanced); view3D->set_as_dirty(); } - //FIXME what about the state of the manipulators? - //FIXME what about the focus? Cursor in the side panel? + //FIXME what about the state of the manipulators? + //FIXME what about the focus? Cursor in the side panel? BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); } @@ -4010,23 +4026,23 @@ void Plater::add_model() for (const auto &file : input_files) paths.push_back(into_path(file)); - wxString snapshot_label; - assert(! paths.empty()); - if (paths.size() == 1) { - snapshot_label = _(L("Import Object")); - snapshot_label += ": "; - snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); - } else { - snapshot_label = _(L("Import Objects")); - snapshot_label += ": "; - snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); - for (size_t i = 1; i < paths.size(); ++ i) { - snapshot_label += ", "; - snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); - } - } + wxString snapshot_label; + assert(! paths.empty()); + if (paths.size() == 1) { + snapshot_label = _(L("Import Object")); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + } else { + snapshot_label = _(L("Import Objects")); + snapshot_label += ": "; + snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str()); + for (size_t i = 1; i < paths.size(); ++ i) { + snapshot_label += ", "; + snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str()); + } + } - Plater::TakeSnapshot snapshot(this, snapshot_label); + Plater::TakeSnapshot snapshot(this, snapshot_label); load_files(paths, true, false); } @@ -4047,7 +4063,7 @@ void Plater::load_files(const std::vector& input_files, bool load_mode // To be called when providing a list of files to the GUI slic3r on command line. void Plater::load_files(const std::vector& input_files, bool load_model, bool load_config) -{ +{ std::vector paths; paths.reserve(input_files.size()); for (const std::string &path : input_files) @@ -4072,7 +4088,7 @@ void Plater::remove(size_t obj_idx) { p->remove(obj_idx); } void Plater::reset() { p->reset(); } void Plater::reset_with_confirm() { - if (wxMessageDialog((wxWindow*)this, _(L("All objects will be removed, continue ?")), wxString(SLIC3R_APP_NAME) + " - " + _(L("Delete all")), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) + if (wxMessageDialog((wxWindow*)this, _(L("All objects will be removed, continue ?")), wxString(SLIC3R_APP_NAME) + " - " + _(L("Delete all")), wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) reset(); } @@ -4080,7 +4096,7 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo void Plater::remove_selected() { - Plater::TakeSnapshot snapshot(this, _(L("Delete Selected Objects"))); + Plater::TakeSnapshot snapshot(this, _(L("Delete Selected Objects"))); this->p->view3D->delete_selected(); } @@ -4088,7 +4104,7 @@ void Plater::increase_instances(size_t num) { if (! can_increase_instances()) { return; } - Plater::TakeSnapshot snapshot(this, _(L("Increase Instances"))); + Plater::TakeSnapshot snapshot(this, _(L("Increase Instances"))); int obj_idx = p->get_selected_object_idx(); @@ -4096,7 +4112,7 @@ void Plater::increase_instances(size_t num) ModelInstance* model_instance = model_object->instances.back(); bool was_one_instance = model_object->instances.size()==1; - + double offset_base = canvas3D()->get_size_proportional_to_max_bed_size(0.05); double offset = offset_base; for (size_t i = 0; i < num; i++, offset += offset_base) { @@ -4124,7 +4140,7 @@ void Plater::decrease_instances(size_t num) { if (! can_decrease_instances()) { return; } - Plater::TakeSnapshot snapshot(this, _(L("Decrease Instances"))); + Plater::TakeSnapshot snapshot(this, _(L("Decrease Instances"))); int obj_idx = p->get_selected_object_idx(); @@ -4155,12 +4171,12 @@ void Plater::set_number_of_copies(/*size_t num*/) ModelObject* model_object = p->model.objects[obj_idx]; - const auto num = wxGetNumberFromUser( " ", _("Enter the number of copies:"), + const auto num = wxGetNumberFromUser( " ", _("Enter the number of copies:"), _("Copies of the selected object"), model_object->instances.size(), 0, 1000, this ); if (num < 0) return; - Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num)); + Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num)); int diff = (int)num - (int)model_object->instances.size(); if (diff > 0) @@ -4190,7 +4206,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe return; } - Plater::TakeSnapshot snapshot(this, _(L("Cut by Plane"))); + Plater::TakeSnapshot snapshot(this, _(L("Cut by Plane"))); wxBusyCursor wait; const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); @@ -4208,11 +4224,11 @@ void Plater::export_gcode() // This function is useful for generating file names to be processed by legacy firmwares. fs::path default_output_file; try { - // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. - // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. - unsigned int state = this->p->update_restart_background_process(false, false); - if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) - return; + // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. + // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. + unsigned int state = this->p->update_restart_background_process(false, false); + if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) + return; default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf"))); } catch (const std::exception &ex) { @@ -4398,7 +4414,7 @@ void Plater::reslice() if (object->sla_points_status == sla::PointsStatus::NoPoints) object->sla_points_status = sla::PointsStatus::Generating; } - + //FIXME Don't reslice if export of G-code or sending to OctoPrint is running. // bitmask of UpdateBackgroundProcessReturnState unsigned int state = this->p->update_background_process(true); @@ -4446,10 +4462,10 @@ void Plater::reslice_SLA_supports(const ModelObject &object) task.single_model_object = object.id(); // If the background processing is not enabled, calculate supports just for the single instance. // Otherwise calculate everything, but start with the provided object. - if (!this->p->background_processing_enabled()) { - task.single_model_instance_only = true; - task.to_object_step = slaposBasePool; - } + if (!this->p->background_processing_enabled()) { + task.single_model_instance_only = true; + task.to_object_step = slaposBasePool; + } this->p->background_process.set_task(task); // and let the background processing start. this->p->restart_background_process(state | priv::UPDATE_BACKGROUND_PROCESS_FORCE_RESTART); @@ -4465,11 +4481,11 @@ void Plater::send_gcode() // Obtain default output path fs::path default_output_file; try { - // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. - // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. - unsigned int state = this->p->update_restart_background_process(false, false); - if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) - return; + // Update the background processing, so that the placeholder parser will get the correct values for the ouput file template. + // Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed. + unsigned int state = this->p->update_restart_background_process(false, false); + if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) + return; default_output_file = this->p->background_process.output_filepath_for_project(into_path(get_project_filename(".3mf"))); } catch (const std::exception &ex) { @@ -4499,7 +4515,7 @@ void Plater::undo_to(int selection) p->undo(); return; } - + const int idx = p->get_active_snapshot_index() - selection - 1; p->undo_redo_to(p->undo_redo_stack().snapshots()[idx].timestamp); } @@ -4509,7 +4525,7 @@ void Plater::redo_to(int selection) p->redo(); return; } - + const int idx = p->get_active_snapshot_index() + selection + 1; p->undo_redo_to(p->undo_redo_stack().snapshots()[idx].timestamp); } @@ -4578,12 +4594,12 @@ void Plater::on_config_change(const DynamicPrintConfig &config) else if ((opt_key == "bed_shape") || (opt_key == "bed_custom_texture") || (opt_key == "bed_custom_model")) { bed_shape_changed = true; update_scheduled = true; - } + } else if (boost::starts_with(opt_key, "wipe_tower") || // opt_key == "filament_minimal_purge_on_wipe_tower" // ? #ys_FIXME opt_key == "single_extruder_multi_material") { update_scheduled = true; - } + } else if(opt_key == "variable_layer_height") { if (p->config->opt_bool("variable_layer_height") != true) { p->view3D->enable_layers_editing(false); @@ -4613,7 +4629,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) p->config->option("bed_custom_texture")->value, p->config->option("bed_custom_model")->value); - if (update_scheduled) + if (update_scheduled) update(); if (p->main_frame->is_loaded()) @@ -4634,10 +4650,10 @@ void Plater::on_activate() #endif if (! this->p->delayed_error_message.empty()) { - std::string msg = std::move(this->p->delayed_error_message); - this->p->delayed_error_message.clear(); + std::string msg = std::move(this->p->delayed_error_message); + this->p->delayed_error_message.clear(); GUI::show_error(this, msg); - } + } } wxString Plater::get_project_filename(const wxString& extension) const @@ -4681,7 +4697,7 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology) if (p->background_process.select_technology(printer_technology)) { // Update the active presets. } - //FIXME for SLA synchronize + //FIXME for SLA synchronize //p->background_process.apply(Model)! p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export"); @@ -4741,7 +4757,7 @@ void Plater::schedule_background_process(bool schedule/* = true*/) this->p->suppressed_backround_processing_update = false; } -bool Plater::is_background_process_running() const +bool Plater::is_background_process_running() const { return this->p->background_process_timer.IsRunning(); } From 91e0b7aa9ac15e8cdc5236cd3bd9ba346f5d314d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 6 Aug 2019 19:01:59 +0200 Subject: [PATCH 512/627] Fixed control of options category for single material profiles --- src/slic3r/GUI/GUI_ObjectList.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 346d4494b0..1c3b1a3e41 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1013,6 +1013,11 @@ const std::vector& ObjectList::get_options_for_bundle(const wxStrin return empty; } +static bool improper_category(const std::string& category, const int extruders_cnt) +{ + return category.empty() || (extruders_cnt == 1 && (category == "Extruders" || category == "Wipe options" )); +} + void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part) { auto options = get_options(is_part); @@ -1024,8 +1029,8 @@ void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const { auto const opt = config.def()->get(option); auto category = opt->category; - if (category.empty() || - (category == "Extruders" && extruders_cnt == 1)) continue; + if (improper_category(category, extruders_cnt)) + continue; const std::string& label = !opt->full_label.empty() ? opt->full_label : opt->label; std::pair option_label(option, label); @@ -1537,7 +1542,7 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) const int extruders_cnt = extruders_count(); for (auto& it : bundle) { - if (it.first.empty() || it.first == "Extruders" && extruders_cnt == 1) + if (improper_category(it.first, extruders_cnt)) continue; append_menu_item(menu, wxID_ANY, _(it.first), "", @@ -1550,7 +1555,7 @@ void ObjectList::create_freq_settings_popupmenu(wxMenu *menu) m_freq_settings_fff : m_freq_settings_sla; for (auto& it : bundle_quick) { - if (it.first.empty() || it.first == "Extruders" && extruders_cnt == 1) + if (improper_category(it.first, extruders_cnt)) continue; append_menu_item(menu, wxID_ANY, wxString::Format(_(L("Quick Add Settings (%s)")), _(it.first)), "", @@ -2184,7 +2189,7 @@ SettingsBundle ObjectList::get_item_settings_bundle(const DynamicPrintConfig* co for (auto& opt_key : opt_keys) { auto category = config->def()->get(opt_key)->category; - if (category.empty() || (category == "Extruders" && extruders_cnt == 1)) + if (improper_category(category, extruders_cnt)) continue; std::vector< std::string > new_category; @@ -2456,13 +2461,13 @@ void ObjectList::del_layer_range(const t_layer_height_range& range) select_item(selectable_item); } -double get_min_layer_height(const int extruder_idx) +static double get_min_layer_height(const int extruder_idx) { const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; return config.opt_float("min_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1); } -double get_max_layer_height(const int extruder_idx) +static double get_max_layer_height(const int extruder_idx) { const DynamicPrintConfig& config = wxGetApp().preset_bundle->printers.get_edited_preset().config; return config.opt_float("max_layer_height", extruder_idx <= 0 ? 0 : extruder_idx-1); From d36c64873d9260c26be552905a4c2edb10256f79 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 6 Aug 2019 19:53:20 +0200 Subject: [PATCH 513/627] Fix of a Linux / GTK specific crash on deleting of Layers from the side panel: wxWidgets / GTK internally delays the EnsureVisible() call to Idle processing. If the item is deleted after EnsureVisible() is planned and before the Idle processed, the Idle processing routine works with deleted data --- src/slic3r/GUI/GUI_ObjectList.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 346d4494b0..a85ab0609a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -186,7 +186,18 @@ ObjectList::ObjectList(wxWindow* parent) : Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, [this](wxCommandEvent& e) { last_volume_is_deleted(e.GetInt()); }); - Bind(wxEVT_SIZE, ([this](wxSizeEvent &e) { this->EnsureVisible(this->GetCurrentItem()); e.Skip(); })); + Bind(wxEVT_SIZE, ([this](wxSizeEvent &e) { +#ifdef __WXGTK__ + // On GTK, the EnsureVisible call is postponed to Idle processing (see wxDataViewCtrl::m_ensureVisibleDefered). + // So the postponed EnsureVisible() call is planned for an item, which may not exist at the Idle processing time, if this wxEVT_SIZE + // event is succeeded by a delete of the currently active item. We are trying our luck by postponing the wxEVT_SIZE triggered EnsureVisible(), + // which seems to be working as of now. + this->CallAfter([this](){ this->EnsureVisible(this->GetCurrentItem()); }); +#else + this->EnsureVisible(this->GetCurrentItem()); +#endif + e.Skip(); + })); } ObjectList::~ObjectList() @@ -3601,4 +3612,4 @@ ModelObject* ObjectList::object(const int obj_idx) const } } //namespace GUI -} //namespace Slic3r \ No newline at end of file +} //namespace Slic3r From 2f642ffffb3601b870c2e53194866f035e174bc7 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 7 Aug 2019 10:18:16 +0200 Subject: [PATCH 514/627] ModelInstance::is_printable() takes into account printability of the parent ModelObject. --- src/libslic3r/Model.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 10d6a6d46e..2678ddfcc6 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -643,7 +643,7 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - bool is_printable() const { return printable && (print_volume_state == PVS_Inside); } + bool is_printable() const { return object->printable && printable && (print_volume_state == PVS_Inside); } // Getting the input polygon for arrange arrangement::ArrangePolygon get_arrange_polygon() const; From f61c982111ec15567082fb46c985380ece2f6413 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 7 Aug 2019 10:54:36 +0200 Subject: [PATCH 515/627] GCode.cpp: fixed detection of empty layers so it doesn't give false positives That could happen on empty support layers which do not necessarily matter, since their spacing is not generally synchronized with the object The new hopefully correct logic is "if there are extrusions on a layer, check that last layer with extrusions is at most the new layer height below This is a fixup of changes from 0de6e53 and 6ab1cec --- src/libslic3r/GCode.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index aa857e8e42..f21baaab44 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -442,6 +442,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Pair the object layers with the support layers by z. size_t idx_object_layer = 0; size_t idx_support_layer = 0; + double last_extrusion_z = 0.; while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { LayerToPrint layer_to_print; layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer ++] : nullptr; @@ -456,15 +457,16 @@ std::vector GCode::collect_layers_to_print(const PrintObjec } } - // Let's make sure that the last layer is not empty, so we don't build on top of it. - if (! layers_to_print.empty() - && ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) - || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions())) - && (! layers_to_print.back().object_layer || ! layers_to_print.back().object_layer->has_extrusions()) - && (! layers_to_print.back().support_layer || ! layers_to_print.back().support_layer->has_extrusions())) - throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + - _(L("Object name: ")) + object.model_object()->name + "\n" + _(L("Print z: ")) + - std::to_string(layers_to_print.back().print_z())); + // In case there are extrusions on this layer, check there is a layer to lay it on. + if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) + || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions())) { + if (layer_to_print.print_z() - layer_to_print.layer()->height - EPSILON > last_extrusion_z) + throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + + _(L("Object name: ")) + object.model_object()->name + "\n" + _(L("Print z: ")) + + std::to_string(layers_to_print.back().print_z())); + // Stash last print_z with extrusions. + last_extrusion_z = layer_to_print.print_z(); + } layers_to_print.emplace_back(layer_to_print); } @@ -766,7 +768,7 @@ void GCode::_do_export(Print &print, FILE *file) mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end()); if (! mm3_per_mm.empty()) { // In order to honor max_print_speed we need to find a target volumetric - // speed that we can use throughout the print. So we define this target + // speed that we can use throughout the print. So we define this target // volumetric speed as the volumetric speed produced by printing the // smallest cross-section at the maximum speed: any larger cross-section // will need slower feedrates. @@ -833,7 +835,7 @@ void GCode::_do_export(Print &print, FILE *file) _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag); } - // Prepare the helper object for replacing placeholders in custom G-code and output filename. + // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser(); m_placeholder_parser.update_timestamp(); print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode"); @@ -2617,7 +2619,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm ); } - double F = speed * 60; // convert mm/sec to mm/min + double F = speed * 60; // convert mm/sec to mm/min // extrude arc or line if (m_enable_extrusion_role_markers) From a58b1844e032fc58e13a115a3fabedfd0690a04f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 7 Aug 2019 11:14:04 +0200 Subject: [PATCH 516/627] WIP: Picking fix - disable alpha blending and render with false colors including the alpha channels, which have their 3 lowest bits set to zero. In case some blending occurs, the lowest 3 bits will likely be used to interpolate between the false colors, therefore the 3 lowest bits may be used to detect alpha blending or multi-sampling. --- src/slic3r/GUI/GLCanvas3D.cpp | 73 +++++++++++++++-------------------- 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ffc33a93e4..9937d8de8a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3764,6 +3764,7 @@ void GLCanvas3D::_picking_pass() const // Better to use software ray - casting on a bounding - box hierarchy. if (m_multisample_allowed) + // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. glsafe(::glDisable(GL_MULTISAMPLE)); glsafe(::glDisable(GL_BLEND)); @@ -3793,7 +3794,9 @@ void GLCanvas3D::_picking_pass() const if (inside) { glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color)); - volume_id = color[0] + (color[1] << 8) + (color[2] << 16); + if (((color[0] | color[1] | color[2] | color[3]) & 0x7) == 0) + // Only non-interpolated colors are valid, those have their lowest three bits zeroed. + volume_id = (color[0] >> 3) + (color[1] << (5 - 3)) + (color[2] << (10 - 3)) + (color[3] << (15 - 3)); } if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) { @@ -3816,6 +3819,7 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const if (m_picking_enabled) { if (m_multisample_allowed) + // This flag is often ignored by NVIDIA drivers if rendering into a screen buffer. glsafe(::glDisable(GL_MULTISAMPLE)); glsafe(::glDisable(GL_BLEND)); @@ -3841,7 +3845,9 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const struct Pixel { std::array data; - int id() const { return data[0] + (data[1] << 8) + (data[2] << 16); } + // Only non-interpolated colors are valid, those have their lowest three bits zeroed. + bool valid() const { return ((data[0] | data[1] | data[2] | data[3]) & 0x7) == 0; } + int id() const { return (data[0] >> 3) + (data[1] << (5 - 3)) + (data[2] << (10 - 3)) + (data[3] << (15 - 3)); } }; std::vector frame(px_count); @@ -3851,17 +3857,15 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const tbb::parallel_for(tbb::blocked_range(0, frame.size(), (size_t)width), [this, &frame, &idxs, &mutex](const tbb::blocked_range& range) { for (size_t i = range.begin(); i < range.end(); ++i) - { - int volume_id = frame[i].id(); - if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) - { - mutex.lock(); - idxs.insert(volume_id); - mutex.unlock(); - } - } - } - ); + if (frame[i].valid()) { + int volume_id = frame[i].id(); + if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) { + mutex.lock(); + idxs.insert(volume_id); + mutex.unlock(); + } + } + }); #else std::vector frame(4 * px_count); glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data())); @@ -4040,42 +4044,27 @@ void GLCanvas3D::_render_volumes_for_picking() const // do not cull backfaces to show broken geometry, if any glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); const Transform3d& view_matrix = m_camera.get_view_matrix(); - GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, GLVolumeCollection::Opaque, view_matrix); - for (const GLVolumeWithIdAndZ& volume : to_render) - { - // Object picking mode. Render the object with a color encoding the object index. - unsigned int r = (volume.second.first & 0x000000FF) >> 0; - unsigned int g = (volume.second.first & 0x0000FF00) >> 8; - unsigned int b = (volume.second.first & 0x00FF0000) >> 16; - glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); - - if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) - volume.first->render(); - } - - to_render = volumes_to_render(m_volumes.volumes, GLVolumeCollection::Transparent, view_matrix); - for (const GLVolumeWithIdAndZ& volume : to_render) - { - // Object picking mode. Render the object with a color encoding the object index. - unsigned int r = (volume.second.first & 0x000000FF) >> 0; - unsigned int g = (volume.second.first & 0x0000FF00) >> 8; - unsigned int b = (volume.second.first & 0x00FF0000) >> 16; - glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255)); - - if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) - volume.first->render(); - } + for (size_t type = 0; type < 2; ++ type) { + GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, (type == 0) ? GLVolumeCollection::Opaque : GLVolumeCollection::Transparent, view_matrix); + for (const GLVolumeWithIdAndZ& volume : to_render) + if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) { + // Object picking mode. Render the object with a color encoding the object index. + unsigned int id = volume.second.first; + unsigned int r = (id & (0x0000001F << 0)) << 3; + unsigned int g = (id & (0x0000001F << 5)) >> (5 - 3); + unsigned int b = (id & (0x0000001F << 10)) >> (10 - 3); + unsigned int a = (id & (0x0000001F << 15)) >> (15 - 3); + glsafe(::glColor4f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255, (GLfloat)a * INV_255)); + volume.first->render(); + } + } glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glDisable(GL_BLEND)); glsafe(::glEnable(GL_CULL_FACE)); } From 1c479ad6c5a9862ea214c63e31a0305ab1d591ea Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 7 Aug 2019 11:17:17 +0200 Subject: [PATCH 517/627] Fix build without PCH --- src/slic3r/GUI/GLTexture.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index c4063b93d4..c8fd01f41e 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -5,6 +5,7 @@ #include #include #include +#include class wxImage; From 2ad3c05a655cd801dc88adf3af7a4429a47ee6b3 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 7 Aug 2019 11:37:38 +0200 Subject: [PATCH 518/627] WIP: Improvement in the picking robustness: store a checksum into the alpha channel. --- src/slic3r/GUI/GLCanvas3D.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9937d8de8a..4de20c491b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3753,6 +3753,19 @@ void GLCanvas3D::_refresh_if_shown_on_screen() } } +static inline unsigned char picking_checksum(unsigned char red, unsigned char green, unsigned char blue) +{ + // 8 bit hash for the color + unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff; + // Increase enthropy by a bit reversal + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + // Flip every second bit to increase the enthropy even more. + b ^= 0x55; + return b; +} + void GLCanvas3D::_picking_pass() const { if (m_picking_enabled && !m_mouse.dragging && (m_mouse.position != Vec2d(DBL_MAX, DBL_MAX))) @@ -3794,9 +3807,9 @@ void GLCanvas3D::_picking_pass() const if (inside) { glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color)); - if (((color[0] | color[1] | color[2] | color[3]) & 0x7) == 0) + if (picking_checksum(color[0], color[1], color[2]) == color[3]) // Only non-interpolated colors are valid, those have their lowest three bits zeroed. - volume_id = (color[0] >> 3) + (color[1] << (5 - 3)) + (color[2] << (10 - 3)) + (color[3] << (15 - 3)); + volume_id = color[0] + (color[1] << 8) + (color[2] << 16); } if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size())) { @@ -3846,8 +3859,8 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const { std::array data; // Only non-interpolated colors are valid, those have their lowest three bits zeroed. - bool valid() const { return ((data[0] | data[1] | data[2] | data[3]) & 0x7) == 0; } - int id() const { return (data[0] >> 3) + (data[1] << (5 - 3)) + (data[2] << (10 - 3)) + (data[3] << (15 - 3)); } + bool valid() const { return picking_checksum(data[0], data[1], data[2]) == data[3]; } + int id() const { return data[0] + (data[1] << 8) + (data[2] << 16); } }; std::vector frame(px_count); @@ -4054,10 +4067,10 @@ void GLCanvas3D::_render_volumes_for_picking() const if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) { // Object picking mode. Render the object with a color encoding the object index. unsigned int id = volume.second.first; - unsigned int r = (id & (0x0000001F << 0)) << 3; - unsigned int g = (id & (0x0000001F << 5)) >> (5 - 3); - unsigned int b = (id & (0x0000001F << 10)) >> (10 - 3); - unsigned int a = (id & (0x0000001F << 15)) >> (15 - 3); + unsigned int r = (id & (0x000000FF << 0)) << 0; + unsigned int g = (id & (0x000000FF << 8)) >> 8; + unsigned int b = (id & (0x000000FF << 16)) >> 16; + unsigned int a = picking_checksum(r, g, b); glsafe(::glColor4f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255, (GLfloat)a * INV_255)); volume.first->render(); } From 8e4f777bd33ee6708b7f2dc308de1ea223025d2f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 7 Aug 2019 12:00:36 +0200 Subject: [PATCH 519/627] One more fix on the empty layers detection - support contact z distance is taken into account If it wasn't, anything with raft would be rejected unless contact z was zero. We do not want that. --- src/libslic3r/GCode.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f21baaab44..02fefceec1 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -442,7 +442,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Pair the object layers with the support layers by z. size_t idx_object_layer = 0; size_t idx_support_layer = 0; - double last_extrusion_z = 0.; + const LayerToPrint* last_extrusion_layer = nullptr; while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) { LayerToPrint layer_to_print; layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer ++] : nullptr; @@ -457,18 +457,25 @@ std::vector GCode::collect_layers_to_print(const PrintObjec } } + layers_to_print.emplace_back(layer_to_print); + // In case there are extrusions on this layer, check there is a layer to lay it on. if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions())) { - if (layer_to_print.print_z() - layer_to_print.layer()->height - EPSILON > last_extrusion_z) + double support_contact_z = (last_extrusion_layer && last_extrusion_layer->support_layer) + ? object.config().support_material_contact_distance + : 0.; + double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + + layer_to_print.layer()->height + + support_contact_z; + + if (layer_to_print.print_z() > maximal_print_z + EPSILON) throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + _(L("Object name: ")) + object.model_object()->name + "\n" + _(L("Print z: ")) + std::to_string(layers_to_print.back().print_z())); - // Stash last print_z with extrusions. - last_extrusion_z = layer_to_print.print_z(); + // Remember last layer with extrusions. + last_extrusion_layer = &layers_to_print.back(); } - - layers_to_print.emplace_back(layer_to_print); } return layers_to_print; From abe16fa22e59090669e1e6ca4a8329f4ac49e682 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 7 Aug 2019 14:11:41 +0200 Subject: [PATCH 520/627] Added printable property for object with instances --- src/slic3r/GUI/GUI_ObjectList.cpp | 31 ++++++---- src/slic3r/GUI/wxExtensions.cpp | 95 ++++++++++++++++++++++++++----- src/slic3r/GUI/wxExtensions.hpp | 5 ++ 3 files changed, 106 insertions(+), 25 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index daed3fb720..66387b9f05 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3645,18 +3645,10 @@ void ObjectList::update_after_undo_redo() void ObjectList::update_printable_state(int obj_idx, int instance_idx) { ModelObject* object = (*m_objects)[obj_idx]; - PrintIndicator printable = piUndef; + const PrintIndicator printable = object->instances[instance_idx]->printable ? piPrintable : piUnprintable; if (object->instances.size() == 1) - { - printable = object->instances[0]->printable ? piPrintable : piUnprintable; instance_idx = -1; - } - else - { - m_objects_model->SetPrintableState(piUndef, obj_idx); - printable = object->instances[instance_idx]->printable ? piPrintable : piUnprintable; - } m_objects_model->SetPrintableState(printable, obj_idx, instance_idx); } @@ -3667,7 +3659,26 @@ void ObjectList::toggle_printable_state(wxDataViewItem item) if (!(type&(itObject|itInstance/*|itVolume*/))) return; - wxGetApp().plater()->canvas3D()->get_selection().toggle_instance_printable_state(); + if (type & itObject) + { + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + ModelObject* object = (*m_objects)[obj_idx]; + + // get object's printable and change it + bool printable = !m_objects_model->IsPrintable(item); + // set printable value for all instances in object + for (auto inst : object->instances) + inst->printable = printable; + + // update printable state on canvas + std::vector obj_idxs = {(size_t)obj_idx}; + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs); + + // update printable state in ObjectList + m_objects_model->SetObjectPrintableState(printable ? piPrintable : piUnprintable , item); + } + else + wxGetApp().plater()->canvas3D()->get_selection().toggle_instance_printable_state(); // update scene wxGetApp().plater()->update(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 5928a4c5dc..dcc47750ef 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -806,22 +806,8 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem& paren size_t counter = 0; while (counter < print_indicator.size()) { instance_node = new ObjectDataViewModelNode(inst_root_node, itInstance); + instance_node->set_printable_icon(print_indicator[counter] ? piPrintable : piUnprintable); - // if InstanceRoot item is just created and start to adding Instances - if (just_created && counter == 0) - { - ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); - - // use object's printable state to first instance, if it was defined - instance_node->set_printable_icon(obj_node->IsPrintable() != piUndef ? obj_node->IsPrintable() : - print_indicator[counter] ? piPrintable : piUnprintable ); - - // and set printable state for object_node to piUndef - obj_node->set_printable_icon(piUndef); - ItemChanged(parent_item); - } - else - instance_node->set_printable_icon(print_indicator[counter] ? piPrintable : piUnprintable); inst_root_node->Append(instance_node); // notify control const wxDataViewItem instance_item((void*)instance_node); @@ -829,9 +815,64 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem& paren ++counter; } + // update object_node printable property + UpdateObjectPrintable(parent_item); + return wxDataViewItem((void*)instance_node); } +void ObjectDataViewModel::UpdateObjectPrintable(wxDataViewItem parent_item) +{ + const wxDataViewItem inst_root_item = GetInstanceRootItem(parent_item); + if (!inst_root_item) + return; + + ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); + + const size_t child_cnt = inst_root_node->GetChildren().Count(); + PrintIndicator obj_pi = piUnprintable; + for (size_t i=0; i < child_cnt; i++) + if (inst_root_node->GetNthChild(i)->IsPrintable() & piPrintable) { + obj_pi = piPrintable; + break; + } + // and set printable state for object_node to piUndef + ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); + obj_node->set_printable_icon(obj_pi); + ItemChanged(parent_item); +} + +// update printable property for all instances from object +void ObjectDataViewModel::UpdateInstancesPrintable(wxDataViewItem parent_item) +{ + const wxDataViewItem inst_root_item = GetInstanceRootItem(parent_item); + if (!inst_root_item) + return; + + ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); + const PrintIndicator obj_pi = obj_node->IsPrintable(); + + ObjectDataViewModelNode* inst_root_node = (ObjectDataViewModelNode*)inst_root_item.GetID(); + const size_t child_cnt = inst_root_node->GetChildren().Count(); + + for (size_t i=0; i < child_cnt; i++) + { + ObjectDataViewModelNode* inst_node = inst_root_node->GetNthChild(i); + // and set printable state for object_node to piUndef + inst_node->set_printable_icon(obj_pi); + ItemChanged(wxDataViewItem((void*)inst_node)); + } +} + +bool ObjectDataViewModel::IsPrintable(const wxDataViewItem& item) const +{ + ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)item.GetID(); + if (!node) + return false; + + return node->IsPrintable() == piPrintable; +} + wxDataViewItem ObjectDataViewModel::AddLayersRoot(const wxDataViewItem &parent_item) { return AddRoot(parent_item, itLayerRoot); @@ -951,6 +992,9 @@ wxDataViewItem ObjectDataViewModel::Delete(const wxDataViewItem &item) return ret_item; } + if (node->m_type & itInstance) + UpdateObjectPrintable(wxDataViewItem(node_parent->GetParent())); + // if there was last layer item, delete this one and layers root item if (node_parent->GetChildCount() == 0 && node_parent->m_type == itLayerRoot) { @@ -1083,6 +1127,9 @@ wxDataViewItem ObjectDataViewModel::DeleteLastInstance(const wxDataViewItem &par #endif //__WXGTK__ } + // update object_node printable property + UpdateObjectPrintable(parent_item); + return ret_item; } @@ -1676,9 +1723,27 @@ wxDataViewItem ObjectDataViewModel::SetPrintableState( node->set_printable_icon(printable); ItemChanged(item); + if (subobj_idx >= 0) + UpdateObjectPrintable(GetItemById(obj_idx)); + return item; } +wxDataViewItem ObjectDataViewModel::SetObjectPrintableState( + PrintIndicator printable, + wxDataViewItem obj_item) +{ + ObjectDataViewModelNode* node = (ObjectDataViewModelNode*)obj_item.GetID(); + if (!node) + return wxDataViewItem(0); + node->set_printable_icon(printable); + ItemChanged(obj_item); + + UpdateInstancesPrintable(obj_item); + + return obj_item; +} + void ObjectDataViewModel::Rescale() { wxDataViewItemArray all_items; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 242a487d1c..24a28ecff1 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -482,12 +482,17 @@ public: void UpdateSettingsDigest( const wxDataViewItem &item, const std::vector& categories); + bool IsPrintable(const wxDataViewItem &item) const; + void UpdateObjectPrintable(wxDataViewItem parent_item); + void UpdateInstancesPrintable(wxDataViewItem parent_item); + void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } void SetWarningBitmap(wxBitmap* bitmap) { m_warning_bmp = bitmap; } void SetVolumeType(const wxDataViewItem &item, const Slic3r::ModelVolumeType type); wxDataViewItem SetPrintableState( PrintIndicator printable, int obj_idx, int subobj_idx = -1, ItemType subobj_type = itInstance); + wxDataViewItem SetObjectPrintableState(PrintIndicator printable, wxDataViewItem obj_item); void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } // Rescale bitmaps for existing Items From 2d7f478dac6206284da6aed9c75a111a0a1db2b7 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 7 Aug 2019 14:15:38 +0200 Subject: [PATCH 521/627] Finished picking by color with a checksum in the alpha channel to guard against unwanted alpha blending and / or multi sampling. --- src/slic3r/GUI/GLCanvas3D.cpp | 19 ++------- src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 45 ++++++++++++++------ src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 24 ++++++----- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoCut.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 17 ++++---- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 14 +++--- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 30 ++++++------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 8 ++-- 10 files changed, 89 insertions(+), 74 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 4de20c491b..b91b8c36a9 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3753,19 +3753,6 @@ void GLCanvas3D::_refresh_if_shown_on_screen() } } -static inline unsigned char picking_checksum(unsigned char red, unsigned char green, unsigned char blue) -{ - // 8 bit hash for the color - unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff; - // Increase enthropy by a bit reversal - b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; - b = (b & 0xCC) >> 2 | (b & 0x33) << 2; - b = (b & 0xAA) >> 1 | (b & 0x55) << 1; - // Flip every second bit to increase the enthropy even more. - b ^= 0x55; - return b; -} - void GLCanvas3D::_picking_pass() const { if (m_picking_enabled && !m_mouse.dragging && (m_mouse.position != Vec2d(DBL_MAX, DBL_MAX))) @@ -3807,7 +3794,7 @@ void GLCanvas3D::_picking_pass() const if (inside) { glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color)); - if (picking_checksum(color[0], color[1], color[2]) == color[3]) + if (picking_checksum_alpha_channel(color[0], color[1], color[2]) == color[3]) // Only non-interpolated colors are valid, those have their lowest three bits zeroed. volume_id = color[0] + (color[1] << 8) + (color[2] << 16); } @@ -3859,7 +3846,7 @@ void GLCanvas3D::_rectangular_selection_picking_pass() const { std::array data; // Only non-interpolated colors are valid, those have their lowest three bits zeroed. - bool valid() const { return picking_checksum(data[0], data[1], data[2]) == data[3]; } + bool valid() const { return picking_checksum_alpha_channel(data[0], data[1], data[2]) == data[3]; } int id() const { return data[0] + (data[1] << 8) + (data[2] << 16); } }; @@ -4070,7 +4057,7 @@ void GLCanvas3D::_render_volumes_for_picking() const unsigned int r = (id & (0x000000FF << 0)) << 0; unsigned int g = (id & (0x000000FF << 8)) >> 8; unsigned int b = (id & (0x000000FF << 16)) >> 16; - unsigned int a = picking_checksum(r, g, b); + unsigned int a = picking_checksum_alpha_channel(r, g, b); glsafe(::glColor4f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255, (GLfloat)a * INV_255)); volume.first->render(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index a650746468..29e5e5686d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -29,19 +29,21 @@ GLGizmoBase::Grabber::Grabber() color[0] = 1.0f; color[1] = 1.0f; color[2] = 1.0f; + color[3] = 1.0f; } void GLGizmoBase::Grabber::render(bool hover, float size) const { - float render_color[3]; + float render_color[4]; if (hover) { render_color[0] = 1.0f - color[0]; render_color[1] = 1.0f - color[1]; render_color[2] = 1.0f - color[2]; + render_color[3] = color[3]; } else - ::memcpy((void*)render_color, (const void*)color, 3 * sizeof(float)); + ::memcpy((void*)render_color, (const void*)color, 4 * sizeof(float)); render(size, render_color, true); } @@ -63,7 +65,7 @@ void GLGizmoBase::Grabber::render(float size, const float* render_color, bool us if (use_lighting) glsafe(::glEnable(GL_LIGHTING)); - glsafe(::glColor3fv(render_color)); + glsafe(::glColor4fv(render_color)); glsafe(::glPushMatrix()); glsafe(::glTranslated(center(0), center(1), center(2))); @@ -144,9 +146,9 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, u , m_dragging(false) , m_imgui(wxGetApp().imgui()) { - ::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 3 * sizeof(float)); - ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 3 * sizeof(float)); - ::memcpy((void*)m_highlight_color, (const void*)DEFAULT_HIGHLIGHT_COLOR, 3 * sizeof(float)); + ::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 4 * sizeof(float)); + ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 4 * sizeof(float)); + ::memcpy((void*)m_highlight_color, (const void*)DEFAULT_HIGHLIGHT_COLOR, 4 * sizeof(float)); } void GLGizmoBase::set_hover_id(int id) @@ -161,7 +163,7 @@ void GLGizmoBase::set_hover_id(int id) void GLGizmoBase::set_highlight_color(const float* color) { if (color != nullptr) - ::memcpy((void*)m_highlight_color, (const void*)color, 3 * sizeof(float)); + ::memcpy((void*)m_highlight_color, (const void*)color, 4 * sizeof(float)); } void GLGizmoBase::enable_grabber(unsigned int id) @@ -210,7 +212,7 @@ void GLGizmoBase::update(const UpdateData& data) on_update(data); } -std::array GLGizmoBase::picking_color_component(unsigned int id) const +std::array GLGizmoBase::picking_color_component(unsigned int id) const { static const float INV_255 = 1.0f / 255.0f; @@ -220,9 +222,12 @@ std::array GLGizmoBase::picking_color_component(unsigned int id) const id -= m_group_id; // color components are encoded to match the calculation of volume_id made into GLCanvas3D::_picking_pass() - return std::array { (float)((id >> 0) & 0xff) * INV_255, // red - (float)((id >> 8) & 0xff) * INV_255, // green - (float)((id >> 16) & 0xff) * INV_255 }; // blue + return std::array { + float((id >> 0) & 0xff) * INV_255, // red + float((id >> 8) & 0xff) * INV_255, // green + float((id >> 16) & 0xff) * INV_255, // blue + float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff))* INV_255 // checksum for validating against unwanted alpha blending and multi sampling + }; } void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const @@ -247,10 +252,11 @@ void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const { if (m_grabbers[i].enabled) { - std::array color = picking_color_component(i); + std::array color = picking_color_component(i); m_grabbers[i].color[0] = color[0]; m_grabbers[i].color[1] = color[1]; m_grabbers[i].color[2] = color[2]; + m_grabbers[i].color[3] = color[3]; m_grabbers[i].render_for_picking(mean_size); } } @@ -267,5 +273,20 @@ std::string GLGizmoBase::format(float value, unsigned int decimals) const return Slic3r::string_printf("%.*f", decimals, value); } +// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components +// were not interpolated by alpha blending or multi sampling. +unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue) +{ + // 8 bit hash for the color + unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff; + // Increase enthropy by a bit reversal + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + // Flip every second bit to increase the enthropy even more. + b ^= 0x55; + return b; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index b84442b94d..7b73c62c25 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -21,11 +21,11 @@ class ModelObject; namespace GUI { -static const float DEFAULT_BASE_COLOR[3] = { 0.625f, 0.625f, 0.625f }; -static const float DEFAULT_DRAG_COLOR[3] = { 1.0f, 1.0f, 1.0f }; -static const float DEFAULT_HIGHLIGHT_COLOR[3] = { 1.0f, 0.38f, 0.0f }; -static const float AXES_COLOR[3][3] = { { 0.75f, 0.0f, 0.0f }, { 0.0f, 0.75f, 0.0f }, { 0.0f, 0.0f, 0.75f } }; -static const float CONSTRAINED_COLOR[3] = { 0.5f, 0.5f, 0.5f }; +static const float DEFAULT_BASE_COLOR[4] = { 0.625f, 0.625f, 0.625f, 1.0f }; +static const float DEFAULT_DRAG_COLOR[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; +static const float DEFAULT_HIGHLIGHT_COLOR[4] = { 1.0f, 0.38f, 0.0f, 1.0f }; +static const float AXES_COLOR[][4] = { { 0.75f, 0.0f, 0.0f, 1.0f }, { 0.0f, 0.75f, 0.0f, 1.0f }, { 0.0f, 0.0f, 0.75f, 1.0f } }; +static const float CONSTRAINED_COLOR[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; @@ -48,7 +48,7 @@ protected: Vec3d center; Vec3d angles; - float color[3]; + float color[4]; bool enabled; bool dragging; @@ -94,9 +94,9 @@ protected: unsigned int m_sprite_id; int m_hover_id; bool m_dragging; - float m_base_color[3]; - float m_drag_color[3]; - float m_highlight_color[3]; + float m_base_color[4]; + float m_drag_color[4]; + float m_highlight_color[4]; mutable std::vector m_grabbers; ImGuiWrapper* m_imgui; @@ -166,7 +166,7 @@ protected: // Returns the picking color for the given id, based on the BASE_ID constant // No check is made for clashing with other picking color (i.e. GLVolumes) - std::array picking_color_component(unsigned int id) const; + std::array picking_color_component(unsigned int id) const; void render_grabbers(const BoundingBoxf3& box) const; void render_grabbers(float size) const; void render_grabbers_for_picking(const BoundingBoxf3& box) const; @@ -175,6 +175,10 @@ protected: std::string format(float value, unsigned int decimals) const; }; +// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components +// were not interpolated by alpha blending or multi sampling. +extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue); + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 39399fc0d3..481bec9562 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -17,7 +17,7 @@ namespace GUI { const double GLGizmoCut::Offset = 10.0; const double GLGizmoCut::Margin = 20.0; -const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0 }; +const std::array GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0, 1.0 }; GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 5bfeda526a..6e5738a422 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -11,7 +11,7 @@ class GLGizmoCut : public GLGizmoBase { static const double Offset; static const double Margin; - static const std::array GrabberColor; + static const std::array GrabberColor; mutable double m_cut_z; double m_start_z; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 5a42cbd31f..9fae8893ac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -115,7 +115,7 @@ void GLGizmoFlatten::on_render_for_picking() const const_cast(this)->update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { - glsafe(::glColor3fv(picking_color_component(i).data())); + glsafe(::glColor4fv(picking_color_component(i).data())); ::glBegin(GL_POLYGON); for (const Vec3d& vertex : m_planes[i].vertices) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 11bdcd4f83..862ffe41af 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -104,15 +104,15 @@ void GLGizmoMove3D::on_render() const // x axis m_grabbers[0].center = Vec3d(box.max(0) + Offset, center(1), center(2)); - ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[0].color, (const void*)&AXES_COLOR[0], 4 * sizeof(float)); // y axis m_grabbers[1].center = Vec3d(center(0), box.max(1) + Offset, center(2)); - ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[1], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[1].color, (const void*)&AXES_COLOR[1], 4 * sizeof(float)); // z axis m_grabbers[2].center = Vec3d(center(0), center(1), box.max(2) + Offset); - ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[2], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[2].color, (const void*)&AXES_COLOR[2], 4 * sizeof(float)); glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); @@ -123,7 +123,7 @@ void GLGizmoMove3D::on_render() const { if (m_grabbers[i].enabled) { - glsafe(::glColor3fv(AXES_COLOR[i])); + glsafe(::glColor4fv(AXES_COLOR[i])); ::glBegin(GL_LINES); ::glVertex3dv(center.data()); ::glVertex3dv(m_grabbers[i].center.data()); @@ -142,7 +142,7 @@ void GLGizmoMove3D::on_render() const else { // draw axis - glsafe(::glColor3fv(AXES_COLOR[m_hover_id])); + glsafe(::glColor4fv(AXES_COLOR[m_hover_id])); ::glBegin(GL_LINES); ::glVertex3dv(center.data()); ::glVertex3dv(m_grabbers[m_hover_id].center.data()); @@ -220,19 +220,20 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box float mean_size = (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0); double size = m_dragging ? (double)m_grabbers[axis].get_dragging_half_size(mean_size) : (double)m_grabbers[axis].get_half_size(mean_size); - float color[3]; - ::memcpy((void*)color, (const void*)m_grabbers[axis].color, 3 * sizeof(float)); + float color[4]; + ::memcpy((void*)color, (const void*)m_grabbers[axis].color, 4 * sizeof(float)); if (!picking && (m_hover_id != -1)) { color[0] = 1.0f - color[0]; color[1] = 1.0f - color[1]; color[2] = 1.0f - color[2]; + color[3] = color[3]; } if (!picking) glsafe(::glEnable(GL_LIGHTING)); - glsafe(::glColor3fv(color)); + glsafe(::glColor4fv(color)); glsafe(::glPushMatrix()); glsafe(::glTranslated(m_grabbers[axis].center(0), m_grabbers[axis].center(1), m_grabbers[axis].center(2))); if (axis == X) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index f481bb5d74..9a2c72633e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -155,7 +155,7 @@ void GLGizmoRotate::on_render() const transform_to_local(selection); glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); - glsafe(::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color)); + glsafe(::glColor4fv((m_hover_id != -1) ? m_drag_color : m_highlight_color)); render_circle(); @@ -166,7 +166,7 @@ void GLGizmoRotate::on_render() const render_reference_radius(); } - glsafe(::glColor3fv(m_highlight_color)); + glsafe(::glColor4fv(m_highlight_color)); if (m_hover_id != -1) render_angle(); @@ -287,14 +287,14 @@ void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const m_grabbers[0].center = Vec3d(::cos(m_angle) * grabber_radius, ::sin(m_angle) * grabber_radius, 0.0); m_grabbers[0].angles(2) = m_angle; - glsafe(::glColor3fv((m_hover_id != -1) ? m_drag_color : m_highlight_color)); + glsafe(::glColor4fv((m_hover_id != -1) ? m_drag_color : m_highlight_color)); ::glBegin(GL_LINES); ::glVertex3f(0.0f, 0.0f, 0.0f); ::glVertex3dv(m_grabbers[0].center.data()); glsafe(::glEnd()); - ::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[0].color, (const void*)m_highlight_color, 4 * sizeof(float)); render_grabbers(box); } @@ -306,8 +306,8 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick float mean_size = (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0); double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size(mean_size) : (double)m_grabbers[0].get_half_size(mean_size); - float color[3]; - ::memcpy((void*)color, (const void*)m_grabbers[0].color, 3 * sizeof(float)); + float color[4]; + ::memcpy((void*)color, (const void*)m_grabbers[0].color, 4 * sizeof(float)); if (!picking && (m_hover_id != -1)) { color[0] = 1.0f - color[0]; @@ -318,7 +318,7 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick if (!picking) glsafe(::glEnable(GL_LIGHTING)); - glsafe(::glColor3fv(color)); + glsafe(::glColor4fv(color)); glsafe(::glPushMatrix()); glsafe(::glTranslated(m_grabbers[0].center(0), m_grabbers[0].center(1), m_grabbers[0].center(2))); glsafe(::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 7dc38b8011..bf540cb00c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -172,20 +172,20 @@ void GLGizmoScale3D::on_render() const // x axis m_grabbers[0].center = m_transform * Vec3d(m_box.min(0), center(1), center(2)) - offset_x; m_grabbers[1].center = m_transform * Vec3d(m_box.max(0), center(1), center(2)) + offset_x; - ::memcpy((void*)m_grabbers[0].color, (ctrl_down && (m_hover_id == 1)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[0], 3 * sizeof(float)); - ::memcpy((void*)m_grabbers[1].color, (ctrl_down && (m_hover_id == 0)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[0], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[0].color, (ctrl_down && (m_hover_id == 1)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[0], 4 * sizeof(float)); + ::memcpy((void*)m_grabbers[1].color, (ctrl_down && (m_hover_id == 0)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[0], 4 * sizeof(float)); // y axis m_grabbers[2].center = m_transform * Vec3d(center(0), m_box.min(1), center(2)) - offset_y; m_grabbers[3].center = m_transform * Vec3d(center(0), m_box.max(1), center(2)) + offset_y; - ::memcpy((void*)m_grabbers[2].color, (ctrl_down && (m_hover_id == 3)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[1], 3 * sizeof(float)); - ::memcpy((void*)m_grabbers[3].color, (ctrl_down && (m_hover_id == 2)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[1], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[2].color, (ctrl_down && (m_hover_id == 3)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[1], 4 * sizeof(float)); + ::memcpy((void*)m_grabbers[3].color, (ctrl_down && (m_hover_id == 2)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[1], 4 * sizeof(float)); // z axis m_grabbers[4].center = m_transform * Vec3d(center(0), center(1), m_box.min(2)) - offset_z; m_grabbers[5].center = m_transform * Vec3d(center(0), center(1), m_box.max(2)) + offset_z; - ::memcpy((void*)m_grabbers[4].color, (ctrl_down && (m_hover_id == 5)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[2], 3 * sizeof(float)); - ::memcpy((void*)m_grabbers[5].color, (ctrl_down && (m_hover_id == 4)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[2], 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[4].color, (ctrl_down && (m_hover_id == 5)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[2], 4 * sizeof(float)); + ::memcpy((void*)m_grabbers[5].color, (ctrl_down && (m_hover_id == 4)) ? (const void*)CONSTRAINED_COLOR : (const void*)&AXES_COLOR[2], 4 * sizeof(float)); // uniform m_grabbers[6].center = m_transform * Vec3d(m_box.min(0), m_box.min(1), center(2)) - offset_x - offset_y; @@ -194,7 +194,7 @@ void GLGizmoScale3D::on_render() const m_grabbers[9].center = m_transform * Vec3d(m_box.min(0), m_box.max(1), center(2)) - offset_x + offset_y; for (int i = 6; i < 10; ++i) { - ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 3 * sizeof(float)); + ::memcpy((void*)m_grabbers[i].color, (const void*)m_highlight_color, 4 * sizeof(float)); } // sets grabbers orientation @@ -214,20 +214,20 @@ void GLGizmoScale3D::on_render() const // draw connections if (m_grabbers[0].enabled && m_grabbers[1].enabled) { - glsafe(::glColor3fv(m_grabbers[0].color)); + glsafe(::glColor4fv(m_grabbers[0].color)); render_grabbers_connection(0, 1); } if (m_grabbers[2].enabled && m_grabbers[3].enabled) { - glsafe(::glColor3fv(m_grabbers[2].color)); + glsafe(::glColor4fv(m_grabbers[2].color)); render_grabbers_connection(2, 3); } if (m_grabbers[4].enabled && m_grabbers[5].enabled) { - glsafe(::glColor3fv(m_grabbers[4].color)); + glsafe(::glColor4fv(m_grabbers[4].color)); render_grabbers_connection(4, 5); } - glsafe(::glColor3fv(m_base_color)); + glsafe(::glColor4fv(m_base_color)); render_grabbers_connection(6, 7); render_grabbers_connection(7, 8); render_grabbers_connection(8, 9); @@ -238,7 +238,7 @@ void GLGizmoScale3D::on_render() const else if ((m_hover_id == 0) || (m_hover_id == 1)) { // draw connection - glsafe(::glColor3fv(m_grabbers[0].color)); + glsafe(::glColor4fv(m_grabbers[0].color)); render_grabbers_connection(0, 1); // draw grabbers m_grabbers[0].render(true, grabber_mean_size); @@ -247,7 +247,7 @@ void GLGizmoScale3D::on_render() const else if ((m_hover_id == 2) || (m_hover_id == 3)) { // draw connection - glsafe(::glColor3fv(m_grabbers[2].color)); + glsafe(::glColor4fv(m_grabbers[2].color)); render_grabbers_connection(2, 3); // draw grabbers m_grabbers[2].render(true, grabber_mean_size); @@ -256,7 +256,7 @@ void GLGizmoScale3D::on_render() const else if ((m_hover_id == 4) || (m_hover_id == 5)) { // draw connection - glsafe(::glColor3fv(m_grabbers[4].color)); + glsafe(::glColor4fv(m_grabbers[4].color)); render_grabbers_connection(4, 5); // draw grabbers m_grabbers[4].render(true, grabber_mean_size); @@ -265,7 +265,7 @@ void GLGizmoScale3D::on_render() const else if (m_hover_id >= 6) { // draw connection - glsafe(::glColor3fv(m_drag_color)); + glsafe(::glColor4fv(m_drag_color)); render_grabbers_connection(6, 7); render_grabbers_connection(7, 8); render_grabbers_connection(8, 9); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 19b0c791ca..7db9444069 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -286,7 +286,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) glsafe(::glTranslated(0.0, 0.0, m_z_shift)); glsafe(::glMultMatrixd(instance_matrix.data())); - float render_color[3]; + float render_color[4]; size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); for (size_t i = 0; i < cache_size; ++i) { @@ -298,12 +298,14 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) // First decide about the color of the point. if (picking) { - std::array color = picking_color_component(i); + std::array color = picking_color_component(i); render_color[0] = color[0]; render_color[1] = color[1]; render_color[2] = color[2]; + render_color[3] = picking_checksum_alpha_channel(render_color[0], render_color[1], render_color[2]); } else { + render_color[3] = 1.f; if ((m_hover_id == i && m_editing_mode)) { // ignore hover state unless editing mode is active render_color[0] = 0.f; render_color[1] = 1.0f; @@ -320,7 +322,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) for (unsigned char i=0; i<3; ++i) render_color[i] = 0.5f; } } - glsafe(::glColor3fv(render_color)); + glsafe(::glColor4fv(render_color)); float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f}; glsafe(::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive)); From d25c5e04730cf6795fa634164faf79ae4bf613ba Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 7 Aug 2019 14:28:46 +0200 Subject: [PATCH 522/627] Added printable state update on canvas from Undo/redo --- src/slic3r/GUI/GUI_ObjectList.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 66387b9f05..441e91707b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3628,18 +3628,22 @@ void ObjectList::update_after_undo_redo() m_objects_model->DeleteAll(); size_t obj_idx = 0; + std::vector obj_idxs; + obj_idxs.reserve(m_objects->size()); while (obj_idx < m_objects->size()) { add_object_to_list(obj_idx, false); + obj_idxs.push_back(obj_idx); ++obj_idx; } -#ifndef __WXOSX__ -// selection_changed(); -#endif /* __WXOSX__ */ - update_selections(); m_prevent_canvas_selection_update = false; + + // update printable states on canvas + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs); + // update scene + wxGetApp().plater()->update(); } void ObjectList::update_printable_state(int obj_idx, int instance_idx) From a19a506ad8d925f9c9a6df93311f8b87b7205012 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 7 Aug 2019 14:55:34 +0200 Subject: [PATCH 523/627] GLCanvas requires 8bit alpha channel for picking checksums. --- src/slic3r/GUI/GLCanvas3DManager.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index b2a3161e89..781fc79482 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -290,7 +290,21 @@ GLCanvas3D* GLCanvas3DManager::get_canvas(wxGLCanvas* canvas) wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent) { - int attribList[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, 0 }; + int attribList[] = { + WX_GL_RGBA, + WX_GL_DOUBLEBUFFER, + // RGB channels each should be allocated with 8 bit depth. One should almost certainly get these bit depths by default. + WX_GL_MIN_RED, 8, + WX_GL_MIN_GREEN, 8, + WX_GL_MIN_BLUE, 8, + // Requesting an 8 bit alpha channel. Interestingly, the NVIDIA drivers would most likely work with some alpha plane, but glReadPixels would not return + // the alpha channel on NVIDIA if not requested when the GL context is created. + WX_GL_MIN_ALPHA, 0, + WX_GL_DEPTH_SIZE, 24, + WX_GL_SAMPLE_BUFFERS, GL_TRUE, + WX_GL_SAMPLES, 4, + 0 + }; if (s_multisample == MS_Unknown) { From 7cef1292b20306eb19cede4fefce492c590dfedf Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 7 Aug 2019 15:03:43 +0200 Subject: [PATCH 524/627] Yet another fix of the preceding commit. --- src/slic3r/GUI/GLCanvas3DManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index 781fc79482..c096c0e9fc 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -299,7 +299,7 @@ wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent) WX_GL_MIN_BLUE, 8, // Requesting an 8 bit alpha channel. Interestingly, the NVIDIA drivers would most likely work with some alpha plane, but glReadPixels would not return // the alpha channel on NVIDIA if not requested when the GL context is created. - WX_GL_MIN_ALPHA, 0, + WX_GL_MIN_ALPHA, 8, WX_GL_DEPTH_SIZE, 24, WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, @@ -314,7 +314,7 @@ wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent) } if (! can_multisample()) - attribList[4] = 0; + attribList[12] = 0; return new wxGLCanvas(parent, wxID_ANY, attribList, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS); } From 841d0796b79b923f98cd477b39c2abf9ab6e7ac4 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 7 Aug 2019 15:36:09 +0200 Subject: [PATCH 525/627] Fix of the SLA gizmo picking. --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 7db9444069..d120cb95f3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -302,7 +302,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) render_color[0] = color[0]; render_color[1] = color[1]; render_color[2] = color[2]; - render_color[3] = picking_checksum_alpha_channel(render_color[0], render_color[1], render_color[2]); + render_color[3] = color[3]; } else { render_color[3] = 1.f; From 850fbdbe56f18c1b18f1e09d6060198f02a1ef2d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 7 Aug 2019 15:39:46 +0200 Subject: [PATCH 526/627] Added snapshot taking for Set Printable/Unprintable for full object --- src/slic3r/GUI/GUI_ObjectList.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 441e91707b..d860e87244 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3669,7 +3669,13 @@ void ObjectList::toggle_printable_state(wxDataViewItem item) ModelObject* object = (*m_objects)[obj_idx]; // get object's printable and change it - bool printable = !m_objects_model->IsPrintable(item); + const bool printable = !m_objects_model->IsPrintable(item); + + const wxString snapshot_text = wxString::Format("%s %s", + printable ? _(L("Set Printable")) : _(L("Set Unprintable")), + object->name); + take_snapshot(snapshot_text); + // set printable value for all instances in object for (auto inst : object->instances) inst->printable = printable; From 178917950665d98932e2b77bf14a1382f8cce44d Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 7 Aug 2019 16:17:41 +0200 Subject: [PATCH 527/627] GLGizmoSlaSupports.cpp: unproject_on_mesh does not throw exceptions but uses bool return value to signal success --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 31 ++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 2 +- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 19b0c791ca..8489d5b984 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -422,9 +422,9 @@ void GLGizmoSlaSupports::update_mesh() -// Unprojects the mouse position on the mesh and return the hit point and normal of the facet. -// The function throws if no intersection if found. -std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) +// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal +// Return false if no intersection was found, true otherwise. +bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) { // if the gizmo doesn't have the V, F structures for igl, calculate them first: if (m_its == nullptr) @@ -457,7 +457,7 @@ std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3), MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3), point1.cast(), (point2-point1).cast(), hits)) - throw std::invalid_argument("unproject_on_mesh(): No intersection found."); + return false; // no intersection found std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); @@ -481,14 +481,12 @@ std::pair GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse if (i==hits.size() || (hits.size()-i) % 2 != 0) { // All hits are either clipped, or there is an odd number of unclipped // hits - meaning the nearest must be from inside the mesh. - throw std::invalid_argument("unproject_on_mesh(): No intersection found."); + return false; } // Calculate and return both the point and the facet normal. - return std::make_pair( - result, - a.cross(b) - ); + pos_and_normal = std::make_pair(result, a.cross(b)); + return true; } // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. @@ -526,16 +524,15 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous // If there is some selection, don't add new point and deselect everything instead. if (m_selection_empty) { - try { - std::pair pos_and_normal = unproject_on_mesh(mouse_position); // don't create anything if this throws + std::pair pos_and_normal; + if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection wxGetApp().plater()->take_snapshot(_(L("Add support point"))); m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); m_parent.set_as_dirty(); m_wait_for_up_event = true; } - catch (...) { // not clicked on object + else return false; - } } else select_point(NoPoints); @@ -739,10 +736,8 @@ void GLGizmoSlaSupports::on_update(const UpdateData& data) else { if (m_hover_id != -1 && (! m_editing_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) { std::pair pos_and_normal; - try { - pos_and_normal = unproject_on_mesh(data.mouse_pos.cast()); - } - catch (...) { return; } + if (! unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) + return; m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first; m_editing_cache[m_hover_id].support_point.is_new_island = false; m_editing_cache[m_hover_id].normal = pos_and_normal.second; @@ -1448,4 +1443,4 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog() } // namespace GUI -} // namespace Slic3r \ No newline at end of file +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index fb312e6644..fa3facf4b5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -32,7 +32,7 @@ private: int m_active_instance = -1; float m_active_instance_bb_radius; // to cache the bb mutable float m_z_shift = 0.f; - std::pair unproject_on_mesh(const Vec2d& mouse_pos); + bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal); const float RenderPointScale = 1.f; From b7f93292fa147d9f6bfd0ca7ae7df03728fe0c7b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 8 Aug 2019 09:45:42 +0200 Subject: [PATCH 528/627] FIx of Single test suite failure on two minority architectures #2461 --- xs/t/03_point.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/t/03_point.t b/xs/t/03_point.t index cb71f68f5f..c950998fbb 100644 --- a/xs/t/03_point.t +++ b/xs/t/03_point.t @@ -44,7 +44,7 @@ ok !$point->coincides_with($point2), 'coincides_with'; { my $line = Slic3r::Line->new([50,50], [125,-25]); - is +Slic3r::Point->new(100,0)->distance_to_line($line), 0, 'distance_to_line()'; + cmp_ok(abs(Slic3r::Point->new(100,0)->distance_to_line($line)), '<=', 4e-15, 'distance_to_line()'); } { From 8970ee28b26edd41496f268e5426d628e5b0db2a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 8 Aug 2019 09:48:56 +0200 Subject: [PATCH 529/627] Fixed linking of the "pad" combo box of the Plater with the respective pad boolean values of the configuration layer. --- src/slic3r/GUI/Field.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 39fa9c54b2..72e5fb8f90 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -905,7 +905,7 @@ boost::any& Choice::get_value() wxString ret_str = field->GetValue(); // options from right panel - std::vector right_panel_options{ "support", "scale_unit" }; + std::vector right_panel_options{ "support", "pad", "scale_unit" }; for (auto rp_option: right_panel_options) if (m_opt_id == rp_option) return m_value = boost::any(ret_str); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index c0ff5644bf..0377fdbbf9 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -834,6 +834,11 @@ static wxString support_combo_value_for_config(const DynamicPrintConfig &config, _("Everywhere")); } +static wxString pad_combo_value_for_config(const DynamicPrintConfig &config) +{ + return config.opt_bool("pad_enable") ? (config.opt_bool("pad_zero_elevation") ? _("Around object") : _("Below object")) : _("None"); +} + void Tab::on_value_change(const std::string& opt_key, const boost::any& value) { if (wxGetApp().plater() == nullptr) { @@ -853,6 +858,9 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) (opt_key == "supports_enable" || opt_key == "support_buildplate_only")) og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); + if (! is_fff && (opt_key == "pad_enable" || opt_key == "pad_zero_elevation")) + og_freq_chng_params->set_value("pad", pad_combo_value_for_config(*m_config)); + if (opt_key == "brim_width") { bool val = m_config->opt_float("brim_width") > 0.0 ? true : false; @@ -987,6 +995,8 @@ void Tab::update_frequently_changed_parameters() if (!og_freq_chng_params) return; og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); + if (! is_fff) + og_freq_chng_params->set_value("pad", pad_combo_value_for_config(*m_config)); const std::string updated_value_key = is_fff ? "fill_density" : "pad_enable"; From 0fb81e6cda1b3cec6da939685c3dd8b6b6b3adfa Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 8 Aug 2019 10:00:11 +0200 Subject: [PATCH 530/627] Let the alpha build store its profile into PrusaSlicer-alpha directory. --- src/slic3r/GUI/GUI_App.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index dd107550ec..066dab90fd 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -173,7 +173,8 @@ bool GUI_App::on_init_inner() wxCHECK_MSG(wxDirExists(resources_dir), false, wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); - SetAppName(SLIC3R_APP_KEY); + //SetAppName(SLIC3R_APP_KEY); + SetAppName(SLIC3R_APP_KEY "-alpha"); SetAppDisplayName(SLIC3R_APP_NAME); // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. From 07480edc50e2eefbee85fe2c449cee5ec22d19df Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Sat, 22 Jun 2019 17:38:14 +0200 Subject: [PATCH 531/627] Show/hide the legend using a new checkbox in preview --- src/slic3r/GUI/GUI_Preview.cpp | 16 ++++++++++++++++ src/slic3r/GUI/GUI_Preview.hpp | 2 ++ 2 files changed, 18 insertions(+) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 36354ab240..c26c291879 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -176,6 +176,7 @@ Preview::Preview( , m_checkbox_retractions(nullptr) , m_checkbox_unretractions(nullptr) , m_checkbox_shells(nullptr) + , m_checkbox_legend(nullptr) , m_config(config) , m_process(process) , m_gcode_preview_data(gcode_preview_data) @@ -252,6 +253,9 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_checkbox_unretractions = new wxCheckBox(this, wxID_ANY, _(L("Unretractions"))); m_checkbox_shells = new wxCheckBox(this, wxID_ANY, _(L("Shells"))); + m_checkbox_legend = new wxCheckBox(this, wxID_ANY, _(L("Legend"))); + m_checkbox_legend->SetValue(true); + wxBoxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL); top_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0); top_sizer->Add(m_double_slider_sizer, 0, wxEXPAND, 0); @@ -270,6 +274,8 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view bottom_sizer->Add(m_checkbox_unretractions, 0, wxEXPAND | wxALL, 5); bottom_sizer->AddSpacer(10); bottom_sizer->Add(m_checkbox_shells, 0, wxEXPAND | wxALL, 5); + bottom_sizer->AddSpacer(20); + bottom_sizer->Add(m_checkbox_legend, 0, wxEXPAND | wxALL, 5); wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL); main_sizer->Add(top_sizer, 1, wxALL | wxEXPAND, 0); @@ -442,6 +448,7 @@ void Preview::bind_event_handlers() m_checkbox_retractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); m_checkbox_unretractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this); m_checkbox_shells->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this); + m_checkbox_legend->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_legend, this); } void Preview::unbind_event_handlers() @@ -453,6 +460,7 @@ void Preview::unbind_event_handlers() m_checkbox_retractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this); m_checkbox_unretractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this); m_checkbox_shells->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this); + m_checkbox_legend->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_legend, this); } void Preview::show_hide_ui_elements(const std::string& what) @@ -464,6 +472,7 @@ void Preview::show_hide_ui_elements(const std::string& what) m_checkbox_retractions->Enable(enable); m_checkbox_unretractions->Enable(enable); m_checkbox_shells->Enable(enable); + m_checkbox_legend->Enable(enable); enable = (what != "none"); m_label_view_type->Enable(enable); @@ -476,6 +485,7 @@ void Preview::show_hide_ui_elements(const std::string& what) m_checkbox_retractions->Show(visible); m_checkbox_unretractions->Show(visible); m_checkbox_shells->Show(visible); + m_checkbox_legend->Show(visible); m_label_view_type->Show(visible); m_choice_view_type->Show(visible); } @@ -542,6 +552,12 @@ void Preview::on_checkbox_shells(wxCommandEvent& evt) refresh_print(); } +void Preview::on_checkbox_legend(wxCommandEvent& evt) +{ + m_canvas->enable_legend_texture(m_checkbox_legend->IsChecked()); + m_canvas_widget->Refresh(); +} + void Preview::update_view_type() { const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config; diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index e86d0e4306..b626bd7bde 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -80,6 +80,7 @@ class Preview : public wxPanel wxCheckBox* m_checkbox_retractions; wxCheckBox* m_checkbox_unretractions; wxCheckBox* m_checkbox_shells; + wxCheckBox* m_checkbox_legend; DynamicPrintConfig* m_config; BackgroundSlicingProcess* m_process; @@ -147,6 +148,7 @@ private: void on_checkbox_retractions(wxCommandEvent& evt); void on_checkbox_unretractions(wxCommandEvent& evt); void on_checkbox_shells(wxCommandEvent& evt); + void on_checkbox_legend(wxCommandEvent& evt); // Create/Update/Reset double slider on 3dPreview void create_double_slider(); From 489fba326ee6732955312e31633ccb8e47872d95 Mon Sep 17 00:00:00 2001 From: Yuri D'Elia Date: Sat, 22 Jun 2019 17:39:09 +0200 Subject: [PATCH 532/627] Add 'L' as a legend toggle shortcut in preview --- src/slic3r/GUI/GUI_Preview.cpp | 5 +++++ src/slic3r/GUI/KBShortcutsDialog.cpp | 1 + 2 files changed, 6 insertions(+) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index c26c291879..64218d08f6 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -718,6 +718,11 @@ void Preview::update_double_slider_from_canvas(wxKeyEvent& event) m_slider->SetHigherValue(new_pos); if (event.ShiftDown() || m_slider->is_one_layer()) m_slider->SetLowerValue(m_slider->GetHigherValue()); } + else if (key == 'L') { + m_checkbox_legend->SetValue(!m_checkbox_legend->GetValue()); + auto evt = wxCommandEvent(); + on_checkbox_legend(evt); + } else if (key == 'S') m_slider->ChangeOneLayerLock(); else diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 38d02a98a5..08d46444c4 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -178,6 +178,7 @@ void KBShortcutsDialog::fill_shortcuts() preview_shortcuts.push_back(Shortcut(L("Arrow Down"), L("Lower Layer"))); preview_shortcuts.push_back(Shortcut("U", L("Upper Layer"))); preview_shortcuts.push_back(Shortcut("D", L("Lower Layer"))); + preview_shortcuts.push_back(Shortcut("L", L("Show/Hide (L)egend"))); m_full_shortcuts.push_back(std::make_pair(_(L("Preview Shortcuts")), std::make_pair(preview_shortcuts, szLeft))); From f474978db5519d501c0ad91439b56f5acedf1b99 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 8 Aug 2019 12:59:55 +0200 Subject: [PATCH 533/627] Clean up the mess required for the legacy Perl bindings, which are now used for Perl unit / integration tests only. With this commit, the code will be cleaner, but likely the unit tests will not run on Windows, if installed in a localized path. --- src/PrusaSlicer.cpp | 5 +++++ src/libslic3r/utils.cpp | 10 ---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index b1ba30553e..45a3336304 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "unix/fhs.hpp" // Generated by CMake from ../platform/unix/fhs.hpp.in @@ -59,6 +60,9 @@ PrinterTechnology get_printer_technology(const DynamicConfig &config) int CLI::run(int argc, char **argv) { + // Switch boost::filesystem to utf8. + boost::nowide::nowide_filesystem(); + if (! this->setup(argc, argv)) return 1; @@ -499,6 +503,7 @@ int CLI::run(int argc, char **argv) bool CLI::setup(int argc, char **argv) { { + Slic3r::set_logging_level(1); const char *loglevel = boost::nowide::getenv("SLIC3R_LOGLEVEL"); if (loglevel != nullptr) { if (loglevel[0] >= '0' && loglevel[0] <= '9' && loglevel[1] == 0) diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index ea5e3edec9..2e917ea57d 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include @@ -92,15 +91,6 @@ unsigned get_logging_level() } } -// Force set_logging_level(<=error) after loading of the DLL. -// Switch boost::filesystem to utf8. -static struct RunOnInit { - RunOnInit() { - boost::nowide::nowide_filesystem(); - set_logging_level(1); - } -} g_RunOnInit; - void trace(unsigned int level, const char *message) { boost::log::trivial::severity_level severity = level_to_boost(level); From 7e694a8fb887e6946800d7fe55874bd20e88c478 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 8 Aug 2019 14:21:24 +0200 Subject: [PATCH 534/627] Workaround for invalid access inside some character classification table when parsing localized file names by the PlaceholderParser: UTF8 characters were handled as chars, and the negative char values were used as indices into 7bit long tables. --- src/libslic3r/PlaceholderParser.cpp | 39 ++++++++++++++++------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index c3ac22e968..530d849072 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -175,6 +175,11 @@ void PlaceholderParser::apply_env_variables() } namespace spirit = boost::spirit; +// Using an encoding, which accepts unsigned chars. +// Don't use boost::spirit::ascii, as it crashes internally due to indexing with negative char values for UTF8 characters into some 7bit character classification tables. +//namespace spirit_encoding = boost::spirit::ascii; +//FIXME iso8859_1 is just a workaround for the problem above. Replace it with UTF8 support! +namespace spirit_encoding = boost::spirit::iso8859_1; namespace qi = boost::spirit::qi; namespace px = boost::phoenix; @@ -931,7 +936,7 @@ namespace client /////////////////////////////////////////////////////////////////////////// // Inspired by the C grammar rules https://www.lysator.liu.se/c/ANSI-C-grammar-y.html template - struct macro_processor : qi::grammar, spirit::ascii::space_type> + struct macro_processor : qi::grammar, spirit_encoding::space_type> { macro_processor() : macro_processor::base_type(start) { @@ -944,12 +949,12 @@ namespace client qi::lexeme_type lexeme; qi::no_skip_type no_skip; qi::real_parser strict_double; - spirit::ascii::char_type char_; + spirit_encoding::char_type char_; utf8_char_skipper_parser utf8char; spirit::bool_type bool_; spirit::int_type int_; spirit::double_type double_; - spirit::ascii::string_type string; + spirit_encoding::string_type string; spirit::eoi_type eoi; spirit::repository::qi::iter_pos_type iter_pos; auto kw = spirit::repository::qi::distinct(qi::copy(alnum | '_')); @@ -1178,20 +1183,20 @@ namespace client } // Generic expression over expr. - typedef qi::rule(const MyContext*), spirit::ascii::space_type> RuleExpression; + typedef qi::rule(const MyContext*), spirit_encoding::space_type> RuleExpression; // The start of the grammar. - qi::rule, spirit::ascii::space_type> start; + qi::rule, spirit_encoding::space_type> start; // A free-form text. - qi::rule text; + qi::rule text; // A free-form text, possibly empty, possibly containing macro expansions. - qi::rule text_block; + qi::rule text_block; // Statements enclosed in curely braces {} - qi::rule macro; + qi::rule macro; // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. - qi::rule legacy_variable_expansion; + qi::rule legacy_variable_expansion; // Parsed identifier name. - qi::rule(), spirit::ascii::space_type> identifier; + qi::rule(), spirit_encoding::space_type> identifier; // Ternary operator (?:) over logical_or_expression. RuleExpression conditional_expression; // Logical or over logical_and_expressions. @@ -1209,16 +1214,16 @@ namespace client // Number literals, functions, braced expressions, variable references, variable indexing references. RuleExpression unary_expression; // Rule to capture a regular expression enclosed in //. - qi::rule(), spirit::ascii::space_type> regular_expression; + qi::rule(), spirit_encoding::space_type> regular_expression; // Evaluate boolean expression into bool. - qi::rule bool_expr_eval; + qi::rule bool_expr_eval; // Reference of a scalar variable, or reference to a field of a vector variable. - qi::rule(const MyContext*), qi::locals, int>, spirit::ascii::space_type> scalar_variable_reference; + qi::rule(const MyContext*), qi::locals, int>, spirit_encoding::space_type> scalar_variable_reference; // Rule to translate an identifier to a ConfigOption, or to fail. - qi::rule(const MyContext*), spirit::ascii::space_type> variable_reference; + qi::rule(const MyContext*), spirit_encoding::space_type> variable_reference; - qi::rule, spirit::ascii::space_type> if_else_output; -// qi::rule, bool, std::string>, spirit::ascii::space_type> switch_output; + qi::rule, spirit_encoding::space_type> if_else_output; +// qi::rule, bool, std::string>, spirit_encoding::space_type> switch_output; qi::symbols keywords; }; @@ -1230,7 +1235,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co typedef client::macro_processor macro_processor; // Our whitespace skipper. - spirit::ascii::space_type space; + spirit_encoding::space_type space; // Our grammar, statically allocated inside the method, meaning it will be allocated the first time // PlaceholderParser::process() runs. //FIXME this kind of initialization is not thread safe! From bcfb445d0c70aa6daa332ea3317b1c70c4565873 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 8 Aug 2019 15:24:23 +0200 Subject: [PATCH 535/627] Partial revert of f474978db5519d501c0ad91439b56f5acedf1b99 to fix Perl driven unit tests. --- src/libslic3r/utils.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 2e917ea57d..8fcd611acc 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -91,6 +91,15 @@ unsigned get_logging_level() } } +// Force set_logging_level(<=error) after loading of the DLL. +// This is currently only needed if libslic3r is loaded as a shared library into Perl interpreter +// to perform unit and integration tests. +static struct RunOnInit { + RunOnInit() { + set_logging_level(1); + } +} g_RunOnInit; + void trace(unsigned int level, const char *message) { boost::log::trivial::severity_level severity = level_to_boost(level); From 1b5d561b7cdf9c546e1b9b0355a55928c2de462d Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 8 Aug 2019 15:17:17 +0200 Subject: [PATCH 536/627] Added handling of std::bad_alloc so the user gets more comprehensible error message Call to boost::nowide::nowide_filesystem() was made Windows only --- src/PrusaSlicer.cpp | 15 ++++++++++++++- src/libslic3r/PrintObject.cpp | 4 ++-- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 7 ++++++- src/slic3r/GUI/GUI_App.cpp | 11 ++++++++++- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 45a3336304..9bef4b4192 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -60,8 +60,21 @@ PrinterTechnology get_printer_technology(const DynamicConfig &config) int CLI::run(int argc, char **argv) { +#ifdef _WIN32 // Switch boost::filesystem to utf8. - boost::nowide::nowide_filesystem(); + try { + boost::nowide::nowide_filesystem(); + } catch (const std::runtime_error& ex) { + std::string caption = std::string(SLIC3R_APP_NAME) + " Error"; + std::string text = std::string("An error occured while setting up locale.\n") + SLIC3R_APP_NAME + " will now terminate.\n\n" + ex.what(); + #ifdef SLIC3R_GUI + if (m_actions.empty()) + MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR); + #endif + boost::nowide::cerr << text.c_str() << std::endl; + return 1; + } +#endif if (! this->setup(argc, argv)) return 1; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 37cf0cccc7..e19bceeb76 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -161,7 +161,7 @@ void PrintObject::make_perimeters() const PrintRegion ®ion = *m_print->regions()[region_id]; if (! region.config().extra_perimeters || region.config().perimeters == 0 || region.config().fill_density == 0 || this->layer_count() < 2) continue; - + BOOST_LOG_TRIVIAL(debug) << "Generating extra perimeters for region " << region_id << " in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size() - 1), @@ -2379,7 +2379,7 @@ void PrintObject::discover_horizontal_shells() if (new_internal_solid.empty()) { // No internal solid needed on this layer. In order to decide whether to continue // searching on the next neighbor (thus enforcing the configured number of solid - // layers, use different strategies according to configured infill density: + // layers, use different strategies according to configured infill density: if (region_config.fill_density.value == 0) { // If user expects the object to be void (for example a hollow sloping vase), // don't continue the search. In this case, we only generate the external solid diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index b77a272e2e..94ddb1e5ef 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -151,7 +151,12 @@ void BackgroundSlicingProcess::thread_proc() } catch (CanceledException & /* ex */) { // Canceled, this is all right. assert(m_print->canceled()); - } catch (std::exception &ex) { + } catch (const std::bad_alloc& ex) { + wxString errmsg = wxString::Format(_(L("%s has encountered an error. It was likely caused by running out of memory. " + "If you are sure you have enough RAM on your system, this may also be a bug and we would " + "be glad if you reported it.")), SLIC3R_APP_NAME); + error = errmsg.ToStdString() + "\n\n" + std::string(ex.what()); + } catch (std::exception &ex) { error = ex.what(); } catch (...) { error = "Unknown C++ exception."; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 066dab90fd..9b4522356a 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -126,7 +126,16 @@ static void generic_exception_handle() try { throw; - } catch (const std::exception &ex) { + } catch (const std::bad_alloc& ex) { + // bad_alloc in main thread is most likely fatal. Report immediately to the user (wxLogError would be delayed) + // and terminate the app so it is at least certain to happen now. + wxString errmsg = wxString::Format(_(L("%s has encountered an error. It was likely caused by running out of memory. " + "If you are sure you have enough RAM on your system, this may also be a bug and we would " + "be glad if you reported it.\n\nThe application will now terminate.")), SLIC3R_APP_NAME); + wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _(L("Fatal error")), wxOK | wxICON_ERROR); + BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what(); + std::terminate(); + } catch (const std::exception& ex) { wxLogError("Internal error: %s", ex.what()); BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); throw; From 39b07e7b9452d3f06a551e7b89b58768da16f96a Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 8 Aug 2019 18:26:41 +0200 Subject: [PATCH 537/627] Partial revert of 07a3072622147cf70e3fed9075e5eb713f41055f It fixes an issue where the objects out of print bed are shown in the print path preview in SLA mode. --- src/slic3r/GUI/GLCanvas3D.cpp | 55 +++++++++++++++++++++++++++++++++-- src/slic3r/GUI/GLCanvas3D.hpp | 2 ++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index b91b8c36a9..651240faba 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2126,8 +2126,7 @@ void GLCanvas3D::load_sla_preview() if ((m_canvas != nullptr) && (print != nullptr)) { _set_current(); - // Reload the SLA support structures into GLVolumes. - this->reload_scene(true, true); + _load_sla_shells(); _update_sla_shells_outside_state(); _show_warning_texture_if_needed(WarningTexture::SlaSupportsOutside); } @@ -5426,6 +5425,58 @@ void GLCanvas3D::_load_fff_shells() } } +// While it looks like we can call +// this->reload_scene(true, true) +// the two functions are quite different: +// 1) This function only loads objects, for which the step slaposSliceSupports already finished. Therefore objects outside of the print bed never load. +// 2) This function loads object mesh with the relative scaling correction (the "relative_correction" parameter) was applied, +// therefore the mesh may be slightly larger or smaller than the mesh shown in the 3D scene. +void GLCanvas3D::_load_sla_shells() +{ + const SLAPrint* print = this->sla_print(); + if (print->objects().empty()) + // nothing to render, return + return; + + auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance, + const TriangleMesh &mesh, const float color[4], bool outside_printer_detection_enabled) { + m_volumes.volumes.emplace_back(new GLVolume(color)); + GLVolume& v = *m_volumes.volumes.back(); + v.indexed_vertex_array.load_mesh(mesh); + v.indexed_vertex_array.finalize_geometry(this->m_initialized); + v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; + v.composite_id.volume_id = volume_id; + v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0)); + v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation)); + v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.); + v.set_convex_hull(mesh.convex_hull_3d()); + }; + + // adds objects' volumes + for (const SLAPrintObject* obj : print->objects()) + if (obj->is_step_done(slaposSliceSupports)) { + unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); + for (const SLAPrintObject::Instance& instance : obj->instances()) { + add_volume(*obj, 0, instance, obj->transformed_mesh(), GLVolume::MODEL_COLOR[0], true); + // Set the extruder_id and volume_id to achieve the same color as in the 3D scene when + // through the update_volumes_colors_by_extruder() call. + m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); + if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree)) + add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true); + if (obj->is_step_done(slaposBasePool) && obj->has_mesh(slaposBasePool)) + add_volume(*obj, -int(slaposBasePool), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); + } + double shift_z = obj->get_current_elevation(); + for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { + GLVolume& v = *m_volumes.volumes[i]; + // apply shift z + v.set_sla_shift_z(shift_z); + } + } + + update_volumes_colors_by_extruder(); +} + void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data) { unsigned int size = (unsigned int)m_gcode_preview_volume_index.first_volumes.size(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 1738d77426..c1fc5a948b 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -722,6 +722,8 @@ private: void _load_gcode_unretractions(const GCodePreviewData& preview_data); // generates objects and wipe tower geometry void _load_fff_shells(); + // Load SLA objects and support structures for objects, for which the slaposSliceSupports step has been finished. + void _load_sla_shells(); // sets gcode geometry visibility according to user selection void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data); void _update_toolpath_volumes_outside_state(); From 52702769d4a929cfab869d87db54005bc9e46131 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 8 Aug 2019 19:10:22 +0200 Subject: [PATCH 538/627] Hotfix for crash with support disabled and pad enabled. --- src/libslic3r/SLA/SLASupportTree.cpp | 358 ++++++++++++++------------- src/libslic3r/SLAPrint.cpp | 2 +- 2 files changed, 186 insertions(+), 174 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 6f4ac6c21a..8f4998ce1b 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -580,13 +580,13 @@ struct CompactBridge { double fa = 2*PI/steps; auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); for(auto& p : upperball.points) p += startp; - + if(endball) { auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); for(auto& p : lowerball.points) p += endp; mesh.merge(lowerball); } - + mesh.merge(upperball); } }; @@ -604,15 +604,15 @@ struct Pad { double ground_level, const PoolConfig& pcfg) : cfg(pcfg), - zlevel(ground_level + + zlevel(ground_level + sla::get_pad_fullheight(pcfg) - sla::get_pad_elevation(pcfg)) { Polygons basep; auto &thr = cfg.throw_on_cancel; - + thr(); - + // Get a sample for the pad from the support mesh { ExPolygons platetmp; @@ -626,20 +626,20 @@ struct Pad { for (const ExPolygon &bp : platetmp) basep.emplace_back(std::move(bp.contour)); } - + if(pcfg.embed_object) { - + // If the zero elevation mode is ON, we need to process the model // base silhouette. Create the offsetted version and punch the // breaksticks across its perimeter. - + ExPolygons modelbase_offs = modelbase; - + if (pcfg.embed_object.object_gap_mm > 0.0) modelbase_offs = offset_ex(modelbase_offs, float(scaled(pcfg.embed_object.object_gap_mm))); - + // Create a spatial index of the support silhouette polygons. // This will be used to check for intersections with the model // silhouette polygons. If there is no intersection, then a certain @@ -653,35 +653,35 @@ struct Pad { bindex.insert(bb, idx++); } } - + ExPolygons concaveh = offset_ex( concave_hull(basep, pcfg.max_merge_distance_mm, thr), scaled(pcfg.min_wall_thickness_mm)); - + // Punching the breaksticks across the offsetted polygon perimeters auto pad_stickholes = reserve_vector(modelbase.size()); for(auto& poly : modelbase_offs) { - + bool overlap = false; for (const ExPolygon &p : concaveh) overlap = overlap || poly.overlaps(p); - + auto bb = poly.contour.bounding_box(); bb.offset(scaled(pcfg.min_wall_thickness_mm)); - + std::vector qres = bindex.query(bb, BoxIndex::qtIntersects); - + if (!qres.empty() || overlap) { - + // The model silhouette polygon 'poly' HAS an intersection // with the support silhouettes. Include this polygon // in the pad holes with the breaksticks and merge the // original (offsetted) version with the rest of the pad // base plate. - + basep.emplace_back(poly.contour); - + // The holes of 'poly' will become positive parts of the // pad, so they has to be checked for intersections as well // and erased if there is no intersection with the supports @@ -693,7 +693,7 @@ struct Pad { else ++it; } - + // Punch the breaksticks sla::breakstick_holes( poly, @@ -701,11 +701,11 @@ struct Pad { pcfg.embed_object.stick_stride_mm, pcfg.embed_object.stick_width_mm, pcfg.embed_object.stick_penetration_mm); - + pad_stickholes.emplace_back(poly); } } - + create_base_pool(basep, tmesh, pad_stickholes, cfg); } else { for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); @@ -775,78 +775,78 @@ class SLASupportTree::Impl { // For heads it is beneficial to use the same IDs as for the support points. std::vector m_heads; std::vector m_head_indices; - + std::vector m_pillars; std::vector m_junctions; std::vector m_bridges; std::vector m_compact_bridges; Controller m_ctl; - + Pad m_pad; - + using Mutex = ccr::Mutex; - + mutable Mutex m_mutex; mutable TriangleMesh meshcache; mutable bool meshcache_valid = false; mutable double model_height = 0; // the full height of the model - + public: double ground_level = 0; - + Impl() = default; inline Impl(const Controller& ctl): m_ctl(ctl) {} - + const Controller& ctl() const { return m_ctl; } - + template Head& add_head(unsigned id, Args&&... args) { std::lock_guard lk(m_mutex); m_heads.emplace_back(std::forward(args)...); m_heads.back().id = id; - + if (id >= m_head_indices.size()) m_head_indices.resize(id + 1); m_head_indices[id] = m_heads.size() - 1; - + meshcache_valid = false; return m_heads.back(); } - + template Pillar& add_pillar(unsigned headid, Args&&... args) { std::lock_guard lk(m_mutex); - + assert(headid < m_head_indices.size()); Head &head = m_heads[m_head_indices[headid]]; - + m_pillars.emplace_back(head, std::forward(args)...); Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); head.pillar_id = pillar.id; pillar.start_junction_id = head.id; pillar.starts_from_head = true; - + meshcache_valid = false; return m_pillars.back(); } - + void increment_bridges(const Pillar& pillar) { std::lock_guard lk(m_mutex); assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); - - if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) + + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) m_pillars[size_t(pillar.id)].bridges++; } - + void increment_links(const Pillar& pillar) { std::lock_guard lk(m_mutex); assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); - + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) - m_pillars[size_t(pillar.id)].links++; + m_pillars[size_t(pillar.id)].links++; } - + template Pillar& add_pillar(Args&&...args) { std::lock_guard lk(m_mutex); @@ -857,30 +857,30 @@ public: meshcache_valid = false; return m_pillars.back(); } - + const Head& pillar_head(long pillar_id) const { std::lock_guard lk(m_mutex); assert(pillar_id >= 0 && pillar_id < long(m_pillars.size())); - + const Pillar& p = m_pillars[size_t(pillar_id)]; assert(p.starts_from_head && p.start_junction_id >= 0); assert(size_t(p.start_junction_id) < m_head_indices.size()); - + return m_heads[m_head_indices[p.start_junction_id]]; } - + const Pillar& head_pillar(unsigned headid) const { std::lock_guard lk(m_mutex); assert(headid < m_head_indices.size()); - + const Head& h = m_heads[m_head_indices[headid]]; assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); - + return m_pillars[size_t(h.pillar_id)]; } - + template const Junction& add_junction(Args&&... args) { std::lock_guard lk(m_mutex); @@ -889,7 +889,7 @@ public: meshcache_valid = false; return m_junctions.back(); } - + template const Bridge& add_bridge(Args&&... args) { std::lock_guard lk(m_mutex); @@ -898,7 +898,7 @@ public: meshcache_valid = false; return m_bridges.back(); } - + template const CompactBridge& add_compact_bridge(Args&&...args) { std::lock_guard lk(m_mutex); @@ -907,30 +907,30 @@ public: meshcache_valid = false; return m_compact_bridges.back(); } - + Head &head(unsigned id) { std::lock_guard lk(m_mutex); assert(id < m_head_indices.size()); - + meshcache_valid = false; return m_heads[m_head_indices[id]]; } - + inline size_t pillarcount() const { std::lock_guard lk(m_mutex); return m_pillars.size(); } - + template inline IntegerOnly pillar(T id) const { std::lock_guard lk(m_mutex); assert(id >= 0 && size_t(id) < m_pillars.size() && size_t(id) < std::numeric_limits::max()); - + return m_pillars[size_t(id)]; } - + const Pad &create_pad(const TriangleMesh &object_supports, const ExPolygons & modelbase, const PoolConfig & cfg) @@ -938,86 +938,86 @@ public: m_pad = Pad(object_supports, modelbase, ground_level, cfg); return m_pad; } - + void remove_pad() { m_pad = Pad(); } - + const Pad& pad() const { return m_pad; } - + // WITHOUT THE PAD!!! const TriangleMesh &merged_mesh() const { if (meshcache_valid) return meshcache; - + Contour3D merged; - + for (auto &head : m_heads) { if (m_ctl.stopcondition()) break; if (head.is_valid()) merged.merge(head.mesh); } - + for (auto &stick : m_pillars) { if (m_ctl.stopcondition()) break; merged.merge(stick.mesh); merged.merge(stick.base); } - + for (auto &j : m_junctions) { if (m_ctl.stopcondition()) break; merged.merge(j.mesh); } - + for (auto &cb : m_compact_bridges) { if (m_ctl.stopcondition()) break; merged.merge(cb.mesh); } - + for (auto &bs : m_bridges) { if (m_ctl.stopcondition()) break; merged.merge(bs.mesh); } - + if (m_ctl.stopcondition()) { // In case of failure we have to return an empty mesh meshcache = TriangleMesh(); return meshcache; } - + meshcache = mesh(merged); - + // The mesh will be passed by const-pointer to TriangleMeshSlicer, // which will need this. if (!meshcache.empty()) meshcache.require_shared_vertices(); - + BoundingBoxf3 &&bb = meshcache.bounding_box(); model_height = bb.max(Z) - bb.min(Z); - + meshcache_valid = true; return meshcache; } - + // WITH THE PAD double full_height() const { if (merged_mesh().empty() && !pad().empty()) return get_pad_fullheight(pad().cfg); - + double h = mesh_height(); if (!pad().empty()) h += sla::get_pad_elevation(pad().cfg); return h; } - + // WITHOUT THE PAD!!! double mesh_height() const { if (!meshcache_valid) merged_mesh(); return model_height; } - + // Intended to be called after the generation is fully complete void merge_and_cleanup() { merged_mesh(); // in case the mesh is not generated, it should be... - + // Doing clear() does not garantee to release the memory. m_heads = {}; m_head_indices = {}; @@ -1296,7 +1296,7 @@ class SLASupportTree::Algorithm { // Hit results std::array hits; - + ccr_par::enumerate(phis.begin(), phis.end(), [&m, a, b, sd, dir, r, s, ins_check, &hits] (double phi, size_t i) @@ -1431,11 +1431,11 @@ class SLASupportTree::Algorithm { // For connecting a head to a nearby pillar. bool connect_to_nearpillar(const Head& head, long nearpillar_id) { - + auto nearpillar = [this, nearpillar_id]() { return m_result.pillar(nearpillar_id); }; - + if (nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; Vec3d headjp = head.junction_point(); @@ -1539,7 +1539,7 @@ class SLASupportTree::Algorithm { return nearest_id >= 0; } - + // This is a proxy function for pillar creation which will mind the gap // between the pad and the model bottom in zero elevation mode. void create_ground_pillar(const Vec3d &jp, @@ -1594,7 +1594,7 @@ class SLASupportTree::Algorithm { endp = jp + SQR2 * mv * dir; Vec3d pgnd = {endp(X), endp(Y), gndlvl}; can_add_base = result.score > min_dist; - + double gnd_offs = m_mesh.ground_level_offset(); auto abort_in_shame = [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]() @@ -1612,7 +1612,7 @@ class SLASupportTree::Algorithm { if (endp(Z) < gndlvl) endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off else { - + auto hit = bridge_mesh_intersect(endp, DOWN, radius); if (!std::isinf(hit.distance())) abort_in_shame(); @@ -1636,7 +1636,7 @@ class SLASupportTree::Algorithm { m_result.add_pillar(unsigned(head_id), jp, radius); } } - + if (normal_mode) { Pillar &plr = head_id >= 0 ? m_result.add_pillar(unsigned(head_id), @@ -1648,8 +1648,8 @@ class SLASupportTree::Algorithm { plr.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); pillar_id = plr.id; - } - + } + if(pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.insert(endp, pillar_id); } @@ -1716,52 +1716,52 @@ public: using libnest2d::opt::initvals; using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::StopCriteria; - + ccr::Mutex mutex; auto addfn = [&mutex](PtIndices &container, unsigned val) { std::lock_guard lk(mutex); container.emplace_back(val); }; - + ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), [this, &nmls, addfn](unsigned fidx, size_t i) { m_thr(); - + auto n = nmls.row(i); - + // for all normals we generate the spherical coordinates and // saturate the polar angle to 45 degrees from the bottom then // convert back to standard coordinates to get the new normal. // Then we just create a quaternion from the two normals // (Quaternion::FromTwoVectors) and apply the rotation to the // arrow head. - + double z = n(2); double r = 1.0; // for normalized vector double polar = std::acos(z / r); double azimuth = std::atan2(n(1), n(0)); - + // skip if the tilt is not sane if(polar >= PI - m_cfg.normal_cutoff_angle) { - + // We saturate the polar angle to 3pi/4 polar = std::max(polar, 3*PI / 4); - + // save the head (pinpoint) position Vec3d hp = m_points.row(fidx); - + double w = m_cfg.head_width_mm + m_cfg.head_back_radius_mm + 2*m_cfg.head_front_radius_mm; - + double pin_r = double(m_support_pts[fidx].head_front_radius); - + // Reassemble the now corrected normal auto nn = Vec3d(std::cos(azimuth) * std::sin(polar), std::sin(azimuth) * std::sin(polar), std::cos(polar)).normalized(); - + // check available distance EigenMesh3D::hit_result t = pinhead_mesh_intersect(hp, // touching point @@ -1769,20 +1769,20 @@ public: pin_r, m_cfg.head_back_radius_mm, w); - + if(t.distance() <= w) { - + // Let's try to optimize this angle, there might be a // viable normal that doesn't collide with the model // geometry and its very close to the default. - + StopCriteria stc; stc.max_iterations = m_cfg.optimizer_max_iterations; stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; stc.stop_score = w; // space greater than w is enough GeneticOptimizer solver(stc); solver.seed(0); // we want deterministic behavior - + auto oresult = solver.optimize_max( [this, pin_r, w, hp](double plr, double azm) { @@ -1799,7 +1799,7 @@ public: bound(3*PI/4, PI), // Must not exceed the tilt limit bound(-PI, PI) // azimuth can be a full search ); - + if(oresult.score > w) { polar = std::get<0>(oresult.optimum); azimuth = std::get<1>(oresult.optimum); @@ -1809,10 +1809,10 @@ public: t = oresult.score; } } - + // save the verified and corrected normal m_support_nmls.row(fidx) = nn; - + if (t.distance() > w) { // Check distance from ground, we might have zero elevation. if (hp(Z) + w * nn(Z) < m_result.ground_level) { @@ -1889,7 +1889,7 @@ public: // from each other in the XY plane to not cross their pillar bases // These clusters of support points will join in one pillar, // possibly in their centroid support point. - + auto pointfn = [this](unsigned i) { return m_result.head(i).junction_point(); }; @@ -2178,7 +2178,7 @@ public: m_pillar_index.insert(pillar.endpoint(), pillid); } } - + // Helper function for interconnect_pillars where pairs of already connected // pillars should be checked for not to be processed again. This can be done // in O(log) or even constant time with a set or an unordered set of hash @@ -2187,17 +2187,17 @@ public: template static I pairhash(I a, I b) { using std::ceil; using std::log2; using std::max; using std::min; - + static_assert(std::is_integral::value, "This function works only for integral types."); I g = min(a, b), l = max(a, b); - + auto bits_g = g ? int(ceil(log2(g))) : 0; // Assume the hash will fit into the output variable assert((l ? (ceil(log2(l))) : 0) + bits_g < int(sizeof(I) * CHAR_BIT)); - + return (l << bits_g) + g; } @@ -2217,7 +2217,7 @@ public: double min_height_ratio = 0.5; std::set pairs; - + // A function to connect one pillar with its neighbors. THe number of // neighbors is given in the configuration. This function if called // for every pillar in the pillar index. A pair of pillar will not @@ -2229,7 +2229,7 @@ public: Vec3d qp = el.first; // endpoint of the pillar const Pillar& pillar = m_result.pillar(el.second); // actual pillar - + // Get the max number of neighbors a pillar should connect to unsigned neighbors = m_cfg.pillar_cascade_neighbors; @@ -2255,10 +2255,10 @@ public: // Get unique hash for the given pair (order doesn't matter) auto hashval = pairhash(a, b); - + // Search for the pair amongst the remembered pairs if(pairs.find(hashval) != pairs.end()) continue; - + const Pillar& neighborpillar = m_result.pillar(re.second); // this neighbor is occupied, skip @@ -2283,10 +2283,10 @@ public: if(pillar.links >= neighbors) break; } }; - + // Run the cascade for the pillars in the index m_pillar_index.foreach(cascadefn); - + // We would be done here if we could allow some pillars to not be // connected with any neighbors. But this might leave the support tree // unprintable. @@ -2294,16 +2294,16 @@ public: // The current solution is to insert additional pillars next to these // lonely pillars. One or even two additional pillar might get inserted // depending on the length of the lonely pillar. - + size_t pillarcount = m_result.pillarcount(); - + // Again, go through all pillars, this time in the whole support tree // not just the index. for(size_t pid = 0; pid < pillarcount; pid++) { auto pillar = [this, pid]() { return m_result.pillar(pid); }; - + // Decide how many additional pillars will be needed: - + unsigned needpillars = 0; if (pillar().bridges > m_cfg.max_bridges_on_pillar) needpillars = 3; @@ -2332,7 +2332,7 @@ public: double gnd = m_result.ground_level; double min_dist = m_cfg.pillar_base_safety_distance_mm + m_cfg.base_radius_mm + EPSILON; - + while(!found && alpha < 2*PI) { for (unsigned n = 0; n < needpillars && (!n || canplace[n - 1]); @@ -2343,11 +2343,11 @@ public: s(X) += std::cos(a) * r; s(Y) += std::sin(a) * r; spts[n] = s; - - // Check the path vertically down + + // Check the path vertically down auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); Vec3d gndsp{s(X), s(Y), gnd}; - + // If the path is clear, check for pillar base collisions canplace[n] = std::isinf(hr.distance()) && std::sqrt(m_mesh.squared_distance(gndsp)) > @@ -2365,7 +2365,7 @@ public: newpills.reserve(needpillars); if(found) for(unsigned n = 0; n < needpillars; n++) { - Vec3d s = spts[n]; + Vec3d s = spts[n]; Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); @@ -2447,7 +2447,7 @@ public: m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist)); } } - + void merge_result() { m_result.merge_and_cleanup(); } }; @@ -2457,9 +2457,9 @@ bool SLASupportTree::generate(const std::vector &support_points, const Controller &ctl) { if(support_points.empty()) return false; - + Algorithm alg(cfg, mesh, support_points, *m_impl, ctl.cancelfn); - + // Let's define the individual steps of the processing. We can experiment // later with the ordering and the dependencies between them. enum Steps { @@ -2477,41 +2477,41 @@ bool SLASupportTree::generate(const std::vector &support_points, NUM_STEPS //... }; - + // Collect the algorithm steps into a nice sequence std::array, NUM_STEPS> program = { [] () { // Begin... // Potentially clear up the shared data (not needed for now) }, - + std::bind(&Algorithm::filter, &alg), - + std::bind(&Algorithm::add_pinheads, &alg), - + std::bind(&Algorithm::classify, &alg), - + std::bind(&Algorithm::routing_to_ground, &alg), - + std::bind(&Algorithm::routing_to_model, &alg), - + std::bind(&Algorithm::interconnect_pillars, &alg), - + std::bind(&Algorithm::routing_headless, &alg), - + std::bind(&Algorithm::merge_result, &alg), - + [] () { // Done }, - + [] () { // Abort } }; - + Steps pc = BEGIN; - + if(cfg.ground_facing_only) { program[ROUTING_NONGROUND] = []() { BOOST_LOG_TRIVIAL(info) @@ -2522,7 +2522,7 @@ bool SLASupportTree::generate(const std::vector &support_points, " requested."; }; } - + // Let's define a simple automaton that will run our program. auto progress = [&ctl, &pc] () { static const std::array stepstr { @@ -2538,7 +2538,7 @@ bool SLASupportTree::generate(const std::vector &support_points, "Done", "Abort" }; - + static const std::array stepstate { 0, 10, @@ -2552,9 +2552,9 @@ bool SLASupportTree::generate(const std::vector &support_points, 100, 0 }; - + if(ctl.stopcondition()) pc = ABORT; - + switch(pc) { case BEGIN: pc = FILTER; break; case FILTER: pc = PINHEADS; break; @@ -2569,16 +2569,16 @@ bool SLASupportTree::generate(const std::vector &support_points, case ABORT: break; default: ; } - + ctl.statuscb(stepstate[pc], stepstr[pc]); }; - + // Just here we run the computation... while(pc < DONE) { progress(); program[pc](); } - + return pc == ABORT; } @@ -2597,39 +2597,51 @@ void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { } std::vector SLASupportTree::slice( - const std::vector &heights, float cr) const + const std::vector &grid, float cr) const { const TriangleMesh &sup_mesh = m_impl->merged_mesh(); const TriangleMesh &pad_mesh = get_pad(); - - std::vector sup_slices; - if (!sup_mesh.empty()) { + + using Slices = std::vector; + auto slices = reserve_vector(2); + + if (!sup_mesh.empty()) { + slices.emplace_back(); + TriangleMeshSlicer sup_slicer(&sup_mesh); - sup_slicer.slice(heights, cr, &sup_slices, m_impl->ctl().cancelfn); + sup_slicer.slice(grid, cr, &slices.back(), m_impl->ctl().cancelfn); } - - auto bb = pad_mesh.bounding_box(); - auto maxzit = std::upper_bound(heights.begin(), heights.end(), bb.max.z()); - - auto padgrid = reserve_vector(heights.end() - maxzit); - std::copy(heights.begin(), maxzit, std::back_inserter(padgrid)); - - std::vector pad_slices; - if (!pad_mesh.empty()) { + + if (!pad_mesh.empty()) { + slices.emplace_back(); + + auto bb = pad_mesh.bounding_box(); + auto maxzit = std::upper_bound(grid.begin(), grid.end(), bb.max.z()); + + auto padgrid = reserve_vector(grid.end() - maxzit); + std::copy(grid.begin(), maxzit, std::back_inserter(padgrid)); + TriangleMeshSlicer pad_slicer(&pad_mesh); - pad_slicer.slice(padgrid, cr, &pad_slices, m_impl->ctl().cancelfn); + pad_slicer.slice(padgrid, cr, &slices.back(), m_impl->ctl().cancelfn); } - - size_t len = std::min(heights.size(), pad_slices.size()); - len = std::min(len, sup_slices.size()); - - for (size_t i = 0; i < len; ++i) { - std::copy(pad_slices[i].begin(), pad_slices[i].end(), - std::back_inserter(sup_slices[i])); - pad_slices[i] = {}; + + size_t len = grid.size(); + for (const Slices slv : slices) { len = std::min(len, slv.size()); } + + // Either the support or the pad or both has to be non empty + assert(!slices.empty()); + + Slices &mrg = slices.front(); + + for (auto it = std::next(slices.begin()); it != slices.end(); ++it) { + for (size_t i = 0; i < len; ++i) { + Slices &slv = *it; + std::copy(slv[i].begin(), slv[i].end(), std::back_inserter(mrg[i])); + slv[i] = {}; // clear and delete + } } - - return sup_slices; + + return mrg; } const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase, diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index d885ed4194..893d90bbf6 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -761,7 +761,7 @@ void SLAPrint::process() for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) po.m_slice_index.emplace_back(h, unscaled(h) - lh / 2.f, lh); - // Just get the first record that is form the model: + // Just get the first record that is from the model: auto slindex_it = po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z))); From 0a9c5a94339bd9368c446fcd1a300fb5f54710da Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 9 Aug 2019 09:49:10 +0200 Subject: [PATCH 539/627] Fixed extra "Add_Selection" snapshot on call context menu from scene + Added take_snapshot for toggle_instance_printable_state --- src/slic3r/GUI/GLCanvas3D.cpp | 6 +++++- src/slic3r/GUI/Plater.cpp | 1 - src/slic3r/GUI/Selection.cpp | 9 ++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 651240faba..0d0d4d84b0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2921,7 +2921,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) // disable context menu when the gizmo is open { // forces the selection of the volume - m_selection.add(volume_idx); + /* m_selection.add(volume_idx); // #et_FIXME_if_needed + * To avoid extra "Add-Selection" snapshots, + * call add() with check_for_already_contained=true + * */ + m_selection.add(volume_idx, true, true); m_gizmos.refresh_on_off_state(); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); m_gizmos.update_data(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 166c4d6e55..99f63da8a1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2646,7 +2646,6 @@ void Plater::priv::reset() void Plater::priv::mirror(Axis axis) { - this->take_snapshot(_(L("Mirror"))); view3D->mirror_selection(axis); } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index c2b8c10365..937619a460 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1475,7 +1475,14 @@ void Selection::toggle_instance_printable_state() if ((0 <= instance_idx) && (instance_idx < (int)model_object->instances.size())) { ModelInstance* instance = model_object->instances[instance_idx]; - instance->printable = !instance->printable; + const bool printable = !instance->printable; + + wxString snapshot_text = model_object->instances.size() == 1 ? wxString::Format("%s %s", + printable ? _(L("Set Printable")) : _(L("Set Unprintable")), model_object->name) : + printable ? _(L("Set Printable Instance")) : _(L("Set Unprintable Instance")); + wxGetApp().plater()->take_snapshot(snapshot_text); + + instance->printable = printable; for (GLVolume* volume : *m_volumes) { From 46fa83adac94aefde66f3340fed86f8385dbc435 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 9 Aug 2019 09:58:05 +0200 Subject: [PATCH 540/627] Only validate support_material_extrusion_width with supports or raft enabled. --- src/libslic3r/Print.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 9df122cee5..d8a094b20b 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1310,19 +1310,15 @@ std::string Print::validate() const return L("Layer height can't be greater than nozzle diameter"); // Validate extrusion widths. - for (const char *opt_key : { "extrusion_width", "support_material_extrusion_width" }) { - std::string err_msg; - if (! validate_extrusion_width(object->config(), opt_key, layer_height, err_msg)) - return err_msg; - } - for (const char *opt_key : { "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width" }) { + std::string err_msg; + if (! validate_extrusion_width(object->config(), "extrusion_width", layer_height, err_msg)) + return err_msg; + if ((object->config().support_material || object->config().raft_layers > 0) && ! validate_extrusion_width(object->config(), "support_material_extrusion_width", layer_height, err_msg)) + return err_msg; + for (const char *opt_key : { "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width" }) for (size_t i = 0; i < object->region_volumes.size(); ++ i) - if (! object->region_volumes[i].empty()) { - std::string err_msg; - if (! validate_extrusion_width(this->get_region(i)->config(), opt_key, layer_height, err_msg)) - return err_msg; - } - } + if (! object->region_volumes[i].empty() && ! validate_extrusion_width(this->get_region(i)->config(), opt_key, layer_height, err_msg)) + return err_msg; } } From aa7f98b0200fd9e2c0fa37b0ec7eb17acf43f75f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 9 Aug 2019 13:19:23 +0200 Subject: [PATCH 541/627] Bumped up version to 2.1.0-alpha1 --- version.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.inc b/version.inc index 4e1607bda1..f84354ab81 100644 --- a/version.inc +++ b/version.inc @@ -3,7 +3,7 @@ set(SLIC3R_APP_NAME "PrusaSlicer") set(SLIC3R_APP_KEY "PrusaSlicer") -set(SLIC3R_VERSION "2.1.0-alpha0") +set(SLIC3R_VERSION "2.1.0-alpha1") set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN") set(SLIC3R_RC_VERSION "2,1,0,0") set(SLIC3R_RC_VERSION_DOTS "2.1.0.0") From a49caea6cc49397ecc034582d8f8b902f09c8251 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 9 Aug 2019 17:13:18 +0200 Subject: [PATCH 542/627] Hotfix for crash when empty pad and support mesh is generated. --- src/libslic3r/SLA/SLASupportTree.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 8f4998ce1b..ac3b508316 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -713,7 +713,7 @@ struct Pad { } tmesh.translate(0, 0, float(zlevel)); - tmesh.require_shared_vertices(); + if (!tmesh.empty()) tmesh.require_shared_vertices(); } bool empty() const { return tmesh.facets_count() == 0; } @@ -2626,10 +2626,10 @@ std::vector SLASupportTree::slice( } size_t len = grid.size(); - for (const Slices slv : slices) { len = std::min(len, slv.size()); } + for (const Slices &slv : slices) { len = std::min(len, slv.size()); } // Either the support or the pad or both has to be non empty - assert(!slices.empty()); + if (slices.empty()) return {}; Slices &mrg = slices.front(); From ba15c99c2f536bb9e4368e8fa23aa453d2c0410a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 9 Aug 2019 17:47:35 +0200 Subject: [PATCH 543/627] GTK issue: Fixed layer range editing. Bug description: When try to change min/max Z value, object is unselected and layers "planes" on 3DScene are wrong positioned --- src/slic3r/GUI/GUI_ObjectList.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d860e87244..b3bedaf50d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2636,6 +2636,9 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay ranges[new_range] = config; wxDataViewItem root_item = m_objects_model->GetLayerRootItem(m_objects_model->GetItemById(obj_idx)); + // To avoid update selection after deleting of a selected item (under GTK) + // set m_prevent_list_events to true + m_prevent_list_events = true; m_objects_model->DeleteChildren(root_item); if (root_item.IsOk()) From 08dced3ce7c28e9024abdf2ea732782b966af1d6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 12 Aug 2019 15:21:06 +0200 Subject: [PATCH 544/627] Fix of scaling on Manipulation panel --- src/slic3r/GUI/Field.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 72e5fb8f90..39924e44c5 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -393,6 +393,12 @@ void TextCtrl::set_value(const boost::any& value, bool change_event/* = false*/) else dynamic_cast(window)->SetValue(boost::any_cast(value)); m_disable_change_event = false; + + if (!change_event) { + wxString ret_str = static_cast(window)->GetValue(); + // update m_value to correct work of next value_was_changed() + get_value_by_opt_type(ret_str); + } } void TextCtrl::set_last_meaningful_value() @@ -410,7 +416,7 @@ void TextCtrl::set_na_value() boost::any& TextCtrl::get_value() { wxString ret_str = static_cast(window)->GetValue(); - // modifies ret_string! + // update m_value get_value_by_opt_type(ret_str); return m_value; From c18914022147ad0edb3c12ad1011a158d9c1e9eb Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 13 Aug 2019 11:02:58 +0200 Subject: [PATCH 545/627] Fixed a false positive empty layer report Reported in https://github.com/prusa3d/PrusaSlicer/issues/2752 and was a result of setting support contact z distance to negative value. This lowered the maximum allowed print_z of the next layer, even though previous layer had object layers too, so the penalty from the contact z should not have been applied. Fixed simply by rejecting the negative contact_z. --- src/libslic3r/GCode.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 02fefceec1..b691203c97 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -467,7 +467,10 @@ std::vector GCode::collect_layers_to_print(const PrintObjec : 0.; double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + layer_to_print.layer()->height - + support_contact_z; + + std::max(0., support_contact_z); + // Negative support_contact_z is not taken into account, it can result in false positives in cases + // where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752) + if (layer_to_print.print_z() > maximal_print_z + EPSILON) throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + From 4cd4d2c8b5f8bea59da2a0fbdb096cb6b7391250 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 13 Aug 2019 17:09:42 +0200 Subject: [PATCH 546/627] Fixed wrong page selection for Printer Setting after application run --- src/libslic3r/Config.hpp | 4 +++- src/slic3r/GUI/Tab.cpp | 7 +++++-- src/slic3r/GUI/Tab.hpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index ff55632262..fdf00c2d09 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -91,7 +91,9 @@ enum ConfigOptionType { enum ConfigOptionMode { comSimple = 0, comAdvanced, - comExpert + comExpert, + + comUndef }; enum PrinterTechnology : unsigned char diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 0377fdbbf9..aeb520d09b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -729,10 +729,15 @@ void Tab::update_mode() m_mode_sizer->SetMode(m_mode); update_visibility(); + + update_changed_tree_ui(); } void Tab::update_visibility() { + if (m_mode == comUndef) // if mode isn't set for this moment + m_mode = wxGetApp().get_mode(); + Freeze(); // There is needed Freeze/Thaw to avoid a flashing after Show/Layout for (auto page : m_pages) @@ -741,8 +746,6 @@ void Tab::update_visibility() Layout(); Thaw(); - - update_changed_tree_ui(); } void Tab::msw_rescale() diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 423bc198f1..3aa1835408 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -218,7 +218,7 @@ protected: int m_em_unit; // To avoid actions with no-completed Tab bool m_complited { false }; - ConfigOptionMode m_mode = comSimple; + ConfigOptionMode m_mode = comUndef; public: PresetBundle* m_preset_bundle; From 5cf9bb0282ac8ca38094951fc1ce29d18b4b6dfc Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 13 Aug 2019 18:15:12 +0200 Subject: [PATCH 547/627] Fixed set printable state for a first instance after increasing instance count --- src/slic3r/GUI/wxExtensions.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index dcc47750ef..a4cccfb86f 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -806,7 +806,15 @@ wxDataViewItem ObjectDataViewModel::AddInstanceChild(const wxDataViewItem& paren size_t counter = 0; while (counter < print_indicator.size()) { instance_node = new ObjectDataViewModelNode(inst_root_node, itInstance); - instance_node->set_printable_icon(print_indicator[counter] ? piPrintable : piUnprintable); + + // if InstanceRoot item is just created and start to adding Instances + if (just_created && counter == 0) { + ObjectDataViewModelNode* obj_node = (ObjectDataViewModelNode*)parent_item.GetID(); + // use object's printable state to first instance + instance_node->set_printable_icon(obj_node->IsPrintable()); + } + else + instance_node->set_printable_icon(print_indicator[counter] ? piPrintable : piUnprintable); inst_root_node->Append(instance_node); // notify control From b2fc0cd4278dacb085aa0c3b2d50238ce8661d00 Mon Sep 17 00:00:00 2001 From: Jason Tibbitts Date: Tue, 13 Aug 2019 14:21:41 -0500 Subject: [PATCH 548/627] Fix format-security-related build failure Calling a printf-like function without a format string will cause gcc to emit a warhing and causes a build failure on distros which build everything with -Werror=format-security. Signed-off-by: Jason Tibbitts --- src/slic3r/GUI/ImGuiWrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index f58266a5df..8e4d9eebfa 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -354,7 +354,7 @@ bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool ( ImGui::Selectable(item_text, i < hovered); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip(item_text); + ImGui::SetTooltip("%s", item_text); hovered = i; is_hovered = true; } From 2d1c55475890f6744bf28cfcc838b90ad0661d18 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 14 Aug 2019 17:05:26 +0200 Subject: [PATCH 549/627] Some reverts for commit 4cd4d2c8b5f8bea59da2a0fbdb096cb6b7391250 --- src/libslic3r/Config.hpp | 4 +--- src/slic3r/GUI/Tab.cpp | 3 --- src/slic3r/GUI/Tab.hpp | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index fdf00c2d09..ff55632262 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -91,9 +91,7 @@ enum ConfigOptionType { enum ConfigOptionMode { comSimple = 0, comAdvanced, - comExpert, - - comUndef + comExpert }; enum PrinterTechnology : unsigned char diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index aeb520d09b..a9439d339f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -735,9 +735,6 @@ void Tab::update_mode() void Tab::update_visibility() { - if (m_mode == comUndef) // if mode isn't set for this moment - m_mode = wxGetApp().get_mode(); - Freeze(); // There is needed Freeze/Thaw to avoid a flashing after Show/Layout for (auto page : m_pages) diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 3aa1835408..efefc47c56 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -218,7 +218,7 @@ protected: int m_em_unit; // To avoid actions with no-completed Tab bool m_complited { false }; - ConfigOptionMode m_mode = comUndef; + ConfigOptionMode m_mode = comExpert; // to correct first Tab update_visibility() set mode to Expert public: PresetBundle* m_preset_bundle; From 36d2a38a2f9d29c92e75683b2e228dadd953351b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 14 Aug 2019 17:06:53 +0200 Subject: [PATCH 550/627] Fix of #2745 --- src/slic3r/GUI/Tab.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a9439d339f..a2cf23faf9 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -373,6 +373,8 @@ void Tab::update_labels_colour() // Thaw(); auto cur_item = m_treectrl->GetFirstVisibleItem(); + if (!cur_item || !m_treectrl->IsVisible(cur_item)) + return; while (cur_item) { auto title = m_treectrl->GetItemText(cur_item); for (auto page : m_pages) From c84b1ca34b86e4f35b6caac1a8e51897951f14de Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 15 Aug 2019 01:20:38 +0200 Subject: [PATCH 551/627] Multimaterial initial priming for non-Prusa printers (https://github.com/prusa3d/PrusaSlicer/issues/1121) The initial priming now does not assume anything about bed width and always uses the space it has In case of circular beds it places the priming lines along the diameter Custom beds are not supported (they are treated as circular with no extra checks whether it is sane) Slight refactoring of the WipeTower class (constructor now gets reference to PrintConfig and not the individual values, same with set_extruder). This was legacy from times when the wipe tower was meant to be abstract and independent on the rest) --- src/libslic3r/GCode/WipeTower.cpp | 86 +++++++++++++++++++++++++++++-- src/libslic3r/GCode/WipeTower.hpp | 83 ++++------------------------- src/libslic3r/Print.cpp | 27 +--------- 3 files changed, 94 insertions(+), 102 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index e1b34fdeae..8385f579c8 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -22,6 +22,7 @@ TODO LIST #include #include "Analyzer.hpp" +#include "BoundingBox.hpp" #if defined(__linux) || defined(__GNUC__ ) #include @@ -470,6 +471,83 @@ private: +WipeTower::WipeTower(const PrintConfig& config, const std::vector>& wiping_matrix, size_t initial_tool) : + m_semm(config.single_extruder_multi_material.value), + m_wipe_tower_pos(config.wipe_tower_x, config.wipe_tower_y), + m_wipe_tower_width(config.wipe_tower_width), + m_wipe_tower_rotation_angle(config.wipe_tower_rotation_angle), + m_y_shift(0.f), + m_z_pos(0.f), + m_is_first_layer(false), + m_bridging(config.wipe_tower_bridging), + m_gcode_flavor(config.gcode_flavor), + m_current_tool(initial_tool), + wipe_volumes(wiping_matrix) +{ + // If this is a single extruder MM printer, we will use all the SE-specific config values. + // Otherwise, the defaults will be used to turn off the SE stuff. + if (m_semm) { + m_cooling_tube_retraction = config.cooling_tube_retraction; + m_cooling_tube_length = config.cooling_tube_length; + m_parking_pos_retraction = config.parking_pos_retraction; + m_extra_loading_move = config.extra_loading_move; + m_set_extruder_trimpot = config.high_current_on_filament_swap; + } + // Calculate where the priming lines should be - very naive test not detecting parallelograms or custom shapes + const std::vector& bed_points = config.bed_shape.values; + m_bed_shape = (bed_points.size() == 4 ? RectangularBed : CircularBed); + m_bed_width = BoundingBoxf(bed_points).size().x(); +} + + + +void WipeTower::set_extruder(size_t idx, const PrintConfig& config) +{ + //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector + m_filpar.push_back(FilamentParameters()); + + m_filpar[idx].material = config.filament_type.get_at(idx); + m_filpar[idx].temperature = config.temperature.get_at(idx); + m_filpar[idx].first_layer_temperature = config.first_layer_temperature.get_at(idx); + + // If this is a single extruder MM printer, we will use all the SE-specific config values. + // Otherwise, the defaults will be used to turn off the SE stuff. + if (m_semm) { + m_filpar[idx].loading_speed = config.filament_loading_speed.get_at(idx); + m_filpar[idx].loading_speed_start = config.filament_loading_speed_start.get_at(idx); + m_filpar[idx].unloading_speed = config.filament_unloading_speed.get_at(idx); + m_filpar[idx].unloading_speed_start = config.filament_unloading_speed_start.get_at(idx); + m_filpar[idx].delay = config.filament_toolchange_delay.get_at(idx); + m_filpar[idx].cooling_moves = config.filament_cooling_moves.get_at(idx); + m_filpar[idx].cooling_initial_speed = config.filament_cooling_initial_speed.get_at(idx); + m_filpar[idx].cooling_final_speed = config.filament_cooling_final_speed.get_at(idx); + } + + m_filpar[idx].filament_area = float((M_PI/4.f) * pow(config.filament_diameter.get_at(idx), 2)); // all extruders are assumed to have the same filament diameter at this point + float nozzle_diameter = config.nozzle_diameter.get_at(idx); + m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM + + float max_vol_speed = config.filament_max_volumetric_speed.get_at(idx); + if (max_vol_speed!= 0.f) + m_filpar[idx].max_e_speed = (max_vol_speed / filament_area()); + + m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter + + if (m_semm) { + std::istringstream stream{config.filament_ramming_parameters.get_at(idx)}; + float speed = 0.f; + stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; + m_filpar[idx].ramming_line_width_multiplicator /= 100; + m_filpar[idx].ramming_step_multiplicator /= 100; + while (stream >> speed) + m_filpar[idx].ramming_speed.push_back(speed); + } + + m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later +} + + + // Returns gcode to prime the nozzles at the front edge of the print bed. std::vector WipeTower::prime( // print_z of the first layer. @@ -488,9 +566,11 @@ std::vector WipeTower::prime( // therefore the homing position is shifted inside the bed by 0.2 in the firmware to [0.2, -2.0]. // box_coordinates cleaning_box(xy(0.5f, - 1.5f), m_wipe_tower_width, wipe_area); - const float prime_section_width = std::min(240.f / tools.size(), 60.f); - box_coordinates cleaning_box(Vec2f(5.f, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); - + float prime_section_width = std::min(0.9f * m_bed_width / tools.size(), 60.f); + box_coordinates cleaning_box(Vec2f(0.02f * m_bed_width, 0.01f + m_perimeter_width/2.f), prime_section_width, 100.f); + // In case of a circular bed, place it so it goes across the diameter and hope it will fit + if (m_bed_shape == CircularBed) + cleaning_box.translate(-m_bed_width/2 + m_bed_width * 0.03f, -m_bed_width * 0.12f); std::vector results; diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 3c6b4afca2..fab75c5e60 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -78,83 +78,12 @@ public: // y -- y coordinates of wipe tower in mm ( left bottom corner ) // width -- width of wipe tower in mm ( default 60 mm - leave as it is ) // wipe_area -- space available for one toolchange in mm - WipeTower(bool semm, float x, float y, float width, float rotation_angle, float cooling_tube_retraction, - float cooling_tube_length, float parking_pos_retraction, float extra_loading_move, - float bridging, bool set_extruder_trimpot, GCodeFlavor flavor, - const std::vector>& wiping_matrix, unsigned int initial_tool) : - m_semm(semm), - m_wipe_tower_pos(x, y), - m_wipe_tower_width(width), - m_wipe_tower_rotation_angle(rotation_angle), - m_y_shift(0.f), - m_z_pos(0.f), - m_is_first_layer(false), - m_gcode_flavor(flavor), - m_bridging(bridging), - m_current_tool(initial_tool), - wipe_volumes(wiping_matrix) - { - // If this is a single extruder MM printer, we will use all the SE-specific config values. - // Otherwise, the defaults will be used to turn off the SE stuff. - if (m_semm) { - m_cooling_tube_retraction = cooling_tube_retraction; - m_cooling_tube_length = cooling_tube_length; - m_parking_pos_retraction = parking_pos_retraction; - m_extra_loading_move = extra_loading_move; - m_set_extruder_trimpot = set_extruder_trimpot; - } - } - + WipeTower(const PrintConfig& config, const std::vector>& wiping_matrix, size_t initial_tool); virtual ~WipeTower() {} // Set the extruder properties. - void set_extruder(size_t idx, std::string material, int temp, int first_layer_temp, float loading_speed, float loading_speed_start, - float unloading_speed, float unloading_speed_start, float delay, int cooling_moves, - float cooling_initial_speed, float cooling_final_speed, std::string ramming_parameters, float max_volumetric_speed, - float nozzle_diameter, float filament_diameter) - { - //while (m_filpar.size() < idx+1) // makes sure the required element is in the vector - m_filpar.push_back(FilamentParameters()); - - m_filpar[idx].material = material; - m_filpar[idx].temperature = temp; - m_filpar[idx].first_layer_temperature = first_layer_temp; - - // If this is a single extruder MM printer, we will use all the SE-specific config values. - // Otherwise, the defaults will be used to turn off the SE stuff. - if (m_semm) { - m_filpar[idx].loading_speed = loading_speed; - m_filpar[idx].loading_speed_start = loading_speed_start; - m_filpar[idx].unloading_speed = unloading_speed; - m_filpar[idx].unloading_speed_start = unloading_speed_start; - m_filpar[idx].delay = delay; - m_filpar[idx].cooling_moves = cooling_moves; - m_filpar[idx].cooling_initial_speed = cooling_initial_speed; - m_filpar[idx].cooling_final_speed = cooling_final_speed; - } - - m_filpar[idx].filament_area = float((M_PI/4.f) * pow(filament_diameter, 2)); // all extruders are assumed to have the same filament diameter at this point - m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM - - if (max_volumetric_speed != 0.f) - m_filpar[idx].max_e_speed = (max_volumetric_speed / filament_area()); - - m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter - - if (m_semm) { - std::stringstream stream{ramming_parameters}; - float speed = 0.f; - stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator; - m_filpar[idx].ramming_line_width_multiplicator /= 100; - m_filpar[idx].ramming_step_multiplicator /= 100; - while (stream >> speed) - m_filpar[idx].ramming_speed.push_back(speed); - } - - m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later - } - + void set_extruder(size_t idx, const PrintConfig& config); // Appends into internal structure m_plan containing info about the future wipe tower // to be used before building begins. The entries must be added ordered in z. @@ -263,7 +192,6 @@ private: SHAPE_REVERSED = -1 }; - const bool m_peters_wipe_tower = false; // sparse wipe tower inspired by Peter's post processor - not finished yet const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust const float WT_EPSILON = 1e-3f; @@ -295,6 +223,13 @@ private: bool m_adhesion = true; GCodeFlavor m_gcode_flavor; + // Bed properties + enum { + RectangularBed, + CircularBed + } m_bed_shape; + float m_bed_width; // width of the bed bounding box + float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill. float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index d8a094b20b..0ea07e9a40 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1760,15 +1760,7 @@ void Print::_make_wipe_tower() this->throw_if_canceled(); // Initialize the wipe tower. - WipeTower wipe_tower( - m_config.single_extruder_multi_material.value, - float(m_config.wipe_tower_x.value), float(m_config.wipe_tower_y.value), - float(m_config.wipe_tower_width.value), - float(m_config.wipe_tower_rotation_angle.value), float(m_config.cooling_tube_retraction.value), - float(m_config.cooling_tube_length.value), float(m_config.parking_pos_retraction.value), - float(m_config.extra_loading_move.value), float(m_config.wipe_tower_bridging), - m_config.high_current_on_filament_swap.value, m_config.gcode_flavor, wipe_volumes, - m_wipe_tower_data.tool_ordering.first_extruder()); + WipeTower wipe_tower(m_config, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder()); //wipe_tower.set_retract(); //wipe_tower.set_zhop(); @@ -1777,22 +1769,7 @@ void Print::_make_wipe_tower() for (size_t i = 0; i < number_of_extruders; ++ i) wipe_tower.set_extruder( - i, - m_config.filament_type.get_at(i), - m_config.temperature.get_at(i), - m_config.first_layer_temperature.get_at(i), - (float)m_config.filament_loading_speed.get_at(i), - (float)m_config.filament_loading_speed_start.get_at(i), - (float)m_config.filament_unloading_speed.get_at(i), - (float)m_config.filament_unloading_speed_start.get_at(i), - (float)m_config.filament_toolchange_delay.get_at(i), - m_config.filament_cooling_moves.get_at(i), - (float)m_config.filament_cooling_initial_speed.get_at(i), - (float)m_config.filament_cooling_final_speed.get_at(i), - m_config.filament_ramming_parameters.get_at(i), - (float)m_config.filament_max_volumetric_speed.get_at(i), - (float)m_config.nozzle_diameter.get_at(i), - (float)m_config.filament_diameter.get_at(i)); + i, m_config); m_wipe_tower_data.priming = Slic3r::make_unique>( wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); From 15744f021a0aa84e3386fb8579cc47e43e427eb5 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 15 Aug 2019 10:35:50 +0200 Subject: [PATCH 552/627] Print::validate: added epsilon checks on nozzle and filament diameter validation (https://github.com/prusa3d/PrusaSlicer/issues/2737) Changed messagebox text after changing extruder diameter with single extruder printers so it is more obvious why it shows In case nozzle diameters differ and someone checks 'single_extruder_mm', PrusaSlicer asks whether all diameters should be unified. Answering NO did not undo the SEMM check. It does now. --- src/libslic3r/Print.cpp | 18 ++++++++++-------- src/slic3r/GUI/Tab.cpp | 10 +++++++--- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index d8a094b20b..e376f54c30 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1149,11 +1149,17 @@ std::string Print::validate() const } if (this->has_wipe_tower() && ! m_objects.empty()) { - // make sure all extruders use same diameter filament and have the same nozzle diameter + // Make sure all extruders use same diameter filament and have the same nozzle diameter + // EPSILON comparison is used for nozzles and 10 % tolerance is used for filaments + double first_nozzle_diam = m_config.nozzle_diameter.get_at(extruders().front()); + double first_filament_diam = m_config.filament_diameter.get_at(extruders().front()); for (const auto& extruder_idx : extruders()) { - if (m_config.nozzle_diameter.get_at(extruder_idx) != m_config.nozzle_diameter.get_at(extruders().front()) - || m_config.filament_diameter.get_at(extruder_idx) != m_config.filament_diameter.get_at(extruders().front())) - return L("The wipe tower is only supported if all extruders have the same nozzle diameter and use filaments of the same diameter."); + double nozzle_diam = m_config.nozzle_diameter.get_at(extruder_idx); + double filament_diam = m_config.filament_diameter.get_at(extruder_idx); + if (nozzle_diam - EPSILON > first_nozzle_diam || nozzle_diam + EPSILON < first_nozzle_diam + || std::abs((filament_diam-first_filament_diam)/first_filament_diam) > 0.1) + return L("The wipe tower is only supported if all extruders have the same nozzle diameter " + "and use filaments of the same diameter."); } if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin) @@ -1161,10 +1167,6 @@ std::string Print::validate() const if (! m_config.use_relative_e_distances) return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); - for (size_t i=1; i 1) { bool has_custom_layering = false; std::vector> layer_height_profiles; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a2cf23faf9..d643aa4a86 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2047,14 +2047,17 @@ void TabPrinter::build_fff() "Do you want to change the diameter for all extruders to first extruder nozzle diameter value?")); auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); + DynamicPrintConfig new_conf = *m_config; if (dialog->ShowModal() == wxID_YES) { - DynamicPrintConfig new_conf = *m_config; for (size_t i = 1; i < nozzle_diameters.size(); i++) nozzle_diameters[i] = frst_diam; new_conf.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameters)); - load_config(new_conf); } + else + new_conf.set_key_value("single_extruder_multi_material", new ConfigOptionBool(false)); + + load_config(new_conf); break; } } @@ -2502,7 +2505,8 @@ void TabPrinter::build_unregular_pages() // if value was changed if (fabs(nozzle_diameters[extruder_idx == 0 ? 1 : 0] - new_nd) > EPSILON) { - const wxString msg_text = _(L("Do you want to change the diameter for all extruders?")); + const wxString msg_text = _(L("This is a single extruder multimaterial printer, diameters of all extruders " + "will be set to the new value. Do you want to proceed?")); auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); DynamicPrintConfig new_conf = *m_config; From c8ac46df43f0ae467edc02f7587094f7facfe16f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 15 Aug 2019 12:52:56 +0200 Subject: [PATCH 553/627] Fixed some memory leaks related to heap-allocated wxDialogs Some correctly destroyed dialogs were also converted to stack-allocated --- src/slic3r/GUI/Field.cpp | 4 +- src/slic3r/GUI/MainFrame.cpp | 85 ++++++++++++++---------------------- src/slic3r/GUI/Plater.cpp | 16 +++---- src/slic3r/GUI/Tab.cpp | 76 ++++++++++++++++---------------- 4 files changed, 79 insertions(+), 102 deletions(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 39924e44c5..3e3026a4cd 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -206,8 +206,8 @@ void Field::get_value_by_opt_type(wxString& str) const wxString msg_text = wxString::Format(_(L("Do you mean %s%% instead of %s %s?\n" "Select YES if you want to change this value to %s%%, \n" "or NO if you are sure that %s %s is a correct value.")), stVal, stVal, sidetext, stVal, stVal, sidetext); - auto dialog = new wxMessageDialog(m_parent, msg_text, _(L("Parameter validation")), wxICON_WARNING | wxYES | wxNO); - if (dialog->ShowModal() == wxID_YES) { + wxMessageDialog dialog(m_parent, msg_text, _(L("Parameter validation")), wxICON_WARNING | wxYES | wxNO); + if (dialog.ShowModal() == wxID_YES) { set_value(wxString::Format("%s%%", stVal), false/*true*/); str += "%%"; } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b0945aea83..f10a106c18 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -729,29 +729,26 @@ void MainFrame::quick_slice(const int qs) // select input file if (!(qs & qsReslice)) { - auto dlg = new wxFileDialog(this, _(L("Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):")), + wxFileDialog dlg(this, _(L("Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):")), wxGetApp().app_config->get_last_dir(), "", file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dlg->ShowModal() != wxID_OK) { - dlg->Destroy(); + if (dlg.ShowModal() != wxID_OK) return; - } - input_file = dlg->GetPath(); - dlg->Destroy(); + input_file = dlg.GetPath(); if (!(qs & qsExportSVG)) m_qs_last_input_file = input_file; } else { if (m_qs_last_input_file.IsEmpty()) { - auto dlg = new wxMessageDialog(this, _(L("No previously sliced file.")), + wxMessageDialog dlg(this, _(L("No previously sliced file.")), _(L("Error")), wxICON_ERROR | wxOK); - dlg->ShowModal(); + dlg.ShowModal(); return; } if (std::ifstream(m_qs_last_input_file.ToUTF8().data())) { - auto dlg = new wxMessageDialog(this, _(L("Previously sliced file ("))+m_qs_last_input_file+_(L(") not found.")), + wxMessageDialog dlg(this, _(L("Previously sliced file ("))+m_qs_last_input_file+_(L(") not found.")), _(L("File Not Found")), wxICON_ERROR | wxOK); - dlg->ShowModal(); + dlg.ShowModal(); return; } input_file = m_qs_last_input_file; @@ -785,30 +782,24 @@ void MainFrame::quick_slice(const int qs) } else if (qs & qsSaveAs) { // The following line may die if the output_filename_format template substitution fails. - auto dlg = new wxFileDialog(this, wxString::Format(_(L("Save %s file as:")) , qs & qsExportSVG ? _(L("SVG")) : _(L("G-code")) ), + wxFileDialog dlg(this, wxString::Format(_(L("Save %s file as:")) , qs & qsExportSVG ? _(L("SVG")) : _(L("G-code")) ), wxGetApp().app_config->get_last_output_dir(get_dir_name(output_file)), get_base_name(input_file), qs & qsExportSVG ? file_wildcards(FT_SVG) : file_wildcards(FT_GCODE), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if (dlg->ShowModal() != wxID_OK) { - dlg->Destroy(); + if (dlg.ShowModal() != wxID_OK) return; - } - output_file = dlg->GetPath(); - dlg->Destroy(); + output_file = dlg.GetPath(); if (!(qs & qsExportSVG)) m_qs_last_output_file = output_file; wxGetApp().app_config->update_last_output_dir(get_dir_name(output_file)); } else if (qs & qsExportPNG) { - auto dlg = new wxFileDialog(this, _(L("Save zip file as:")), + wxFileDialog dlg(this, _(L("Save zip file as:")), wxGetApp().app_config->get_last_output_dir(get_dir_name(output_file)), get_base_name(output_file), "*.sl1", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if (dlg->ShowModal() != wxID_OK) { - dlg->Destroy(); + if (dlg.ShowModal() != wxID_OK) return; - } - output_file = dlg->GetPath(); - dlg->Destroy(); + output_file = dlg.GetPath(); } // show processbar dialog @@ -854,28 +845,22 @@ void MainFrame::repair_stl() { wxString input_file; { - auto dlg = new wxFileDialog(this, _(L("Select the STL file to repair:")), + wxFileDialog dlg(this, _(L("Select the STL file to repair:")), wxGetApp().app_config->get_last_dir(), "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dlg->ShowModal() != wxID_OK) { - dlg->Destroy(); + if (dlg.ShowModal() != wxID_OK) return; - } - input_file = dlg->GetPath(); - dlg->Destroy(); + input_file = dlg.GetPath(); } wxString output_file = input_file; { - auto dlg = new wxFileDialog( this, L("Save OBJ file (less prone to coordinate errors than STL) as:"), + wxFileDialog dlg( this, L("Save OBJ file (less prone to coordinate errors than STL) as:"), get_dir_name(output_file), get_base_name(output_file, ".obj"), file_wildcards(FT_OBJ), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if (dlg->ShowModal() != wxID_OK) { - dlg->Destroy(); + if (dlg.ShowModal() != wxID_OK) return; - } - output_file = dlg->GetPath(); - dlg->Destroy(); + output_file = dlg.GetPath(); } auto tmesh = new Slic3r::TriangleMesh(); @@ -896,14 +881,13 @@ void MainFrame::export_config() return; } // Ask user for the file name for the config file. - auto dlg = new wxFileDialog(this, _(L("Save configuration as:")), + wxFileDialog dlg(this, _(L("Save configuration as:")), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), !m_last_config.IsEmpty() ? get_base_name(m_last_config) : "config.ini", file_wildcards(FT_INI), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); wxString file; - if (dlg->ShowModal() == wxID_OK) - file = dlg->GetPath(); - dlg->Destroy(); + if (dlg.ShowModal() == wxID_OK) + file = dlg.GetPath(); if (!file.IsEmpty()) { wxGetApp().app_config->update_config_dir(get_dir_name(file)); m_last_config = file; @@ -916,13 +900,12 @@ void MainFrame::load_config_file() { if (!wxGetApp().check_unsaved_changes()) return; - auto dlg = new wxFileDialog(this, _(L("Select configuration to load:")), + wxFileDialog dlg(this, _(L("Select configuration to load:")), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), "config.ini", "INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g", wxFD_OPEN | wxFD_FILE_MUST_EXIST); wxString file; - if (dlg->ShowModal() == wxID_OK) - file = dlg->GetPath(); - dlg->Destroy(); + if (dlg.ShowModal() == wxID_OK) + file = dlg.GetPath(); if (! file.IsEmpty() && this->load_config_file(file.ToUTF8().data())) { wxGetApp().app_config->update_config_dir(get_dir_name(file)); m_last_config = file; @@ -953,14 +936,13 @@ void MainFrame::export_configbundle() return; } // Ask user for a file name. - auto dlg = new wxFileDialog(this, _(L("Save presets bundle as:")), + wxFileDialog dlg(this, _(L("Save presets bundle as:")), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), SLIC3R_APP_KEY "_config_bundle.ini", file_wildcards(FT_INI), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); wxString file; - if (dlg->ShowModal() == wxID_OK) - file = dlg->GetPath(); - dlg->Destroy(); + if (dlg.ShowModal() == wxID_OK) + file = dlg.GetPath(); if (!file.IsEmpty()) { // Export the config bundle. wxGetApp().app_config->update_config_dir(get_dir_name(file)); @@ -980,15 +962,12 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re if (!wxGetApp().check_unsaved_changes()) return; if (file.IsEmpty()) { - auto dlg = new wxFileDialog(this, _(L("Select configuration to load:")), + wxFileDialog dlg(this, _(L("Select configuration to load:")), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), "config.ini", file_wildcards(FT_INI), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dlg->ShowModal() != wxID_OK) { - dlg->Destroy(); - return; - } - file = dlg->GetPath(); - dlg->Destroy(); + if (dlg.ShowModal() != wxID_OK) + return; + file = dlg.GetPath(); } wxGetApp().app_config->update_config_dir(get_dir_name(file)); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 99f63da8a1..97a1da2728 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -296,11 +296,11 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * data->SetChooseFull(1); data->SetColour(clr); - auto dialog = new wxColourDialog(this, data); - dialog->CenterOnParent(); - if (dialog->ShowModal() == wxID_OK) + wxColourDialog dialog(this, data); + dialog.CenterOnParent(); + if (dialog.ShowModal() == wxID_OK) { - colors->values[extruder_idx] = dialog->GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX); + colors->values[extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX); DynamicPrintConfig cfg_new = *cfg; cfg_new.set_key_value("extruder_colour", colors); @@ -309,7 +309,6 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * wxGetApp().preset_bundle->update_platter_filament_ui(extruder_idx, this); wxGetApp().plater()->on_config_change(cfg_new); } - dialog->Destroy(); }); } @@ -2511,15 +2510,14 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) default: break; } - wxFileDialog* dlg = new wxFileDialog(q, dlg_title, + wxFileDialog dlg(q, dlg_title, from_path(output_file.parent_path()), from_path(output_file.filename()), wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if (dlg->ShowModal() != wxID_OK) { + if (dlg.ShowModal() != wxID_OK) return wxEmptyString; - } - wxString out_path = dlg->GetPath(); + wxString out_path = dlg.GetPath(); fs::path path(into_path(out_path)); wxGetApp().app_config->update_last_output_dir(path.parent_path().string()); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index d643aa4a86..a7d178e724 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -159,8 +159,8 @@ void Tab::create_preset_tab() m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { on_roll_back_value(true); })); m_question_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent) { - auto dlg = new ButtonsDescription(this, m_icon_descriptions); - if (dlg->ShowModal() == wxID_OK) { + ButtonsDescription dlg(this, m_icon_descriptions); + if (dlg.ShowModal() == wxID_OK) { // Colors for ui "decoration" for (Tab *tab : wxGetApp().tabs_list) { tab->m_sys_label_clr = wxGetApp().get_label_clr_sys(); @@ -1266,10 +1266,10 @@ void TabPrint::update() if (m_config->opt_float("layer_height") < EPSILON) { const wxString msg_text = _(L("Zero layer height is not valid.\n\nThe layer height will be reset to 0.01.")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Layer height")), wxICON_WARNING | wxOK); + wxMessageDialog dialog(parent(), msg_text, _(L("Layer height")), wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *m_config; is_msg_dlg_already_exist = true; - dialog->ShowModal(); + dialog.ShowModal(); new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.01)); load_config(new_conf); is_msg_dlg_already_exist = false; @@ -1278,10 +1278,10 @@ void TabPrint::update() if (fabs(m_config->option("first_layer_height")->value - 0) < EPSILON) { const wxString msg_text = _(L("Zero first layer height is not valid.\n\nThe first layer height will be reset to 0.01.")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("First layer height")), wxICON_WARNING | wxOK); + wxMessageDialog dialog(parent(), msg_text, _(L("First layer height")), wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *m_config; is_msg_dlg_already_exist = true; - dialog->ShowModal(); + dialog.ShowModal(); new_conf.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.01, false)); load_config(new_conf); is_msg_dlg_already_exist = false; @@ -1299,9 +1299,9 @@ void TabPrint::update() "- no support material\n" "- no ensure_vertical_shell_thickness\n" "\nShall I adjust those settings in order to enable Spiral Vase?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Spiral Vase")), wxICON_WARNING | wxYES | wxNO); + wxMessageDialog dialog(parent(), msg_text, _(L("Spiral Vase")), wxICON_WARNING | wxYES | wxNO); DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { + if (dialog.ShowModal() == wxID_YES) { new_conf.set_key_value("perimeters", new ConfigOptionInt(1)); new_conf.set_key_value("top_solid_layers", new ConfigOptionInt(0)); new_conf.set_key_value("fill_density", new ConfigOptionPercent(0)); @@ -1324,9 +1324,9 @@ void TabPrint::update() "if they are printed with the current extruder without triggering a tool change.\n" "(both support_material_extruder and support_material_interface_extruder need to be set to 0).\n" "\nShall I adjust those settings in order to enable the Wipe Tower?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); + wxMessageDialog dialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { + if (dialog.ShowModal() == wxID_YES) { new_conf.set_key_value("support_material_extruder", new ConfigOptionInt(0)); new_conf.set_key_value("support_material_interface_extruder", new ConfigOptionInt(0)); } @@ -1341,9 +1341,9 @@ void TabPrint::update() wxString msg_text = _(L("For the Wipe Tower to work with the soluble supports, the support layers\n" "need to be synchronized with the object layers.\n" "\nShall I synchronize support layers in order to enable the Wipe Tower?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); + wxMessageDialog dialog(parent(), msg_text, _(L("Wipe Tower")), wxICON_WARNING | wxYES | wxNO); DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { + if (dialog.ShowModal() == wxID_YES) { new_conf.set_key_value("support_material_synchronize_layers", new ConfigOptionBool(true)); } else @@ -1359,9 +1359,9 @@ void TabPrint::update() wxString msg_text = _(L("Supports work better, if the following feature is enabled:\n" "- Detect bridging perimeters\n" "\nShall I adjust those settings for supports?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Support Generator")), wxICON_WARNING | wxYES | wxNO | wxCANCEL); + wxMessageDialog dialog(parent(), msg_text, _(L("Support Generator")), wxICON_WARNING | wxYES | wxNO | wxCANCEL); DynamicPrintConfig new_conf = *m_config; - auto answer = dialog->ShowModal(); + auto answer = dialog.ShowModal(); if (answer == wxID_YES) { // Enable "detect bridging perimeters". new_conf.set_key_value("overhangs", new ConfigOptionBool(true)); @@ -1403,9 +1403,9 @@ void TabPrint::update() if (!correct_100p_fill) { wxString msg_text = GUI::from_u8((boost::format(_utf8(L("The %1% infill pattern is not supposed to work at 100%% density.\n\n" "Shall I switch to rectilinear fill pattern?"))) % str_fill_pattern).str()); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Infill")), wxICON_WARNING | wxYES | wxNO); + wxMessageDialog dialog(parent(), msg_text, _(L("Infill")), wxICON_WARNING | wxYES | wxNO); DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { + if (dialog.ShowModal() == wxID_YES) { new_conf.set_key_value("fill_pattern", new ConfigOptionEnum(ipRectilinear)); fill_density = 100; } @@ -2045,10 +2045,10 @@ void TabPrinter::build_fff() const wxString msg_text = _(L("Single Extruder Multi Material is selected, \n" "and all extruders must have the same diameter.\n" "Do you want to change the diameter for all extruders to first extruder nozzle diameter value?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); + wxMessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { + if (dialog.ShowModal() == wxID_YES) { for (size_t i = 1; i < nozzle_diameters.size(); i++) nozzle_diameters[i] = frst_diam; @@ -2507,10 +2507,10 @@ void TabPrinter::build_unregular_pages() { const wxString msg_text = _(L("This is a single extruder multimaterial printer, diameters of all extruders " "will be set to the new value. Do you want to proceed?")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); + wxMessageDialog dialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { + if (dialog.ShowModal() == wxID_YES) { for (size_t i = 0; i < nozzle_diameters.size(); i++) { if (i==extruder_idx) continue; @@ -2715,13 +2715,13 @@ void TabPrinter::update_fff() get_field("retract_before_wipe", i)->toggle(wipe); if (use_firmware_retraction && wipe) { - auto dialog = new wxMessageDialog(parent(), + wxMessageDialog dialog(parent(), _(L("The Wipe option is not available when using the Firmware Retraction mode.\n" "\nShall I disable it in order to enable Firmware Retraction?")), _(L("Firmware Retraction")), wxICON_WARNING | wxYES | wxNO); DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_YES) { + if (dialog.ShowModal() == wxID_YES) { auto wipe = static_cast(m_config->option("wipe")->clone()); for (int w = 0; w < wipe->values.size(); w++) wipe->values[w] = false; @@ -3073,10 +3073,10 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr message += wxString("\n") + tab + from_u8(new_printer_name) + "\n\n"; message += _(L("and it has the following unsaved changes:")); } - auto confirm = new wxMessageDialog(parent(), + wxMessageDialog confirm(parent(), message + "\n" + changes + "\n\n" + _(L("Discard changes and continue anyway?")), _(L("Unsaved Changes")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); - return confirm->ShowModal() == wxID_YES; + return confirm.ShowModal() == wxID_YES; } // If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). @@ -3183,11 +3183,11 @@ void Tab::save_preset(std::string name /*= ""*/) values.push_back(preset.name); } - auto dlg = new SavePresetWindow(parent()); - dlg->build(title(), default_name, values); - if (dlg->ShowModal() != wxID_OK) + SavePresetWindow dlg(parent()); + dlg.build(title(), default_name, values); + if (dlg.ShowModal() != wxID_OK) return; - name = dlg->get_name(); + name = dlg.get_name(); if (name == "") { show_error(this, _(L("The supplied name is empty. It can't be saved."))); return; @@ -3799,13 +3799,13 @@ void TabSLAPrint::update() wxString msg_text = _( L("Head penetration should not be greater than the head width.")); - auto dialog = new wxMessageDialog(parent(), - msg_text, - _(L("Invalid Head penetration")), - wxICON_WARNING | wxOK); + wxMessageDialog dialog(parent(), + msg_text, + _(L("Invalid Head penetration")), + wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_OK) { + if (dialog.ShowModal() == wxID_OK) { new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width)); } @@ -3819,13 +3819,13 @@ void TabSLAPrint::update() wxString msg_text = _(L( "Pinhead diameter should be smaller than the pillar diameter.")); - auto dialog = new wxMessageDialog(parent(), - msg_text, - _(L("Invalid pinhead diameter")), - wxICON_WARNING | wxOK); + wxMessageDialog dialog (parent(), + msg_text, + _(L("Invalid pinhead diameter")), + wxICON_WARNING | wxOK); DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_OK) { + if (dialog.ShowModal() == wxID_OK) { new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0)); } From 8fce5118069cae1bbe89f0e988d0ff67ffad2022 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 15 Aug 2019 16:15:17 +0200 Subject: [PATCH 554/627] Fix for issue #2765, arrange crash with custom beds --- src/libslic3r/Arrange.cpp | 5 ++++- src/libslic3r/Arrange.hpp | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index b4cfac9546..76ad81e299 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -492,8 +492,11 @@ BedShapeHint::BedShapeHint(const Polyline &bed) { m_type = BedShapes::bsCircle; m_bed.circ = c; } else { + if (m_type == BedShapes::bsIrregular) + m_bed.polygon.Slic3r::Polyline::~Polyline(); + m_type = BedShapes::bsIrregular; - m_bed.polygon = bed; + ::new (&m_bed.polygon) Polyline(bed); } } diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index c02393dd9e..3d405145e6 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -45,12 +45,12 @@ class BedShapeHint { Polyline polygon; InfiniteBed infbed{}; ~BedShape_u() {} - BedShape_u() {}; + BedShape_u() {} } m_bed; public: - BedShapeHint(){}; + BedShapeHint(){} /// Get a bed shape hint for arrange() from a naked Polyline. explicit BedShapeHint(const Polyline &polyl); @@ -73,7 +73,7 @@ public: { if (m_type == BedShapes::bsIrregular) m_bed.polygon.Slic3r::Polyline::~Polyline(); - }; + } BedShapeHint(const BedShapeHint &cpy) { *this = cpy; } BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); } From 0f32223ba0ec19b1e85141d0ccb997574532ad91 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 16 Aug 2019 00:20:51 +0200 Subject: [PATCH 555/627] WipeTower: linear advance is disabled immediately before ramming, not before moving to the wipe tower Linear advance is reset by filament start gcode after a toolchange. However, not all moves to the wipe tower end with a toolchange (brim, empty grid) and it would therefore disable linear advance until the next toolchange This should solve https://github.com/prusa3d/PrusaSlicer/issues/2770 --- src/libslic3r/GCode.cpp | 3 --- src/libslic3r/GCode/WipeTower.cpp | 7 +++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index b691203c97..1e67f02445 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -197,9 +197,6 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); - // Disable linear advance for the wipe tower operations. - gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); - if (!tcr.priming) { // Move over the wipe tower. // Retract for a tool change, using the toolchange retract value and setting the priming extra length. diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index e1b34fdeae..7a764d0f6e 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -113,6 +113,11 @@ public: return (*this); } + WipeTowerWriter& disable_linear_advance() { + m_gcode += (m_gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); + return *this; + } + // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various // filament loading and cooling moves from normal extrusion moves. Therefore the writer // is asked to suppres output of some lines, which look like extrusions. @@ -818,6 +823,8 @@ void WipeTower::toolchange_Unload( } } + writer.disable_linear_advance(); + // now the ramming itself: while (i < m_filpar[m_current_tool].ramming_speed.size()) { From dac301e3b68be4cd3da5e4a2bbef764d33747e94 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 16 Aug 2019 13:14:51 +0200 Subject: [PATCH 556/627] Update volumes on 3DScene, after adding of part for unprintable object --- src/slic3r/GUI/GLCanvas3D.cpp | 29 ++++++++++++++++------------- src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/GUI_ObjectList.cpp | 12 ++++++++---- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 0d0d4d84b0..a5906f619b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1319,22 +1319,25 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject _set_warning_texture(WarningTexture::SomethingNotShown, false); } +void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx) +{ + ModelObject* model_object = m_model->objects[obj_idx]; + for (int inst_idx = 0; inst_idx < model_object->instances.size(); inst_idx++) + { + ModelInstance* instance = model_object->instances[inst_idx]; + + for (GLVolume* volume : m_volumes.volumes) + { + if ((volume->object_idx() == obj_idx) && (volume->instance_idx() == inst_idx)) + volume->printable = instance->printable; + } + } +} + void GLCanvas3D::update_instance_printable_state_for_objects(std::vector& object_idxs) { for (size_t obj_idx : object_idxs) - { - ModelObject* model_object = m_model->objects[obj_idx]; - for (int inst_idx = 0; inst_idx < model_object->instances.size(); inst_idx++) - { - ModelInstance* instance = model_object->instances[inst_idx]; - - for (GLVolume* volume : m_volumes.volumes) - { - if ((volume->object_idx() == obj_idx) && (volume->instance_idx() == inst_idx)) - volume->printable = instance->printable; - } - } - } + update_instance_printable_state_for_object(obj_idx); } void GLCanvas3D::set_config(const DynamicPrintConfig* config) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index c1fc5a948b..577682fe22 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -482,6 +482,7 @@ public: void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1); + void update_instance_printable_state_for_object(size_t obj_idx); void update_instance_printable_state_for_objects(std::vector& object_idxs); void set_config(const DynamicPrintConfig* config); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b3bedaf50d..3d5efe63fc 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1616,6 +1616,9 @@ void ObjectList::load_subobject(ModelVolumeType type) changed_object(obj_idx); + if (type == ModelVolumeType::MODEL_PART) + // update printable state on canvas + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx); wxDataViewItem sel_item; for (const auto& volume : volumes_info ) @@ -1739,6 +1742,9 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); changed_object(obj_idx); + if (type == ModelVolumeType::MODEL_PART) + // update printable state on canvas + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx); const auto object_item = m_objects_model->GetTopParent(GetSelection()); select_item(m_objects_model->AddVolumeChild(object_item, name, type, @@ -3351,9 +3357,8 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set delete_instance_from_list(obj_idx, *it); } - std::vector object_idxs = { new_obj_indx }; // update printable state for new volumes on canvas3D - wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(object_idxs); + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object(new_obj_indx); } void ObjectList::instances_to_separated_objects(const int obj_idx) @@ -3684,8 +3689,7 @@ void ObjectList::toggle_printable_state(wxDataViewItem item) inst->printable = printable; // update printable state on canvas - std::vector obj_idxs = {(size_t)obj_idx}; - wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(obj_idxs); + wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object((size_t)obj_idx); // update printable state in ObjectList m_objects_model->SetObjectPrintableState(printable ? piPrintable : piUnprintable , item); From eba8c39846c7ada22f68001ac484c56241e417bd Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 16 Aug 2019 13:55:39 +0200 Subject: [PATCH 557/627] Fix performance bottleneck in IGL --- src/libigl/igl/ray_box_intersect.cpp | 28 +++++++++++++-------------- src/libigl/igl/ray_mesh_intersect.cpp | 4 +++- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/libigl/igl/ray_box_intersect.cpp b/src/libigl/igl/ray_box_intersect.cpp index 8c6346d86d..088273f255 100644 --- a/src/libigl/igl/ray_box_intersect.cpp +++ b/src/libigl/igl/ray_box_intersect.cpp @@ -1,12 +1,12 @@ // This file is part of libigl, a simple c++ geometry processing library. -// +// // Copyright (C) 2016 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can // obtain one at http://mozilla.org/MPL/2.0/. #include "ray_box_intersect.h" -#include +#include template < typename Derivedsource, @@ -27,7 +27,7 @@ IGL_INLINE bool igl::ray_box_intersect( const Eigen::Vector3f& rayo, const Eigen::Vector3f& rayd, const Eigen::Vector3f& bmin, - const Eigen::Vector3f& bmax, + const Eigen::Vector3f& bmax, float & tnear, float & tfar )->bool @@ -35,12 +35,12 @@ IGL_INLINE bool igl::ray_box_intersect( Eigen::Vector3f bnear; Eigen::Vector3f bfar; // Checks for intersection testing on each direction coordinate - // Computes + // Computes float t1, t2; tnear = -1e+6f, tfar = 1e+6f; //, tCube; bool intersectFlag = true; for (int i = 0; i < 3; ++i) { - // std::cout << "coordinate " << i << ": bmin " << bmin(i) << ", bmax " << bmax(i) << std::endl; + // std::cout << "coordinate " << i << ": bmin " << bmin(i) << ", bmax " << bmax(i) << std::endl; assert(bmin(i) <= bmax(i)); if (::fabs(rayd(i)) < 1e-6) { // Ray parallel to axis i-th if (rayo(i) < bmin(i) || rayo(i) > bmax(i)) { @@ -59,12 +59,12 @@ IGL_INLINE bool igl::ray_box_intersect( } // std::cout << " bnear " << bnear(i) << ", bfar " << bfar(i) << std::endl; // Finds the distance parameters t1 and t2 of the two ray-box intersections: - // t1 must be the closest to the ray origin rayo. + // t1 must be the closest to the ray origin rayo. t1 = (bnear(i) - rayo(i)) / rayd(i); t2 = (bfar(i) - rayo(i)) / rayd(i); if (t1 > t2) { std::swap(t1,t2); - } + } // The two intersection values are used to saturate tnear and tfar if (t1 > tnear) { tnear = t1; @@ -72,7 +72,7 @@ IGL_INLINE bool igl::ray_box_intersect( if (t2 < tfar) { tfar = t2; } - // std::cout << " t1 " << t1 << ", t2 " << t2 << ", tnear " << tnear << ", tfar " << tfar + // std::cout << " t1 " << t1 << ", t2 " << t2 << ", tnear " << tnear << ", tfar " << tfar // << " tnear > tfar? " << (tnear > tfar) << ", tfar < 0? " << (tfar < 0) << std::endl; if(tnear > tfar) { intersectFlag = false; @@ -101,11 +101,11 @@ IGL_INLINE bool igl::ray_box_intersect( // This should be precomputed and provided as input typedef Matrix RowVector3S; const RowVector3S inv_dir( 1./dir(0),1./dir(1),1./dir(2)); - const std::vector sign = { inv_dir(0)<0, inv_dir(1)<0, inv_dir(2)<0}; + const std::array sign = { inv_dir(0)<0, inv_dir(1)<0, inv_dir(2)<0}; // http://people.csail.mit.edu/amy/papers/box-jgt.pdf // "An Efficient and Robust Ray–Box Intersection Algorithm" Scalar tymin, tymax, tzmin, tzmax; - std::vector bounds = {box.min(),box.max()}; + std::array bounds = {box.min(),box.max()}; tmin = ( bounds[sign[0]](0) - origin(0)) * inv_dir(0); tmax = ( bounds[1-sign[0]](0) - origin(0)) * inv_dir(0); tymin = (bounds[sign[1]](1) - origin(1)) * inv_dir(1); @@ -146,4 +146,4 @@ IGL_INLINE bool igl::ray_box_intersect( #ifdef IGL_STATIC_LIBRARY template bool igl::ray_box_intersect, Eigen::Matrix, double>(Eigen::MatrixBase > const&, Eigen::MatrixBase > const&, Eigen::AlignedBox const&, double const&, double const&, double&, double&); -#endif \ No newline at end of file +#endif diff --git a/src/libigl/igl/ray_mesh_intersect.cpp b/src/libigl/igl/ray_mesh_intersect.cpp index 512a35c461..18060fbadb 100644 --- a/src/libigl/igl/ray_mesh_intersect.cpp +++ b/src/libigl/igl/ray_mesh_intersect.cpp @@ -29,7 +29,9 @@ IGL_INLINE bool igl::ray_mesh_intersect( // Should be but can't be const Vector3d s_d = s.template cast(); Vector3d dir_d = dir.template cast(); - hits.clear(); + hits.clear(); + hits.reserve(F.rows()); + // loop over all triangles for(int f = 0;f Date: Fri, 16 Aug 2019 16:17:37 +0200 Subject: [PATCH 558/627] more clang warnings enabled, performance measuring Succesfull build on mingw-w64 fix sandboxes Mingw fixes and full parallel support tree gen. --- CMakeLists.txt | 63 ++- deps/CMakeLists.txt | 5 +- deps/deps-mingw.cmake | 76 +++ deps/deps-unix-common.cmake | 7 + sandboxes/CMakeLists.txt | 1 + sandboxes/slabasebed/slabasebed.cpp | 17 +- sandboxes/slasupporttree/CMakeLists.txt | 2 + sandboxes/slasupporttree/slasupporttree.cpp | 42 ++ src/CMakeLists.txt | 36 +- src/PrusaSlicer.cpp | 238 ++++----- src/PrusaSlicer_app_msvc.cpp | 381 +++++++-------- src/avrdude/CMakeLists.txt | 8 +- src/avrdude/lexer.c | 2 +- src/avrdude/main-standalone.cpp | 4 + src/avrdude/windows/unistd.h | 2 +- src/libigl/igl/SortableRow.h | 84 ++-- .../libnest2d/backends/clipper/geometries.hpp | 8 + src/libslic3r/Arrange.cpp | 10 + src/libslic3r/CMakeLists.txt | 6 +- src/libslic3r/GCode/ToolOrdering.hpp | 73 +-- src/libslic3r/PrintConfig.cpp | 402 ++++++++-------- src/libslic3r/SLA/SLAAutoSupports.cpp | 88 ++-- src/libslic3r/SLA/SLASupportTree.cpp | 328 ++++++------- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 13 +- src/libslic3r/SLAPrint.cpp | 192 ++++---- src/slic3r/GUI/wxExtensions.hpp | 451 +++++++++--------- src/slic3r/Utils/Time.hpp | 2 +- 27 files changed, 1393 insertions(+), 1148 deletions(-) create mode 100644 deps/deps-mingw.cmake create mode 100644 sandboxes/slasupporttree/CMakeLists.txt create mode 100644 sandboxes/slasupporttree/slasupporttree.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a29a144fe3..9b8a1c8480 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,13 +13,13 @@ if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) endif() if(DEFINED ENV{SLIC3R_STATIC}) - set(SLIC3R_STATIC_INITIAL $ENV{SLIC3R_STATIC}) + set(SLIC3R_STATIC_INITIAL $ENV{SLIC3R_STATIC}) else() - if (MSVC OR MINGW OR APPLE) - set(SLIC3R_STATIC_INITIAL 1) - else() - set(SLIC3R_STATIC_INITIAL 0) - endif() + if (MSVC OR MINGW OR APPLE) + set(SLIC3R_STATIC_INITIAL 1) + else() + set(SLIC3R_STATIC_INITIAL 0) + endif() endif() option(SLIC3R_STATIC "Compile PrusaSlicer with static libraries (Boost, TBB, glew)" ${SLIC3R_STATIC_INITIAL}) @@ -54,13 +54,19 @@ endif () if (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) set(IS_CLANG_CL TRUE) + + # clang-cl can interpret SYSTEM header paths if -imsvc is used + set(CMAKE_INCLUDE_SYSTEM_FLAG_CXX "-imsvc") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall \ + -Wno-old-style-cast -Wno-reserved-id-macro -Wno-c++98-compat-pedantic") else () set(IS_CLANG_CL FALSE) endif () if (MSVC) if (SLIC3R_MSVC_COMPILE_PARALLEL AND NOT IS_CLANG_CL) - add_compile_options(/MP) + add_compile_options(/MP) endif () # /bigobj (Increase Number of Sections in .Obj file) # error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm90' or greater @@ -68,6 +74,10 @@ if (MSVC) add_compile_options(-bigobj -Zm520 /Zi) endif () +if (MINGW) + add_compile_options(-Wa,-mbig-obj) +endif () + # Display and check CMAKE_PREFIX_PATH message(STATUS "SLIC3R_STATIC: ${SLIC3R_STATIC}") if (NOT "${CMAKE_PREFIX_PATH}" STREQUAL "") @@ -107,17 +117,17 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory. # We pick it from environment if it is not defined in another way if(WIN32) - if(NOT DEFINED WIN10SDK_PATH) - if(DEFINED ENV{WIN10SDK_PATH}) - set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") - endif() - endif() - if(DEFINED WIN10SDK_PATH AND NOT EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h") - message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") - message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found") - message("STL fixing by the Netfabb service will not be compiled") - unset(WIN10SDK_PATH) - endif() + if(NOT DEFINED WIN10SDK_PATH) + if(DEFINED ENV{WIN10SDK_PATH}) + set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") + endif() + endif() + if(DEFINED WIN10SDK_PATH AND NOT EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h") + message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}") + message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found") + message("STL fixing by the Netfabb service will not be compiled") + unset(WIN10SDK_PATH) + endif() if(WIN10SDK_PATH) message("Building with Win10 Netfabb STL fixing service support") add_definitions(-DHAS_WIN10SDK) @@ -155,7 +165,9 @@ if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUXX) endif() if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall" ) + if (NOT MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall" ) + endif () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-reorder" ) # On GCC and Clang, no return from a non-void function is a warning only. Here, we make it an error. @@ -201,9 +213,12 @@ include_directories(${LIBDIR_BIN}/platform) include_directories(${LIBDIR}/clipper ${LIBDIR}/polypartition) if(WIN32) - # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking. - add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x601 -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS) -endif() + add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS) + if(MSVC) + # BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking. + add_definitions(-DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x601 ) + endif(MSVC) +endif(WIN32) add_definitions(-DwxUSE_UNICODE -D_UNICODE -DUNICODE -DWXINTL_NO_GETTEXT_MACRO) @@ -234,7 +249,7 @@ if(SLIC3R_STATIC) # set(Boost_USE_STATIC_RUNTIME ON) endif() #set(Boost_DEBUG ON) -# set(Boost_COMPILER "-vc120") +# set(Boost_COMPILER "-mgw81") if(NOT WIN32) # boost::process was introduced first in version 1.64.0 set(MINIMUM_BOOST_VERSION "1.64.0") @@ -256,7 +271,7 @@ endif() if(TARGET Boost::system) message(STATUS "Boost::boost exists") target_link_libraries(boost_headeronly INTERFACE Boost::boost) - target_link_libraries(boost_libs INTERFACE + target_link_libraries(boost_libs INTERFACE boost_headeronly # includes the custom compile definitions as well Boost::system Boost::filesystem diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index f6a80f3ca0..90ad6f0fa7 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -76,7 +76,10 @@ elseif (APPLE) endif () include("deps-macos.cmake") -else () +elseif (MINGW) + message(STATUS "Building for MinGW...") + include("deps-mingw.cmake") +else() include("deps-linux.cmake") endif() diff --git a/deps/deps-mingw.cmake b/deps/deps-mingw.cmake new file mode 100644 index 0000000000..bfe5f9fe43 --- /dev/null +++ b/deps/deps-mingw.cmake @@ -0,0 +1,76 @@ +set(DEP_CMAKE_OPTS "-DCMAKE_POSITION_INDEPENDENT_CODE=ON") +set(DEP_BOOST_TOOLSET "gcc") +set(DEP_BITS 64) + +find_package(Git REQUIRED) + +# TODO make sure to build tbb with -flifetime-dse=1 +include("deps-unix-common.cmake") + +ExternalProject_Add(dep_boost + EXCLUDE_FROM_ALL 1 + URL "https://dl.bintray.com/boostorg/release/1.70.0/source/boost_1_70_0.tar.gz" + URL_HASH SHA256=882b48708d211a5f48e60b0124cf5863c1534cd544ecd0664bb534a4b5d506e9 + BUILD_IN_SOURCE 1 + CONFIGURE_COMMAND bootstrap.bat + BUILD_COMMAND b2.exe + -j "${NPROC}" + --with-system + --with-filesystem + --with-thread + --with-log + --with-locale + --with-regex + "--prefix=${DESTDIR}/usr/local" + "address-model=${DEPS_BITS}" + "toolset=${DEP_BOOST_TOOLSET}" + link=static + define=BOOST_USE_WINAPI_VERSION=0x0502 + variant=release + threading=multi + boost.locale.icu=off + "${DEP_BOOST_DEBUG}" release install + INSTALL_COMMAND "" # b2 does that already +) + +ExternalProject_Add(dep_libcurl + EXCLUDE_FROM_ALL 1 + URL "https://curl.haxx.se/download/curl-7.58.0.tar.gz" + URL_HASH SHA256=cc245bf9a1a42a45df491501d97d5593392a03f7b4f07b952793518d97666115 + CMAKE_ARGS + -DBUILD_SHARED_LIBS=OFF + -DBUILD_TESTING=OFF + -DCURL_STATICLIB=ON + -DCURL_STATIC_CRT=ON + -DENABLE_THREADED_RESOLVER=ON + -DCURL_DISABLE_FTP=ON + -DCURL_DISABLE_LDAP=ON + -DCURL_DISABLE_LDAPS=ON + -DCURL_DISABLE_TELNET=ON + -DCURL_DISABLE_DICT=ON + -DCURL_DISABLE_FILE=ON + -DCURL_DISABLE_TFTP=ON + -DCURL_DISABLE_RTSP=ON + -DCURL_DISABLE_POP3=ON + -DCURL_DISABLE_IMAP=ON + -DCURL_DISABLE_SMTP=ON + -DCURL_DISABLE_GOPHER=ON + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + ${DEP_CMAKE_OPTS} +) + +ExternalProject_Add(dep_wxwidgets + EXCLUDE_FROM_ALL 1 + GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets" + GIT_TAG v3.1.1-patched +# URL "https://github.com/wxWidgets/wxWidgets/releases/download/v3.1.1/wxWidgets-3.1.1.tar.bz2" +# URL_HASH SHA256=c925dfe17e8f8b09eb7ea9bfdcfcc13696a3e14e92750effd839f5e10726159e +# PATCH_COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}\\wxwidgets-pngprefix.h" src\\png\\pngprefix.h + CMAKE_ARGS + -DBUILD_SHARED_LIBS=OFF + -DwxUSE_LIBPNG=builtin + -DwxUSE_ZLIB=builtin + -DwxUSE_OPENGL=ON + -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local + ${DEP_CMAKE_OPTS} +) \ No newline at end of file diff --git a/deps/deps-unix-common.cmake b/deps/deps-unix-common.cmake index e323460a6d..6e559d05a3 100644 --- a/deps/deps-unix-common.cmake +++ b/deps/deps-unix-common.cmake @@ -1,6 +1,12 @@ # The unix common part expects DEP_CMAKE_OPTS to be set +if (MINGW) + set(TBB_MINGW_WORKAROUND "-flifetime-dse=1") +else () + set(TBB_MINGW_WORKAROUND "") +endif () + ExternalProject_Add(dep_tbb EXCLUDE_FROM_ALL 1 URL "https://github.com/wjakob/tbb/archive/a0dc9bf76d0120f917b641ed095360448cabc85b.tar.gz" @@ -8,6 +14,7 @@ ExternalProject_Add(dep_tbb CMAKE_ARGS -DTBB_BUILD_SHARED=OFF -DTBB_BUILD_TESTS=OFF + -DCMAKE_CXX_FLAGS=${TBB_MINGW_WORKAROUND} -DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local ${DEP_CMAKE_OPTS} ) diff --git a/sandboxes/CMakeLists.txt b/sandboxes/CMakeLists.txt index 0e1f52d6cf..5905c438e9 100644 --- a/sandboxes/CMakeLists.txt +++ b/sandboxes/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(slabasebed) +add_subdirectory(slasupporttree) diff --git a/sandboxes/slabasebed/slabasebed.cpp b/sandboxes/slabasebed/slabasebed.cpp index 5393f61fd7..b8b94d86f3 100644 --- a/sandboxes/slabasebed/slabasebed.cpp +++ b/sandboxes/slabasebed/slabasebed.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -15,8 +16,8 @@ const std::string USAGE_STR = { namespace Slic3r { namespace sla { -Contour3D create_base_pool(const Polygons &ground_layer, - const Polygons &holes = {}, +Contour3D create_base_pool(const Polygons &ground_layer, + const ExPolygons &holes = {}, const PoolConfig& cfg = PoolConfig()); Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling, @@ -43,22 +44,22 @@ int main(const int argc, const char *argv[]) { model.ReadSTLFile(argv[1]); model.align_to_origin(); - Polygons ground_slice; + ExPolygons ground_slice; sla::base_plate(model, ground_slice, 0.1f); if(ground_slice.empty()) return EXIT_FAILURE; - Polygon gndfirst; gndfirst = ground_slice.front(); - sla::offset_with_breakstick_holes(gndfirst, 0.5, 10, 0.3); + ground_slice = offset_ex(ground_slice, 0.5); + ExPolygon gndfirst; gndfirst = ground_slice.front(); + sla::breakstick_holes(gndfirst, 0.5, 10, 0.3); sla::Contour3D mesh; - bench.start(); sla::PoolConfig cfg; cfg.min_wall_height_mm = 0; cfg.edge_radius_mm = 0; - mesh = sla::create_base_pool(ground_slice, {}, cfg); + mesh = sla::create_base_pool(to_polygons(ground_slice), {}, cfg); bench.stop(); @@ -75,7 +76,7 @@ int main(const int argc, const char *argv[]) { if(std::abs(a) < 1e-6) std::cout << "degenerate triangle" << std::endl; } -// basepool.write_ascii("out.stl"); + // basepool.write_ascii("out.stl"); std::fstream outstream("out.obj", std::fstream::out); mesh.to_obj(outstream); diff --git a/sandboxes/slasupporttree/CMakeLists.txt b/sandboxes/slasupporttree/CMakeLists.txt new file mode 100644 index 0000000000..79adb842b9 --- /dev/null +++ b/sandboxes/slasupporttree/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(slasupporttree slasupporttree.cpp) +target_link_libraries(slasupporttree libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES} ${CMAKE_DL_LIBS}) diff --git a/sandboxes/slasupporttree/slasupporttree.cpp b/sandboxes/slasupporttree/slasupporttree.cpp new file mode 100644 index 0000000000..dcaddf6d3f --- /dev/null +++ b/sandboxes/slasupporttree/slasupporttree.cpp @@ -0,0 +1,42 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +const std::string USAGE_STR = { + "Usage: slasupporttree stlfilename.stl" +}; + +int main(const int argc, const char *argv[]) { + using namespace Slic3r; + using std::cout; using std::endl; + + if(argc < 2) { + cout << USAGE_STR << endl; + return EXIT_SUCCESS; + } + + DynamicPrintConfig config; + + Model model = Model::read_from_file(argv[1], &config); + + SLAPrint print; + + print.apply(model, config); + print.process(); + + + return EXIT_SUCCESS; +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3ee46289ad..9f3dbcec89 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -47,7 +47,7 @@ if (SLIC3R_GUI) endif () endif () else () - find_package(wxWidgets 3.1 REQUIRED COMPONENTS base core adv html gl) + find_package(wxWidgets 3.1 REQUIRED COMPONENTS html adv gl core base) endif () if(UNIX) @@ -56,6 +56,9 @@ if (SLIC3R_GUI) include(${wxWidgets_USE_FILE}) +# list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc) + message(STATUS "wx libs: ${wxWidgets_LIBRARIES}") + add_subdirectory(slic3r) endif() @@ -65,12 +68,18 @@ endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/PrusaSlicer.rc.in ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.rc @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/msw/PrusaSlicer.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.manifest @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/platform/osx/Info.plist.in ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY) -if (MSVC) +if (WIN32) add_library(PrusaSlicer SHARED PrusaSlicer.cpp PrusaSlicer.hpp) else () add_executable(PrusaSlicer PrusaSlicer.cpp PrusaSlicer.hpp) endif () -if (NOT MSVC) + +if (MINGW) + target_link_options(PrusaSlicer PUBLIC "-Wl,-allow-multiple-definition") + set_target_properties(PrusaSlicer PROPERTIES PREFIX "") +endif (MINGW) + +if (NOT WIN32) # Binary name on unix like systems (OSX, Linux) set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") endif () @@ -91,11 +100,12 @@ endif () # Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries. if (SLIC3R_GUI) - target_link_libraries(PrusaSlicer libslic3r_gui ${wxWidgets_LIBRARIES}) +# target_link_libraries(PrusaSlicer ws2_32 uxtheme setupapi libslic3r_gui ${wxWidgets_LIBRARIES}) +target_link_libraries(PrusaSlicer libslic3r_gui ${wxWidgets_LIBRARIES}) # Configure libcurl and its dependencies OpenSSL & zlib find_package(CURL REQUIRED) - if (NOT MSVC) + if (NOT WIN32) # Required by libcurl find_package(ZLIB REQUIRED) endif() @@ -123,7 +133,7 @@ if (SLIC3R_GUI) target_link_options(PrusaSlicer PUBLIC "$<$:/DEBUG>") target_link_libraries(PrusaSlicer user32.lib Setupapi.lib OpenGL32.Lib GlU32.Lib) elseif (MINGW) - target_link_libraries(PrusaSlicer -lopengl32) + target_link_libraries(PrusaSlicer opengl32 ws2_32 uxtheme setupapi) elseif (APPLE) target_link_libraries(PrusaSlicer "-framework OpenGL") else () @@ -133,10 +143,16 @@ endif () # On Windows, a shim application is required to produce a console / non console version of the Slic3r application. # Also the shim may load the Mesa software OpenGL renderer if the default renderer does not support OpenGL 2.0 and higher. -if (MSVC) +if (WIN32) + if (MINGW) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -municode") + endif() + add_executable(PrusaSlicer_app_gui WIN32 PrusaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.rc) # Generate debug symbols even in release mode. - target_link_options(PrusaSlicer_app_gui PUBLIC "$<$:/DEBUG>") + if(MSVC) + target_link_options(PrusaSlicer_app_gui PUBLIC "$<$:/DEBUG>") + endif() target_compile_definitions(PrusaSlicer_app_gui PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE) add_dependencies(PrusaSlicer_app_gui PrusaSlicer) set_target_properties(PrusaSlicer_app_gui PROPERTIES OUTPUT_NAME "prusa-slicer") @@ -144,7 +160,9 @@ if (MSVC) add_executable(PrusaSlicer_app_console PrusaSlicer_app_msvc.cpp ${CMAKE_CURRENT_BINARY_DIR}/PrusaSlicer.rc) # Generate debug symbols even in release mode. + if (MSVC) target_link_options(PrusaSlicer_app_console PUBLIC "$<$:/DEBUG>") + endif () target_compile_definitions(PrusaSlicer_app_console PRIVATE -DSLIC3R_WRAPPER_CONSOLE) add_dependencies(PrusaSlicer_app_console PrusaSlicer) set_target_properties(PrusaSlicer_app_console PROPERTIES OUTPUT_NAME "prusa-slicer-console") @@ -152,7 +170,7 @@ if (MSVC) endif () # Link the resources dir to where Slic3r GUI expects it -if (MSVC) +if (WIN32) if (CMAKE_CONFIGURATION_TYPES) foreach (CONF ${CMAKE_CONFIGURATION_TYPES}) file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${CONF}" WIN_CONF_OUTPUT_DIR) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index b1ba30553e..c8cf9b42d8 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -7,8 +7,8 @@ #include #include #ifdef SLIC3R_GUI - extern "C" - { + extern "C" + { // Let the NVIDIA and AMD know we want to use their graphics card // on a dual graphics card system. __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; @@ -53,28 +53,28 @@ using namespace Slic3r; PrinterTechnology get_printer_technology(const DynamicConfig &config) { - const ConfigOptionEnum *opt = config.option>("printer_technology"); + const ConfigOptionEnum *opt = config.option>("printer_technology"); return (opt == nullptr) ? ptUnknown : opt->value; } -int CLI::run(int argc, char **argv) +int CLI::run(int argc, char **argv) { - if (! this->setup(argc, argv)) - return 1; + if (! this->setup(argc, argv)) + return 1; m_extra_config.apply(m_config, true); m_extra_config.normalize(); bool start_gui = m_actions.empty() && // cutting transformations are setting an "export" action. - std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && - std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && - std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); + std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && + std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && + std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); PrinterTechnology printer_technology = get_printer_technology(m_extra_config); - const std::vector &load_configs = m_config.option("load", true)->values; - + const std::vector &load_configs = m_config.option("load", true)->values; + // load config files supplied via --load - for (auto const &file : load_configs) { + for (auto const &file : load_configs) { if (! boost::filesystem::exists(file)) { if (m_config.opt_bool("ignore_nonexistent_config")) { continue; @@ -100,7 +100,7 @@ int CLI::run(int argc, char **argv) } m_print_config.apply(config); } - + // Read input file(s) if any. for (const std::string &file : m_input_files) { if (! boost::filesystem::exists(file)) { @@ -136,21 +136,21 @@ int CLI::run(int argc, char **argv) m_print_config.normalize(); if (printer_technology == ptUnknown) - printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA; + printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA; // Initialize full print configs for both the FFF and SLA technologies. FullPrintConfig fff_print_config; // SLAFullPrintConfig sla_print_config; fff_print_config.apply(m_print_config, true); // sla_print_config.apply(m_print_config, true); - + // Loop through transform options. for (auto const &opt_key : m_transforms) { if (opt_key == "merge") { Model m; for (auto &model : m_models) - for (ModelObject *o : model.objects) - m.add_object(*o); + for (ModelObject *o : model.objects) + m.add_object(*o); // Rearrange instances unless --dont-arrange is supplied if (! m_config.opt_bool("dont_arrange")) { m.add_default_instances(); @@ -162,8 +162,8 @@ int CLI::run(int argc, char **argv) this->has_print_action() ? &bb : nullptr ); } - m_models.clear(); - m_models.emplace_back(std::move(m)); + m_models.clear(); + m_models.emplace_back(std::move(m)); } else if (opt_key == "duplicate") { const BoundingBoxf &bb = fff_print_config.bed_shape.values; for (auto &model : m_models) { @@ -192,11 +192,11 @@ int CLI::run(int argc, char **argv) // this affects instances: model.center_instances_around_point(m_config.option("center")->value); // this affects volumes: - //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? + //FIXME Vojtech: Who knows why the complete model should be aligned with Z as a single rigid body? //model.align_to_ground(); BoundingBoxf3 bbox; for (ModelObject *model_object : model.objects) - // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. + // We are interested into the Z span only, therefore it is sufficient to measure the bounding box of the 1st instance only. bbox.merge(model_object->instance_bounding_box(0, false)); for (ModelObject *model_object : model.objects) for (ModelInstance *model_instance : model_object->instances) @@ -207,7 +207,7 @@ int CLI::run(int argc, char **argv) for (auto &model : m_models) { BoundingBoxf3 bb = model.bounding_box(); // this affects volumes: - model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z()); + model.translate(-(bb.min.x() - p.x()), -(bb.min.y() - p.y()), -bb.min.z()); } } else if (opt_key == "dont_arrange") { // do nothing - this option alters other transform options @@ -245,8 +245,8 @@ int CLI::run(int argc, char **argv) std::vector new_models; for (auto &model : m_models) { model.translate(0, 0, -model.bounding_box().min.z()); // align to z = 0 - size_t num_objects = model.objects.size(); - for (size_t i = 0; i < num_objects; ++ i) { + size_t num_objects = model.objects.size(); + for (size_t i = 0; i < num_objects; ++ i) { #if 0 if (opt_key == "cut_x") { @@ -257,15 +257,15 @@ int CLI::run(int argc, char **argv) o->cut(Z, m_config.opt_float("cut"), &out); } #else - model.objects.front()->cut(0, m_config.opt_float("cut"), true, true, true); + model.objects.front()->cut(0, m_config.opt_float("cut"), true, true, true); #endif - model.delete_object(size_t(0)); + model.delete_object(size_t(0)); } } - + // TODO: copy less stuff around using pointers m_models = new_models; - + if (m_actions.empty()) m_actions.push_back("export_stl"); } @@ -275,7 +275,7 @@ int CLI::run(int argc, char **argv) for (auto &model : m_models) { TriangleMesh mesh = model.mesh(); mesh.repair(); - + TriangleMeshPtrs meshes = mesh.cut_by_grid(m_config.option("cut_grid")->value); size_t i = 0; for (TriangleMesh* m : meshes) { @@ -286,10 +286,10 @@ int CLI::run(int argc, char **argv) delete m; } } - + // TODO: copy less stuff around using pointers m_models = new_models; - + if (m_actions.empty()) m_actions.push_back("export_stl"); } @@ -303,7 +303,7 @@ int CLI::run(int argc, char **argv) } } } else if (opt_key == "repair") { - // Models are repaired by default. + // Models are repaired by default. //for (auto &model : m_models) // model.repair(); } else { @@ -311,7 +311,7 @@ int CLI::run(int argc, char **argv) return 1; } } - + // loop through action options for (auto const &opt_key : m_actions) { if (opt_key == "help") { @@ -354,14 +354,14 @@ int CLI::run(int argc, char **argv) boost::nowide::cerr << "error: cannot export SLA slices for a SLA configuration" << std::endl; return 1; } - // Make a copy of the model if the current action is not the last action, as the model may be - // modified by the centering and such. - Model model_copy; - bool make_copy = &opt_key != &m_actions.back(); + // Make a copy of the model if the current action is not the last action, as the model may be + // modified by the centering and such. + Model model_copy; + bool make_copy = &opt_key != &m_actions.back(); for (Model &model_in : m_models) { - if (make_copy) - model_copy = model_in; - Model &model = make_copy ? model_copy : model_in; + if (make_copy) + model_copy = model_in; + Model &model = make_copy ? model_copy : model_in; // If all objects have defined instances, their relative positions will be // honored when printing (they will be only centered, unless --dont-arrange // is supplied); if any object has no instances, it will get a default one @@ -381,7 +381,7 @@ int CLI::run(int argc, char **argv) if (! m_config.opt_bool("dont_arrange")) { //FIXME make the min_object_distance configurable. model.arrange_objects(fff_print.config().min_object_distance()); - model.center_instances_around_point(m_config.option("center")->value); + model.center_instances_around_point(m_config.option("center")->value); } if (printer_technology == ptFFF) { for (auto* mo : model.objects) @@ -395,40 +395,40 @@ int CLI::run(int argc, char **argv) } if (print->empty()) boost::nowide::cout << "Nothing to print for " << outfile << " . Either the print is empty or no object is fully inside the print volume." << std::endl; - else + else try { std::string outfile_final; - print->process(); + print->process(); if (printer_technology == ptFFF) { // The outfile is processed by a PlaceholderParser. outfile = fff_print.export_gcode(outfile, nullptr); outfile_final = fff_print.print_statistics().finalize_output_path(outfile); } else { - outfile = sla_print.output_filepath(outfile); + outfile = sla_print.output_filepath(outfile); // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata outfile_final = sla_print.print_statistics().finalize_output_path(outfile); - sla_print.export_raster(outfile_final); + sla_print.export_raster(outfile_final); } if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final) != 0) { - boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; + boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; return 1; } boost::nowide::cout << "Slicing result exported to " << outfile << std::endl; } catch (const std::exception &ex) { - boost::nowide::cerr << ex.what() << std::endl; - return 1; + boost::nowide::cerr << ex.what() << std::endl; + return 1; } /* print.center = ! m_config.has("center") && ! m_config.has("align_xy") && ! m_config.opt_bool("dont_arrange"); print.set_model(model); - + // start chronometer typedef std::chrono::high_resolution_clock clock_; typedef std::chrono::duration > second_; std::chrono::time_point t0{ clock_::now() }; - + const std::string outfile = this->output_filepath(model, IO::Gcode); try { print.export_gcode(outfile); @@ -437,7 +437,7 @@ int CLI::run(int argc, char **argv) return 1; } boost::nowide::cout << "G-code exported to " << outfile << std::endl; - + // output some statistics double duration { std::chrono::duration_cast(clock_::now() - t0).count() }; boost::nowide::cout << std::fixed << std::setprecision(0) @@ -454,45 +454,45 @@ int CLI::run(int argc, char **argv) return 1; } } - - if (start_gui) { + + if (start_gui) { #ifdef SLIC3R_GUI // #ifdef USE_WX - GUI::GUI_App *gui = new GUI::GUI_App(); + GUI::GUI_App *gui = new GUI::GUI_App(); // gui->autosave = m_config.opt_string("autosave"); - GUI::GUI_App::SetInstance(gui); - gui->CallAfter([gui, this, &load_configs] { - if (!gui->initialized()) { - return; - } + GUI::GUI_App::SetInstance(gui); + gui->CallAfter([gui, this, &load_configs] { + if (!gui->initialized()) { + return; + } #if 0 - // Load the cummulative config over the currently active profiles. - //FIXME if multiple configs are loaded, only the last one will have an effect. - // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). - // As of now only the full configs are supported here. - if (!m_print_config.empty()) - gui->mainframe->load_config(m_print_config); + // Load the cummulative config over the currently active profiles. + //FIXME if multiple configs are loaded, only the last one will have an effect. + // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). + // As of now only the full configs are supported here. + if (!m_print_config.empty()) + gui->mainframe->load_config(m_print_config); #endif - if (! load_configs.empty()) - // Load the last config to give it a name at the UI. The name of the preset may be later - // changed by loading an AMF or 3MF. - //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. - gui->mainframe->load_config_file(load_configs.back()); - // If loading a 3MF file, the config is loaded from the last one. - if (! m_input_files.empty()) - gui->plater()->load_files(m_input_files, true, true); - if (! m_extra_config.empty()) - gui->mainframe->load_config(m_extra_config); - }); - return wxEntry(argc, argv); + if (! load_configs.empty()) + // Load the last config to give it a name at the UI. The name of the preset may be later + // changed by loading an AMF or 3MF. + //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. + gui->mainframe->load_config_file(load_configs.back()); + // If loading a 3MF file, the config is loaded from the last one. + if (! m_input_files.empty()) + gui->plater()->load_files(m_input_files, true, true); + if (! m_extra_config.empty()) + gui->mainframe->load_config(m_extra_config); + }); + return wxEntry(argc, argv); #else /* SLIC3R_GUI */ - // No GUI support. Just print out a help. - this->print_help(false); - // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). - return (argc == 0) ? 0 : 1; + // No GUI support. Just print out a help. + this->print_help(false); + // If started without a parameter, consider it to be OK, otherwise report an error code (no action etc). + return (argc == 0) ? 0 : 1; #endif /* SLIC3R_GUI */ } - + return 0; } @@ -539,18 +539,18 @@ bool CLI::setup(int argc, char **argv) // If any option is unsupported, print usage and abort immediately. t_config_option_keys opt_order; if (! m_config.read_cli(argc, argv, &m_input_files, &opt_order)) { - // Separate error message reported by the CLI parser from the help. - boost::nowide::cerr << std::endl; + // Separate error message reported by the CLI parser from the help. + boost::nowide::cerr << std::endl; this->print_help(); - return false; + return false; + } + // Parse actions and transform options. + for (auto const &opt_key : opt_order) { + if (cli_actions_config_def.has(opt_key)) + m_actions.emplace_back(opt_key); + else if (cli_transform_config_def.has(opt_key)) + m_transforms.emplace_back(opt_key); } - // Parse actions and transform options. - for (auto const &opt_key : opt_order) { - if (cli_actions_config_def.has(opt_key)) - m_actions.emplace_back(opt_key); - else if (cli_transform_config_def.has(opt_key)) - m_transforms.emplace_back(opt_key); - } { const ConfigOptionInt *opt_loglevel = m_config.opt("loglevel"); @@ -563,15 +563,15 @@ bool CLI::setup(int argc, char **argv) for (const std::pair &optdef : *options) m_config.optptr(optdef.first, true); - set_data_dir(m_config.opt_string("datadir")); + set_data_dir(m_config.opt_string("datadir")); - return true; + return true; } -void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const +void CLI::print_help(bool include_print_options, PrinterTechnology printer_technology) const { boost::nowide::cout - << SLIC3R_BUILD_ID << " " << "based on Slic3r" + << SLIC3R_BUILD_ID << " " << "based on Slic3r" #ifdef SLIC3R_GUI << " (with GUI support)" #else /* SLIC3R_GUI */ @@ -583,20 +583,20 @@ void CLI::print_help(bool include_print_options, PrinterTechnology printer_techn << std::endl << "Actions:" << std::endl; cli_actions_config_def.print_cli_help(boost::nowide::cout, false); - + boost::nowide::cout << std::endl << "Transform options:" << std::endl; cli_transform_config_def.print_cli_help(boost::nowide::cout, false); - + boost::nowide::cout << std::endl << "Other options:" << std::endl; cli_misc_config_def.print_cli_help(boost::nowide::cout, false); - + if (include_print_options) { boost::nowide::cout << std::endl; - print_config_def.print_cli_help(boost::nowide::cout, true, [printer_technology](const ConfigOptionDef &def) + print_config_def.print_cli_help(boost::nowide::cout, true, [printer_technology](const ConfigOptionDef &def) { return printer_technology == ptAny || def.printer_technology == ptAny || printer_technology == def.printer_technology; }); } else { boost::nowide::cout @@ -613,14 +613,14 @@ bool CLI::export_models(IO::ExportFormat format) switch (format) { case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr); break; case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break; - case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break; - case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr); break; + case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break; + case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr); break; default: assert(false); break; } if (success) - std::cout << "File exported to " << path << std::endl; + std::cout << "File exported to " << path << std::endl; else { - std::cerr << "File export to " << path << " failed" << std::endl; + std::cerr << "File export to " << path << " failed" << std::endl; return false; } } @@ -634,12 +634,12 @@ std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) co case IO::AMF: ext = ".zip.amf"; break; case IO::OBJ: ext = ".obj"; break; case IO::STL: ext = ".stl"; break; - case IO::TMF: ext = ".3mf"; break; + case IO::TMF: ext = ".3mf"; break; default: assert(false); break; }; auto proposed_path = boost::filesystem::path(model.propose_export_file_name_and_path(ext)); // use --output when available - std::string cmdline_param = m_config.opt_string("output"); + std::string cmdline_param = m_config.opt_string("output"); if (! cmdline_param.empty()) { // if we were supplied a directory, use it and append our automatically generated filename boost::filesystem::path cmdline_path(cmdline_param); @@ -651,20 +651,20 @@ std::string CLI::output_filepath(const Model &model, IO::ExportFormat format) co return proposed_path.string(); } -#ifdef _MSC_VER +#if defined(_MSC_VER) || defined(__MINGW32__) extern "C" { - __declspec(dllexport) int __stdcall slic3r_main(int argc, wchar_t **argv) - { - // Convert wchar_t arguments to UTF8. - std::vector argv_narrow; - std::vector argv_ptrs(argc + 1, nullptr); - for (size_t i = 0; i < argc; ++ i) - argv_narrow.emplace_back(boost::nowide::narrow(argv[i])); - for (size_t i = 0; i < argc; ++ i) - argv_ptrs[i] = const_cast(argv_narrow[i].data()); - // Call the UTF8 main. - return CLI().run(argc, argv_ptrs.data()); - } + __declspec(dllexport) int __stdcall slic3r_main(int argc, wchar_t **argv) + { + // Convert wchar_t arguments to UTF8. + std::vector argv_narrow; + std::vector argv_ptrs(argc + 1, nullptr); + for (size_t i = 0; i < argc; ++ i) + argv_narrow.emplace_back(boost::nowide::narrow(argv[i])); + for (size_t i = 0; i < argc; ++ i) + argv_ptrs[i] = const_cast(argv_narrow[i].data()); + // Call the UTF8 main. + return CLI().run(argc, argv_ptrs.data()); + } } #else /* _MSC_VER */ int main(int argc, char **argv) diff --git a/src/PrusaSlicer_app_msvc.cpp b/src/PrusaSlicer_app_msvc.cpp index 95dd4fb075..b3d1e8bb47 100644 --- a/src/PrusaSlicer_app_msvc.cpp +++ b/src/PrusaSlicer_app_msvc.cpp @@ -8,12 +8,12 @@ #include #ifdef SLIC3R_GUI -extern "C" -{ - // Let the NVIDIA and AMD know we want to use their graphics card - // on a dual graphics card system. - __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; - __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; +extern "C" +{ + // Let the NVIDIA and AMD know we want to use their graphics card + // on a dual graphics card system. + __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; + __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif /* SLIC3R_GUI */ @@ -21,7 +21,7 @@ extern "C" #include #ifdef SLIC3R_GUI - #include + #include #endif /* SLIC3R_GUI */ #include @@ -36,97 +36,97 @@ extern "C" class OpenGLVersionCheck { public: - std::string version; - std::string glsl_version; - std::string vendor; - std::string renderer; + std::string version; + std::string glsl_version; + std::string vendor; + std::string renderer; - HINSTANCE hOpenGL = nullptr; - bool success = false; + HINSTANCE hOpenGL = nullptr; + bool success = false; - bool load_opengl_dll() - { - MSG msg = {0}; - WNDCLASS wc = {0}; - wc.lpfnWndProc = OpenGLVersionCheck::supports_opengl2_wndproc; - wc.hInstance = (HINSTANCE)GetModuleHandle(nullptr); - wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND); - wc.lpszClassName = L"PrusaSlicer_opengl_version_check"; - wc.style = CS_OWNDC; - if (RegisterClass(&wc)) { - HWND hwnd = CreateWindowW(wc.lpszClassName, L"PrusaSlicer_opengl_version_check", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, 0, 0, wc.hInstance, (LPVOID)this); - if (hwnd) { - message_pump_exit = false; - while (GetMessage(&msg, NULL, 0, 0 ) > 0 && ! message_pump_exit) - DispatchMessage(&msg); - } - } - return this->success; - } + bool load_opengl_dll() + { + MSG msg = {0}; + WNDCLASS wc = {0}; + wc.lpfnWndProc = OpenGLVersionCheck::supports_opengl2_wndproc; + wc.hInstance = (HINSTANCE)GetModuleHandle(nullptr); + wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND); + wc.lpszClassName = L"PrusaSlicer_opengl_version_check"; + wc.style = CS_OWNDC; + if (RegisterClass(&wc)) { + HWND hwnd = CreateWindowW(wc.lpszClassName, L"PrusaSlicer_opengl_version_check", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, 0, 0, wc.hInstance, (LPVOID)this); + if (hwnd) { + message_pump_exit = false; + while (GetMessage(&msg, NULL, 0, 0 ) > 0 && ! message_pump_exit) + DispatchMessage(&msg); + } + } + return this->success; + } - void unload_opengl_dll() - { - if (this->hOpenGL) { - BOOL released = FreeLibrary(this->hOpenGL); - if (released) - printf("System OpenGL library released\n"); - else - printf("System OpenGL library NOT released\n"); - this->hOpenGL = nullptr; - } - } + void unload_opengl_dll() + { + if (this->hOpenGL) { + BOOL released = FreeLibrary(this->hOpenGL); + if (released) + printf("System OpenGL library released\n"); + else + printf("System OpenGL library NOT released\n"); + this->hOpenGL = nullptr; + } + } - bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const - { - // printf("is_version_greater_or_equal_to, version: %s\n", version.c_str()); - std::vector tokens; - boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); - if (tokens.empty()) - return false; + bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const + { + // printf("is_version_greater_or_equal_to, version: %s\n", version.c_str()); + std::vector tokens; + boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on); + if (tokens.empty()) + return false; - std::vector numbers; - boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on); + std::vector numbers; + boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on); - unsigned int gl_major = 0; - unsigned int gl_minor = 0; - if (numbers.size() > 0) - gl_major = ::atoi(numbers[0].c_str()); - if (numbers.size() > 1) - gl_minor = ::atoi(numbers[1].c_str()); - // printf("Major: %d, minor: %d\n", gl_major, gl_minor); - if (gl_major < major) - return false; - else if (gl_major > major) - return true; - else - return gl_minor >= minor; - } + unsigned int gl_major = 0; + unsigned int gl_minor = 0; + if (numbers.size() > 0) + gl_major = ::atoi(numbers[0].c_str()); + if (numbers.size() > 1) + gl_minor = ::atoi(numbers[1].c_str()); + // printf("Major: %d, minor: %d\n", gl_major, gl_minor); + if (gl_major < major) + return false; + else if (gl_major > major) + return true; + else + return gl_minor >= minor; + } protected: - static bool message_pump_exit; + static bool message_pump_exit; - void check(HWND hWnd) - { - hOpenGL = LoadLibraryExW(L"opengl32.dll", nullptr, 0); - if (hOpenGL == nullptr) { - printf("Failed loading the system opengl32.dll\n"); - return; - } + void check(HWND hWnd) + { + hOpenGL = LoadLibraryExW(L"opengl32.dll", nullptr, 0); + if (hOpenGL == nullptr) { + printf("Failed loading the system opengl32.dll\n"); + return; + } - typedef HGLRC (WINAPI *Func_wglCreateContext)(HDC); - typedef BOOL (WINAPI *Func_wglMakeCurrent )(HDC, HGLRC); - typedef BOOL (WINAPI *Func_wglDeleteContext)(HGLRC); - typedef GLubyte* (WINAPI *Func_glGetString )(GLenum); + typedef HGLRC (WINAPI *Func_wglCreateContext)(HDC); + typedef BOOL (WINAPI *Func_wglMakeCurrent )(HDC, HGLRC); + typedef BOOL (WINAPI *Func_wglDeleteContext)(HGLRC); + typedef GLubyte* (WINAPI *Func_glGetString )(GLenum); - Func_wglCreateContext wglCreateContext = (Func_wglCreateContext)GetProcAddress(hOpenGL, "wglCreateContext"); - Func_wglMakeCurrent wglMakeCurrent = (Func_wglMakeCurrent) GetProcAddress(hOpenGL, "wglMakeCurrent"); - Func_wglDeleteContext wglDeleteContext = (Func_wglDeleteContext)GetProcAddress(hOpenGL, "wglDeleteContext"); - Func_glGetString glGetString = (Func_glGetString) GetProcAddress(hOpenGL, "glGetString"); + Func_wglCreateContext wglCreateContext = (Func_wglCreateContext)GetProcAddress(hOpenGL, "wglCreateContext"); + Func_wglMakeCurrent wglMakeCurrent = (Func_wglMakeCurrent) GetProcAddress(hOpenGL, "wglMakeCurrent"); + Func_wglDeleteContext wglDeleteContext = (Func_wglDeleteContext)GetProcAddress(hOpenGL, "wglDeleteContext"); + Func_glGetString glGetString = (Func_glGetString) GetProcAddress(hOpenGL, "glGetString"); - if (wglCreateContext == nullptr || wglMakeCurrent == nullptr || wglDeleteContext == nullptr || glGetString == nullptr) { - printf("Failed loading the system opengl32.dll: The library is invalid.\n"); - return; - } + if (wglCreateContext == nullptr || wglMakeCurrent == nullptr || wglDeleteContext == nullptr || glGetString == nullptr) { + printf("Failed loading the system opengl32.dll: The library is invalid.\n"); + return; + } PIXELFORMATDESCRIPTOR pfd = { @@ -149,152 +149,155 @@ protected: }; HDC ourWindowHandleToDeviceContext = ::GetDC(hWnd); - // Gdi32.dll - int letWindowsChooseThisPixelFormat = ::ChoosePixelFormat(ourWindowHandleToDeviceContext, &pfd); - // Gdi32.dll + // Gdi32.dll + int letWindowsChooseThisPixelFormat = ::ChoosePixelFormat(ourWindowHandleToDeviceContext, &pfd); + // Gdi32.dll SetPixelFormat(ourWindowHandleToDeviceContext, letWindowsChooseThisPixelFormat, &pfd); - // Opengl32.dll + // Opengl32.dll HGLRC glcontext = wglCreateContext(ourWindowHandleToDeviceContext); wglMakeCurrent(ourWindowHandleToDeviceContext, glcontext); // Opengl32.dll - const char *data = (const char*)glGetString(GL_VERSION); - if (data != nullptr) - this->version = data; - // printf("check -version: %s\n", version.c_str()); - data = (const char*)glGetString(0x8B8C); // GL_SHADING_LANGUAGE_VERSION - if (data != nullptr) - this->glsl_version = data; - data = (const char*)glGetString(GL_VENDOR); - if (data != nullptr) - this->vendor = data; - data = (const char*)glGetString(GL_RENDERER); - if (data != nullptr) - this->renderer = data; + const char *data = (const char*)glGetString(GL_VERSION); + if (data != nullptr) + this->version = data; + // printf("check -version: %s\n", version.c_str()); + data = (const char*)glGetString(0x8B8C); // GL_SHADING_LANGUAGE_VERSION + if (data != nullptr) + this->glsl_version = data; + data = (const char*)glGetString(GL_VENDOR); + if (data != nullptr) + this->vendor = data; + data = (const char*)glGetString(GL_RENDERER); + if (data != nullptr) + this->renderer = data; // Opengl32.dll wglDeleteContext(glcontext); - ::ReleaseDC(hWnd, ourWindowHandleToDeviceContext); + ::ReleaseDC(hWnd, ourWindowHandleToDeviceContext); this->success = true; - } + } - static LRESULT CALLBACK supports_opengl2_wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) - { - switch(message) - { - case WM_CREATE: - { - CREATESTRUCT *pCreate = reinterpret_cast(lParam); - OpenGLVersionCheck *ogl_data = reinterpret_cast(pCreate->lpCreateParams); - ogl_data->check(hWnd); - DestroyWindow(hWnd); - return 0; - } - case WM_NCDESTROY: - message_pump_exit = true; - return 0; - default: - return DefWindowProc(hWnd, message, wParam, lParam); - } - } + static LRESULT CALLBACK supports_opengl2_wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) + { + switch(message) + { + case WM_CREATE: + { + CREATESTRUCT *pCreate = reinterpret_cast(lParam); + OpenGLVersionCheck *ogl_data = reinterpret_cast(pCreate->lpCreateParams); + ogl_data->check(hWnd); + DestroyWindow(hWnd); + return 0; + } + case WM_NCDESTROY: + message_pump_exit = true; + return 0; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + } }; bool OpenGLVersionCheck::message_pump_exit = false; #endif /* SLIC3R_GUI */ extern "C" { - typedef int (__stdcall *Slic3rMainFunc)(int argc, wchar_t **argv); - Slic3rMainFunc slic3r_main = nullptr; + typedef int (__stdcall *Slic3rMainFunc)(int argc, wchar_t **argv); + Slic3rMainFunc slic3r_main = nullptr; } +extern "C" { + #ifdef SLIC3R_WRAPPER_NOCONSOLE int APIENTRY wWinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, PWSTR /* lpCmdLine */, int /* nCmdShow */) { - int argc; - wchar_t **argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + int argc; + wchar_t **argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); #else int wmain(int argc, wchar_t **argv) { #endif - std::vector argv_extended; - argv_extended.emplace_back(argv[0]); + std::vector argv_extended; + argv_extended.emplace_back(argv[0]); #ifdef SLIC3R_GUI - // Here one may push some additional parameters based on the wrapper type. - bool force_mesa = false; + // Here one may push some additional parameters based on the wrapper type. + bool force_mesa = false; #endif /* SLIC3R_GUI */ - for (int i = 1; i < argc; ++ i) { + for (int i = 1; i < argc; ++ i) { #ifdef SLIC3R_GUI - if (wcscmp(argv[i], L"--sw-renderer") == 0) - force_mesa = true; - else if (wcscmp(argv[i], L"--no-sw-renderer") == 0) - force_mesa = false; + if (wcscmp(argv[i], L"--sw-renderer") == 0) + force_mesa = true; + else if (wcscmp(argv[i], L"--no-sw-renderer") == 0) + force_mesa = false; #endif /* SLIC3R_GUI */ - argv_extended.emplace_back(argv[i]); - } - argv_extended.emplace_back(nullptr); + argv_extended.emplace_back(argv[i]); + } + argv_extended.emplace_back(nullptr); #ifdef SLIC3R_GUI - OpenGLVersionCheck opengl_version_check; - bool load_mesa = - // Forced from the command line. - force_mesa || - // Running over a rempote desktop, and the RemoteFX is not enabled, therefore Windows will only provide SW OpenGL 1.1 context. - // In that case, use Mesa. - ::GetSystemMetrics(SM_REMOTESESSION) || - // Try to load the default OpenGL driver and test its context version. - ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0); + OpenGLVersionCheck opengl_version_check; + bool load_mesa = + // Forced from the command line. + force_mesa || + // Running over a rempote desktop, and the RemoteFX is not enabled, therefore Windows will only provide SW OpenGL 1.1 context. + // In that case, use Mesa. + ::GetSystemMetrics(SM_REMOTESESSION) || + // Try to load the default OpenGL driver and test its context version. + ! opengl_version_check.load_opengl_dll() || ! opengl_version_check.is_version_greater_or_equal_to(2, 0); #endif /* SLIC3R_GUI */ - wchar_t path_to_exe[MAX_PATH + 1] = { 0 }; - ::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH); - wchar_t drive[_MAX_DRIVE]; - wchar_t dir[_MAX_DIR]; - wchar_t fname[_MAX_FNAME]; - wchar_t ext[_MAX_EXT]; - _wsplitpath(path_to_exe, drive, dir, fname, ext); - _wmakepath(path_to_exe, drive, dir, nullptr, nullptr); + wchar_t path_to_exe[MAX_PATH + 1] = { 0 }; + ::GetModuleFileNameW(nullptr, path_to_exe, MAX_PATH); + wchar_t drive[_MAX_DRIVE]; + wchar_t dir[_MAX_DIR]; + wchar_t fname[_MAX_FNAME]; + wchar_t ext[_MAX_EXT]; + _wsplitpath(path_to_exe, drive, dir, fname, ext); + _wmakepath(path_to_exe, drive, dir, nullptr, nullptr); #ifdef SLIC3R_GUI // https://wiki.qt.io/Cross_compiling_Mesa_for_Windows // http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/ - if (load_mesa) { - opengl_version_check.unload_opengl_dll(); - wchar_t path_to_mesa[MAX_PATH + 1] = { 0 }; - wcscpy(path_to_mesa, path_to_exe); - wcscat(path_to_mesa, L"mesa\\opengl32.dll"); - printf("Loading MESA OpenGL library: %S\n", path_to_mesa); - HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0); - if (hInstance_OpenGL == nullptr) { - printf("MESA OpenGL library was not loaded\n"); - } else - printf("MESA OpenGL library was loaded sucessfully\n"); - } + if (load_mesa) { + opengl_version_check.unload_opengl_dll(); + wchar_t path_to_mesa[MAX_PATH + 1] = { 0 }; + wcscpy(path_to_mesa, path_to_exe); + wcscat(path_to_mesa, L"mesa\\opengl32.dll"); + printf("Loading MESA OpenGL library: %S\n", path_to_mesa); + HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0); + if (hInstance_OpenGL == nullptr) { + printf("MESA OpenGL library was not loaded\n"); + } else + printf("MESA OpenGL library was loaded sucessfully\n"); + } #endif /* SLIC3R_GUI */ - wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 }; - wcscpy(path_to_slic3r, path_to_exe); - wcscat(path_to_slic3r, L"PrusaSlicer.dll"); + wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 }; + wcscpy(path_to_slic3r, path_to_exe); + wcscat(path_to_slic3r, L"PrusaSlicer.dll"); // printf("Loading Slic3r library: %S\n", path_to_slic3r); - HINSTANCE hInstance_Slic3r = LoadLibraryExW(path_to_slic3r, nullptr, 0); - if (hInstance_Slic3r == nullptr) { - printf("PrusaSlicer.dll was not loaded\n"); - return -1; - } + HINSTANCE hInstance_Slic3r = LoadLibraryExW(path_to_slic3r, nullptr, 0); + if (hInstance_Slic3r == nullptr) { + printf("PrusaSlicer.dll was not loaded\n"); + return -1; + } - // resolve function address here - slic3r_main = (Slic3rMainFunc)GetProcAddress(hInstance_Slic3r, + // resolve function address here + slic3r_main = (Slic3rMainFunc)GetProcAddress(hInstance_Slic3r, #ifdef _WIN64 - // there is just a single calling conversion, therefore no mangling of the function name. - "slic3r_main" + // there is just a single calling conversion, therefore no mangling of the function name. + "slic3r_main" #else // stdcall calling convention declaration - "_slic3r_main@8" + "_slic3r_main@8" #endif - ); - if (slic3r_main == nullptr) { - printf("could not locate the function slic3r_main in PrusaSlicer.dll\n"); - return -1; - } - // argc minus the trailing nullptr of the argv - return slic3r_main((int)argv_extended.size() - 1, argv_extended.data()); + ); + if (slic3r_main == nullptr) { + printf("could not locate the function slic3r_main in PrusaSlicer.dll\n"); + return -1; + } + // argc minus the trailing nullptr of the argv + return slic3r_main((int)argv_extended.size() - 1, argv_extended.data()); +} } diff --git a/src/avrdude/CMakeLists.txt b/src/avrdude/CMakeLists.txt index 0e9b9e6d4d..a1930ad5f9 100644 --- a/src/avrdude/CMakeLists.txt +++ b/src/avrdude/CMakeLists.txt @@ -74,6 +74,10 @@ if (MSVC) windows/unistd.cpp windows/getopt.c ) +elseif (MINGW) + set(AVRDUDE_SOURCES ${AVRDUDE_SOURCES} + windows/utf8.c + ) endif() add_executable(avrdude-conf-gen conf-generate.cpp) @@ -98,5 +102,7 @@ target_link_libraries(avrdude-slic3r avrdude) if (WIN32) target_compile_definitions(avrdude PRIVATE WIN32NATIVE=1) - target_include_directories(avrdude SYSTEM PRIVATE windows) # So that sources find the getopt.h windows drop-in + if(MSVC) + target_include_directories(avrdude SYSTEM PRIVATE windows) # So that sources find the getopt.h windows drop-in + endif(MSVC) endif() diff --git a/src/avrdude/lexer.c b/src/avrdude/lexer.c index f2d8adb4bc..46d88170f5 100644 --- a/src/avrdude/lexer.c +++ b/src/avrdude/lexer.c @@ -30,7 +30,7 @@ /* C99 systems have . Non-C99 systems may or may not. */ -#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#if (defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) /* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, * if you want the limit (max/min) macros for int types. diff --git a/src/avrdude/main-standalone.cpp b/src/avrdude/main-standalone.cpp index df6d79e136..f7cd6d1d4a 100644 --- a/src/avrdude/main-standalone.cpp +++ b/src/avrdude/main-standalone.cpp @@ -38,6 +38,10 @@ struct ArgvUtf8 : std::vector } }; +#endif + +#ifdef _MSC_VER + int wmain(int argc_w, wchar_t *argv_w[]) { ArgvUtf8 argv_utf8(argc_w, argv_w); diff --git a/src/avrdude/windows/unistd.h b/src/avrdude/windows/unistd.h index fe6a8fb871..c88b780bfa 100644 --- a/src/avrdude/windows/unistd.h +++ b/src/avrdude/windows/unistd.h @@ -63,7 +63,7 @@ extern "C" { #define STDOUT_FILENO 1 #define STDERR_FILENO 2 -#ifdef _MSC_VER +#if defined(_MSC_VER) && defined(__clang__) #include struct timezone; struct timeval; diff --git a/src/libigl/igl/SortableRow.h b/src/libigl/igl/SortableRow.h index 5f172987bf..182bf81343 100644 --- a/src/libigl/igl/SortableRow.h +++ b/src/libigl/igl/SortableRow.h @@ -1,9 +1,9 @@ // This file is part of libigl, a simple c++ geometry processing library. -// +// // Copyright (C) 2013 Alec Jacobson -// -// This Source Code Form is subject to the terms of the Mozilla Public License -// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// +// This Source Code Form is subject to the terms of the Mozilla Public License +// v. 2.0. If a copy of the MPL was not distributed with this file, You can // obtain one at http://mozilla.org/MPL/2.0/. #ifndef IGL_SORTABLE_ROW_H #define IGL_SORTABLE_ROW_H @@ -14,57 +14,53 @@ namespace igl { - // Templates: - // T should be a matrix that implements .size(), and operator(int i) - template - class SortableRow - { - public: - T data; - public: - SortableRow():data(){}; - SortableRow(const T & data):data(data){}; - bool operator<(const SortableRow & that) const - { - // Get reference so that I can use parenthesis - const SortableRow & THIS = *this; +// Templates: +// T should be a matrix that implements .size(), and operator(int i) +template +class SortableRow +{ +public: + T data; +public: + SortableRow():data(){}; + SortableRow(const T & data):data(data){}; + bool operator<(const SortableRow & that) const + { // Lexicographical - int minc = (THIS.data.size() < that.data.size()? - THIS.data.size() : that.data.size()); + int minc = (this->data.size() < that.data.size()? + this->data.size() : that.data.size()); // loop over columns for(int i = 0;idata(i) == that.data(i)) + { + continue; + } + return this->data(i) < that.data(i); } // All characters the same, comes done to length - return THIS.data.size() & THIS = *this; - if(THIS.data.size() != that.data.size()) + return this->data.size()data.size() != that.data.size()) { - return false; - } - for(int i = 0;idata.size();i++) + { + if(this->data(i) != that.data(i)) + { + return false; + } } return true; - }; - bool operator!=(const SortableRow & that) const - { + }; + bool operator!=(const SortableRow & that) const + { return !(*this == that); - }; - }; + }; +}; } #endif diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index 06afbd1872..56330e15e7 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -337,7 +337,15 @@ merge(const TMultiShape& shapes) //#define DISABLE_BOOST_SERIALIZE //#define DISABLE_BOOST_UNSERIALIZE +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif // All other operators and algorithms are implemented with boost #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif #endif // CLIPPER_BACKEND_HPP diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index ed599d11da..1b7451f8f3 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -133,8 +133,18 @@ protected: PConfig m_pconf; // Placement configuration TBin m_bin; double m_bin_area; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif SpatIndex m_rtree; // spatial index for the normal (bigger) objects SpatIndex m_smallsrtree; // spatial index for only the smaller items +#ifdef _MSC_VER +#pragma warning(pop) +#endif + double m_norm; // A coefficient to scale distances MultiPolygon m_merged_pile; // The already merged pile (vector of items) Box m_pilebb; // The bounding box of the merged pile. diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 4842b51239..1ebd922e20 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -5,6 +5,10 @@ include(PrecompiledHeader) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libslic3r_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h @ONLY) +if (MINGW) + add_compile_options(-Wa,-mbig-obj) +endif () + add_library(libslic3r STATIC pchheader.cpp pchheader.hpp @@ -70,7 +74,7 @@ add_library(libslic3r STATIC GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp GCode/PostProcessor.cpp - GCode/PostProcessor.hpp + GCode/PostProcessor.hpp # GCode/PressureEqualizer.cpp # GCode/PressureEqualizer.hpp GCode/PreviewData.cpp diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 538127810d..d4006120d2 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -102,45 +102,60 @@ private: class ToolOrdering { public: - ToolOrdering() {} + ToolOrdering() {} - // For the use case when each object is printed separately - // (print.config.complete_objects is true). - ToolOrdering(const PrintObject &object, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false); + // For the use case when each object is printed separately + // (print.config.complete_objects is true). + ToolOrdering(const PrintObject &object, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false); - // For the use case when all objects are printed at once. - // (print.config.complete_objects is false). - ToolOrdering(const Print &print, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false); + // For the use case when all objects are printed at once. + // (print.config.complete_objects is false). + ToolOrdering(const Print &print, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false); - void clear() { m_layer_tools.clear(); } + void clear() { m_layer_tools.clear(); } - // Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed. - unsigned int first_extruder() const { return m_first_printing_extruder; } + // Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed. + unsigned int first_extruder() const { return m_first_printing_extruder; } - // Get the first extruder printing the layer_tools, returns -1 if there is no layer printed. - unsigned int last_extruder() const { return m_last_printing_extruder; } + // Get the first extruder printing the layer_tools, returns -1 if there is no layer printed. + unsigned int last_extruder() const { return m_last_printing_extruder; } - // For a multi-material print, the printing extruders are ordered in the order they shall be primed. - const std::vector& all_extruders() const { return m_all_printing_extruders; } + // For a multi-material print, the printing extruders are ordered in the order they shall be primed. + const std::vector& all_extruders() const { return m_all_printing_extruders; } - // Find LayerTools with the closest print_z. - LayerTools& tools_for_layer(coordf_t print_z); - const LayerTools& tools_for_layer(coordf_t print_z) const - { return *const_cast(&const_cast(this)->tools_for_layer(print_z)); } + template static auto tools_for_layer(Self& self, coordf_t print_z) -> decltype (*self.m_layer_tools.begin()) + { + auto it_layer_tools = std::lower_bound(self.m_layer_tools.begin(), self.m_layer_tools.end(), LayerTools(print_z - EPSILON)); + assert(it_layer_tools != self.m_layer_tools.end()); + coordf_t dist_min = std::abs(it_layer_tools->print_z - print_z); + for (++ it_layer_tools; it_layer_tools != self.m_layer_tools.end(); ++it_layer_tools) { + coordf_t d = std::abs(it_layer_tools->print_z - print_z); + if (d >= dist_min) + break; + dist_min = d; + } + -- it_layer_tools; + assert(dist_min < EPSILON); + return *it_layer_tools; + } - const LayerTools& front() const { return m_layer_tools.front(); } - const LayerTools& back() const { return m_layer_tools.back(); } - std::vector::const_iterator begin() const { return m_layer_tools.begin(); } - std::vector::const_iterator end() const { return m_layer_tools.end(); } - bool empty() const { return m_layer_tools.empty(); } - std::vector& layer_tools() { return m_layer_tools; } - bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().wipe_tower_partitions > 0; } + // Find LayerTools with the closest print_z. + LayerTools& tools_for_layer(coordf_t print_z) { return tools_for_layer(*this, print_z); } + const LayerTools& tools_for_layer(coordf_t print_z) const { return tools_for_layer(*this, print_z); } + + const LayerTools& front() const { return m_layer_tools.front(); } + const LayerTools& back() const { return m_layer_tools.back(); } + std::vector::const_iterator begin() const { return m_layer_tools.begin(); } + std::vector::const_iterator end() const { return m_layer_tools.end(); } + bool empty() const { return m_layer_tools.empty(); } + std::vector& layer_tools() { return m_layer_tools; } + bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().wipe_tower_partitions > 0; } private: - void initialize_layers(std::vector &zs); - void collect_extruders(const PrintObject &object); - void reorder_extruders(unsigned int last_extruder_id); - void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z); + void initialize_layers(std::vector &zs); + void collect_extruders(const PrintObject &object); + void reorder_extruders(unsigned int last_extruder_id); + void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z); void collect_extruder_statistics(bool prime_multi_material); std::vector m_layer_tools; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 304f6f749f..f38ef662d2 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -12,7 +12,7 @@ namespace Slic3r { -//! macro used to mark string used at localization, +//! macro used to mark string used at localization, //! return same string #define L(s) (s) #define _(s) Slic3r::I18N::translate(s) @@ -51,7 +51,7 @@ void PrintConfigDef::init_common_params() def->label = L("Bed shape"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) }); - + def = this->add("bed_custom_texture", coString); def->label = L("Bed custom texture"); def->mode = comAdvanced; @@ -85,8 +85,8 @@ void PrintConfigDef::init_common_params() "The gap closing operation may reduce the final print resolution, therefore it is advisable to keep the value reasonably low."); def->sidetext = L("mm"); def->min = 0; - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(0.049)); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.049)); def = this->add("print_host", coString); def->label = L("Hostname, IP or URL"); @@ -101,7 +101,7 @@ void PrintConfigDef::init_common_params() "the API Key or the password required for authentication."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); - + def = this->add("printhost_cafile", coString); def->label = L("HTTPS CA File"); def->tooltip = L("Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " @@ -117,9 +117,9 @@ void PrintConfigDef::init_fff_params() // Maximum extruder temperature, bumped to 1500 to support printing of glass. const int max_temp = 1500; - def = this->add("avoid_crossing_perimeters", coBool); + def = this->add("avoid_crossing_perimeters", coBool); def->label = L("Avoid crossing perimeters"); - def->tooltip = L("Optimize travel moves in order to minimize the crossing of perimeters. " + def->tooltip = L("Optimize travel moves in order to minimize the crossing of perimeters. " "This is mostly useful with Bowden extruders which suffer from oozing. " "This feature slows down both the print and the G-code generation."); def->mode = comExpert; @@ -178,7 +178,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Bridging angle override. If left to zero, the bridging angle will be calculated " "automatically. Otherwise the provided angle will be used for all bridges. " "Use 180° for zero angle."); - def->sidetext = L("°"); + def->sidetext = L("°"); def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); @@ -200,9 +200,9 @@ void PrintConfigDef::init_fff_params() "although default settings are usually good and you should experiment " "with cooling (use a fan) before tweaking this."); def->min = 0; - def->max = 2; + def->max = 2; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloat(1)); + def->set_default_value(new ConfigOptionFloat(1)); def = this->add("bridge_speed", coFloat); def->label = L("Bridges"); @@ -531,7 +531,7 @@ void PrintConfigDef::init_fff_params() "check filament diameter and your firmware E steps."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats { 1. }); - + def = this->add("extrusion_width", coFloatOrPercent); def->label = L("Default extrusion width"); def->category = L("Extrusion Width"); @@ -677,7 +677,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("This string is edited by RammingDialog and contains ramming specific parameters."); def->mode = comExpert; def->set_default_value(new ConfigOptionStrings { "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0|" - " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" }); + " 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" }); def = this->add("filament_unload_time", coFloats); def->label = L("Filament unload time"); @@ -743,7 +743,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("money/kg"); def->min = 0; def->set_default_value(new ConfigOptionFloats { 0. }); - + def = this->add("filament_settings_id", coStrings); def->set_default_value(new ConfigOptionStrings { "" }); def->cli = ConfigOptionDef::nocli; @@ -889,7 +889,7 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->max = max_temp; def->set_default_value(new ConfigOptionInts { 200 }); - + def = this->add("gap_fill_speed", coFloat); def->label = L("Gap fill"); def->category = L("Speed"); @@ -1072,85 +1072,85 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); - def = this->add("silent_mode", coBool); - def->label = L("Supports stealth mode"); - def->tooltip = L("The firmware supports stealth mode"); + def = this->add("silent_mode", coBool); + def->label = L("Supports stealth mode"); + def->tooltip = L("The firmware supports stealth mode"); def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(true)); + def->set_default_value(new ConfigOptionBool(true)); - const int machine_limits_opt_width = 7; - { - struct AxisDefault { - std::string name; - std::vector max_feedrate; - std::vector max_acceleration; - std::vector max_jerk; - }; - std::vector axes { - // name, max_feedrate, max_acceleration, max_jerk - { "x", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } }, - { "y", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } }, - { "z", { 12., 12. }, { 500., 200. }, { 0.2, 0.4 } }, - { "e", { 120., 120. }, { 10000., 5000. }, { 2.5, 2.5 } } - }; - for (const AxisDefault &axis : axes) { - std::string axis_upper = boost::to_upper_copy(axis.name); - // Add the machine feedrate limits for XYZE axes. (M203) - def = this->add("machine_max_feedrate_" + axis.name, coFloats); - def->full_label = (boost::format("Maximum feedrate %1%") % axis_upper).str(); - (void)L("Maximum feedrate X"); - (void)L("Maximum feedrate Y"); - (void)L("Maximum feedrate Z"); - (void)L("Maximum feedrate E"); - def->category = L("Machine limits"); - def->tooltip = (boost::format("Maximum feedrate of the %1% axis") % axis_upper).str(); - (void)L("Maximum feedrate of the X axis"); - (void)L("Maximum feedrate of the Y axis"); - (void)L("Maximum feedrate of the Z axis"); - (void)L("Maximum feedrate of the E axis"); - def->sidetext = L("mm/s"); - def->min = 0; - def->width = machine_limits_opt_width; + const int machine_limits_opt_width = 7; + { + struct AxisDefault { + std::string name; + std::vector max_feedrate; + std::vector max_acceleration; + std::vector max_jerk; + }; + std::vector axes { + // name, max_feedrate, max_acceleration, max_jerk + { "x", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } }, + { "y", { 500., 200. }, { 9000., 1000. }, { 10. , 10. } }, + { "z", { 12., 12. }, { 500., 200. }, { 0.2, 0.4 } }, + { "e", { 120., 120. }, { 10000., 5000. }, { 2.5, 2.5 } } + }; + for (const AxisDefault &axis : axes) { + std::string axis_upper = boost::to_upper_copy(axis.name); + // Add the machine feedrate limits for XYZE axes. (M203) + def = this->add("machine_max_feedrate_" + axis.name, coFloats); + def->full_label = (boost::format("Maximum feedrate %1%") % axis_upper).str(); + (void)L("Maximum feedrate X"); + (void)L("Maximum feedrate Y"); + (void)L("Maximum feedrate Z"); + (void)L("Maximum feedrate E"); + def->category = L("Machine limits"); + def->tooltip = (boost::format("Maximum feedrate of the %1% axis") % axis_upper).str(); + (void)L("Maximum feedrate of the X axis"); + (void)L("Maximum feedrate of the Y axis"); + (void)L("Maximum feedrate of the Z axis"); + (void)L("Maximum feedrate of the E axis"); + def->sidetext = L("mm/s"); + def->min = 0; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats(axis.max_feedrate)); - // Add the machine acceleration limits for XYZE axes (M201) - def = this->add("machine_max_acceleration_" + axis.name, coFloats); - def->full_label = (boost::format("Maximum acceleration %1%") % axis_upper).str(); - (void)L("Maximum acceleration X"); - (void)L("Maximum acceleration Y"); - (void)L("Maximum acceleration Z"); - (void)L("Maximum acceleration E"); - def->category = L("Machine limits"); - def->tooltip = (boost::format("Maximum acceleration of the %1% axis") % axis_upper).str(); - (void)L("Maximum acceleration of the X axis"); - (void)L("Maximum acceleration of the Y axis"); - (void)L("Maximum acceleration of the Z axis"); - (void)L("Maximum acceleration of the E axis"); - def->sidetext = L("mm/s²"); - def->min = 0; - def->width = machine_limits_opt_width; + def->set_default_value(new ConfigOptionFloats(axis.max_feedrate)); + // Add the machine acceleration limits for XYZE axes (M201) + def = this->add("machine_max_acceleration_" + axis.name, coFloats); + def->full_label = (boost::format("Maximum acceleration %1%") % axis_upper).str(); + (void)L("Maximum acceleration X"); + (void)L("Maximum acceleration Y"); + (void)L("Maximum acceleration Z"); + (void)L("Maximum acceleration E"); + def->category = L("Machine limits"); + def->tooltip = (boost::format("Maximum acceleration of the %1% axis") % axis_upper).str(); + (void)L("Maximum acceleration of the X axis"); + (void)L("Maximum acceleration of the Y axis"); + (void)L("Maximum acceleration of the Z axis"); + (void)L("Maximum acceleration of the E axis"); + def->sidetext = L("mm/s²"); + def->min = 0; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats(axis.max_acceleration)); - // Add the machine jerk limits for XYZE axes (M205) - def = this->add("machine_max_jerk_" + axis.name, coFloats); - def->full_label = (boost::format("Maximum jerk %1%") % axis_upper).str(); - (void)L("Maximum jerk X"); - (void)L("Maximum jerk Y"); - (void)L("Maximum jerk Z"); - (void)L("Maximum jerk E"); - def->category = L("Machine limits"); - def->tooltip = (boost::format("Maximum jerk of the %1% axis") % axis_upper).str(); - (void)L("Maximum jerk of the X axis"); - (void)L("Maximum jerk of the Y axis"); - (void)L("Maximum jerk of the Z axis"); - (void)L("Maximum jerk of the E axis"); - def->sidetext = L("mm/s"); - def->min = 0; - def->width = machine_limits_opt_width; + def->set_default_value(new ConfigOptionFloats(axis.max_acceleration)); + // Add the machine jerk limits for XYZE axes (M205) + def = this->add("machine_max_jerk_" + axis.name, coFloats); + def->full_label = (boost::format("Maximum jerk %1%") % axis_upper).str(); + (void)L("Maximum jerk X"); + (void)L("Maximum jerk Y"); + (void)L("Maximum jerk Z"); + (void)L("Maximum jerk E"); + def->category = L("Machine limits"); + def->tooltip = (boost::format("Maximum jerk of the %1% axis") % axis_upper).str(); + (void)L("Maximum jerk of the X axis"); + (void)L("Maximum jerk of the Y axis"); + (void)L("Maximum jerk of the Z axis"); + (void)L("Maximum jerk of the E axis"); + def->sidetext = L("mm/s"); + def->min = 0; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats(axis.max_jerk)); - } - } + def->set_default_value(new ConfigOptionFloats(axis.max_jerk)); + } + } // M205 S... [mm/sec] def = this->add("machine_min_extruding_rate", coFloats); @@ -1159,9 +1159,9 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Minimum feedrate when extruding (M205 S)"); def->sidetext = L("mm/s"); def->min = 0; - def->width = machine_limits_opt_width; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats{ 0., 0. }); + def->set_default_value(new ConfigOptionFloats{ 0., 0. }); // M205 T... [mm/sec] def = this->add("machine_min_travel_rate", coFloats); @@ -1170,9 +1170,9 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Minimum travel feedrate (M205 T)"); def->sidetext = L("mm/s"); def->min = 0; - def->width = machine_limits_opt_width; + def->width = machine_limits_opt_width; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionFloats{ 0., 0. }); + def->set_default_value(new ConfigOptionFloats{ 0., 0. }); // M204 S... [mm/sec^2] def = this->add("machine_max_acceleration_extruding", coFloats); @@ -1181,7 +1181,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Maximum acceleration when extruding (M204 S)"); def->sidetext = L("mm/s²"); def->min = 0; - def->width = machine_limits_opt_width; + def->width = machine_limits_opt_width; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); @@ -1192,7 +1192,7 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Maximum acceleration when retracting (M204 T)"); def->sidetext = L("mm/s²"); def->min = 0; - def->width = machine_limits_opt_width; + def->width = machine_limits_opt_width; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats{ 1500., 1250. }); @@ -1436,9 +1436,9 @@ void PrintConfigDef::init_fff_params() def->gui_flags = "serialized"; def->multiline = true; def->full_width = true; - def->height = 6; + def->height = 6; def->mode = comExpert; - def->set_default_value(new ConfigOptionStrings()); + def->set_default_value(new ConfigOptionStrings()); def = this->add("printer_model", coString); def->label = L("Printer type"); @@ -1470,7 +1470,7 @@ void PrintConfigDef::init_fff_params() def = this->add("print_settings_id", coString); def->set_default_value(new ConfigOptionString("")); def->cli = ConfigOptionDef::nocli; - + def = this->add("printer_settings_id", coString); def->set_default_value(new ConfigOptionString("")); def->cli = ConfigOptionDef::nocli; @@ -1510,7 +1510,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("%"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionPercents { 0. }); - + def = this->add("retract_layer_change", coBools); def->label = L("Retract on layer change"); def->tooltip = L("This flag enforces a retraction whenever a Z move is done."); @@ -1607,7 +1607,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Random")); def->enum_labels.push_back(L("Nearest")); def->enum_labels.push_back(L("Aligned")); - def->enum_labels.push_back(L("Rear")); + def->enum_labels.push_back(L("Rear")); def->mode = comSimple; def->set_default_value(new ConfigOptionEnum(spAligned)); @@ -1678,7 +1678,7 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(1)); - + def = this->add("slowdown_below_layer_time", coInts); def->label = L("Slow down if layer print time is below"); def->tooltip = L("If layer print time is estimated below this number of seconds, print moves " @@ -1774,7 +1774,7 @@ void PrintConfigDef::init_fff_params() def->label = L("Temperature variation"); def->tooltip = L("Temperature difference to be applied when an extruder is not active. " "Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."); - def->sidetext = "∆°C"; + def->sidetext = "∆°C"; def->min = -max_temp; def->max = max_temp; def->mode = comExpert; @@ -1816,7 +1816,7 @@ void PrintConfigDef::init_fff_params() def->label = L("Single Extruder Multi Material"); def->tooltip = L("The printer multiplexes filaments into a single hot end."); def->mode = comExpert; - def->set_default_value(new ConfigOptionBool(false)); + def->set_default_value(new ConfigOptionBool(false)); def = this->add("single_extruder_multi_material_priming", coBool); def->label = L("Prime all printing extruders"); @@ -1878,8 +1878,8 @@ void PrintConfigDef::init_fff_params() // def->min = 0; def->enum_values.push_back("0"); def->enum_values.push_back("0.2"); - def->enum_labels.push_back(L("0 (soluble)")); - def->enum_labels.push_back(L("0.2 (detachable)")); + def->enum_labels.push_back(L("0 (soluble)")); + def->enum_labels.push_back(L("0.2 (detachable)")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.2)); @@ -1968,7 +1968,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("rectilinear"); def->enum_values.push_back("rectilinear-grid"); def->enum_values.push_back("honeycomb"); - def->enum_labels.push_back(L("Rectilinear")); + def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Rectilinear grid")); def->enum_labels.push_back(L("Honeycomb")); def->mode = comAdvanced; @@ -2030,7 +2030,7 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->max = max_temp; def->set_default_value(new ConfigOptionInts { 200 }); - + def = this->add("thin_walls", coBool); def->label = L("Detect thin walls"); def->category = L("Layers and Perimeters"); @@ -2050,7 +2050,7 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInt(threads > 0 ? threads : 2)); def->cli == ConfigOptionDef::nocli; } - + def = this->add("toolchange_gcode", coString); def->label = L("Tool change G-code"); def->tooltip = L("This custom code is inserted at every extruder change. If you don't leave this empty, you are " @@ -2242,45 +2242,45 @@ void PrintConfigDef::init_fff_params() // Declare retract values for filament profile, overriding the printer's extruder profile. for (const char *opt_key : { - // floats - "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_restart_extra", "retract_before_travel", - // bools - "retract_layer_change", "wipe", - // percents - "retract_before_wipe"}) { - auto it_opt = options.find(opt_key); - assert(it_opt != options.end()); - def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type); - def->label = it_opt->second.label; - def->full_label = it_opt->second.full_label; - def->tooltip = it_opt->second.tooltip; - def->sidetext = it_opt->second.sidetext; - def->mode = it_opt->second.mode; - switch (def->type) { - case coFloats : def->set_default_value(new ConfigOptionFloatsNullable (static_cast(it_opt->second.default_value.get())->values)); break; - case coPercents : def->set_default_value(new ConfigOptionPercentsNullable(static_cast(it_opt->second.default_value.get())->values)); break; - case coBools : def->set_default_value(new ConfigOptionBoolsNullable (static_cast(it_opt->second.default_value.get())->values)); break; - default: assert(false); - } + // floats + "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_restart_extra", "retract_before_travel", + // bools + "retract_layer_change", "wipe", + // percents + "retract_before_wipe"}) { + auto it_opt = options.find(opt_key); + assert(it_opt != options.end()); + def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type); + def->label = it_opt->second.label; + def->full_label = it_opt->second.full_label; + def->tooltip = it_opt->second.tooltip; + def->sidetext = it_opt->second.sidetext; + def->mode = it_opt->second.mode; + switch (def->type) { + case coFloats : def->set_default_value(new ConfigOptionFloatsNullable (static_cast(it_opt->second.default_value.get())->values)); break; + case coPercents : def->set_default_value(new ConfigOptionPercentsNullable(static_cast(it_opt->second.default_value.get())->values)); break; + case coBools : def->set_default_value(new ConfigOptionBoolsNullable (static_cast(it_opt->second.default_value.get())->values)); break; + default: assert(false); + } } } void PrintConfigDef::init_extruder_retract_keys() { - m_extruder_retract_keys = { - "deretract_speed", - "retract_before_travel", - "retract_before_wipe", - "retract_layer_change", - "retract_length", - "retract_lift", - "retract_lift_above", - "retract_lift_below", - "retract_restart_extra", - "retract_speed", - "wipe" - }; - assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end())); + m_extruder_retract_keys = { + "deretract_speed", + "retract_before_travel", + "retract_before_wipe", + "retract_layer_change", + "retract_length", + "retract_lift", + "retract_lift_above", + "retract_lift_below", + "retract_restart_extra", + "retract_speed", + "wipe" + }; + assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end())); } void PrintConfigDef::init_sla_params() @@ -2374,7 +2374,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloats( { 1., 1. } )); - + def = this->add("absolute_correction", coFloat); def->label = L("Printer absolute correction"); def->full_label = L("Printer absolute correction"); @@ -2382,7 +2382,7 @@ void PrintConfigDef::init_sla_params() "to the sign of the correction."); def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0.0)); - + def = this->add("gamma_correction", coFloat); def->label = L("Printer gamma correction"); def->full_label = L("Printer gamma correction"); @@ -2393,7 +2393,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(1.0)); - + // SLA Material settings. def = this->add("initial_layer_height", coFloat); @@ -2560,7 +2560,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(1.0)); - + def = this->add("support_base_safety_distance", coFloat); def->label = L("Support base safety distance"); def->category = L("Supports"); @@ -2694,7 +2694,7 @@ void PrintConfigDef::init_sla_params() def->max = 90; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(45.0)); - + def = this->add("pad_object_gap", coFloat); def->label = L("Pad object gap"); def->category = L("Pad"); @@ -2705,7 +2705,7 @@ void PrintConfigDef::init_sla_params() def->max = 10; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(1)); - + def = this->add("pad_object_connector_stride", coFloat); def->label = L("Pad object connector stride"); def->category = L("Pad"); @@ -2715,7 +2715,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(10)); - + def = this->add("pad_object_connector_width", coFloat); def->label = L("Pad object connector width"); def->category = L("Pad"); @@ -2725,7 +2725,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0.5)); - + def = this->add("pad_object_connector_penetration", coFloat); def->label = L("Pad object connector penetration"); def->category = L("Pad"); @@ -2746,7 +2746,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va if (opt_key == "bottom_layer_speed") opt_key = "first_layer_speed"; try { float v = boost::lexical_cast(value); - if (v != 0) + if (v != 0) value = boost::lexical_cast(v*100) + "%"; } catch (boost::bad_lexical_cast &) { value = "0"; @@ -2786,14 +2786,14 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } else if (opt_key == "octoprint_apikey") { opt_key = "printhost_apikey"; } - + // Ignore the following obsolete configuration keys: static std::set ignore = { "duplicate_x", "duplicate_y", "gcode_arcs", "multiply_x", "multiply_y", - "support_material_tool", "acceleration", "adjust_overhang_flow", + "support_material_tool", "acceleration", "adjust_overhang_flow", "standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid", - "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", - "seal_position", "vibration_limit", "bed_size", + "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", + "seal_position", "vibration_limit", "bed_size", "print_center", "g0", "threads", "pressure_advance", "wipe_tower_per_color_wipe" #ifndef HAS_PRESSURE_EQUALIZER , "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative" @@ -2804,7 +2804,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va opt_key = ""; return; } - + if (! print_config_def.has(opt_key)) { opt_key = ""; return; @@ -2844,10 +2844,10 @@ void DynamicPrintConfig::normalize() // this->option("support_material_interface_extruder", true)->setInt(extruder); } } - + if (!this->has("solid_infill_extruder") && this->has("infill_extruder")) this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt()); - + if (this->has("spiral_vase") && this->opt("spiral_vase", true)->value) { { // this should be actually done only on the spiral layers instead of all @@ -2865,8 +2865,8 @@ void DynamicPrintConfig::normalize() std::string DynamicPrintConfig::validate() { // Full print config is initialized from the defaults. - const ConfigOption *opt = this->option("printer_technology", false); - auto printer_technology = (opt == nullptr) ? ptFFF : static_cast(dynamic_cast(opt)->value); + const ConfigOption *opt = this->option("printer_technology", false); + auto printer_technology = (opt == nullptr) ? ptFFF : static_cast(dynamic_cast(opt)->value); switch (printer_technology) { case ptFFF: { @@ -2890,7 +2890,7 @@ double PrintConfig::min_object_distance(const ConfigBase *config) { double extruder_clearance_radius = config->option("extruder_clearance_radius")->getFloat(); double duplicate_distance = config->option("duplicate_distance")->getFloat(); - + // min object distance is max(duplicate_distance, clearance_radius) return (config->option("complete_objects")->getBool() && extruder_clearance_radius > duplicate_distance) ? extruder_clearance_radius @@ -2919,7 +2919,7 @@ std::string FullPrintConfig::validate() for (double nd : this->nozzle_diameter.values) if (nd < 0.005) return "Invalid value for --nozzle-diameter"; - + // --perimeters if (this->perimeters.value < 0) return "Invalid value for --perimeters"; @@ -2929,8 +2929,8 @@ std::string FullPrintConfig::validate() return "Invalid value for --top-solid-layers"; if (this->bottom_solid_layers < 0) return "Invalid value for --bottom-solid-layers"; - - if (this->use_firmware_retraction.value && + + if (this->use_firmware_retraction.value && this->gcode_flavor.value != gcfSmoothie && this->gcode_flavor.value != gcfRepRap && this->gcode_flavor.value != gcfMarlin && @@ -2942,15 +2942,15 @@ std::string FullPrintConfig::validate() for (unsigned char wipe : this->wipe.values) if (wipe) return "--use-firmware-retraction is not compatible with --wipe"; - + // --gcode-flavor if (! print_config_def.get("gcode_flavor")->has_enum_value(this->gcode_flavor.serialize())) return "Invalid value for --gcode-flavor"; - + // --fill-pattern if (! print_config_def.get("fill_pattern")->has_enum_value(this->fill_pattern.serialize())) return "Invalid value for --fill-pattern"; - + // --top-fill-pattern if (! print_config_def.get("top_fill_pattern")->has_enum_value(this->top_fill_pattern.serialize())) return "Invalid value for --top-fill-pattern"; @@ -2963,7 +2963,7 @@ std::string FullPrintConfig::validate() if (fabs(this->fill_density.value - 100.) < EPSILON && ! print_config_def.get("top_fill_pattern")->has_enum_value(this->fill_pattern.serialize())) return "The selected fill pattern is not supposed to work at 100% density"; - + // --infill-every-layers if (this->infill_every_layers < 1) return "Invalid value for --infill-every-layers"; @@ -2971,11 +2971,11 @@ std::string FullPrintConfig::validate() // --skirt-height if (this->skirt_height < -1) // -1 means as tall as the object return "Invalid value for --skirt-height"; - + // --bridge-flow-ratio if (this->bridge_flow_ratio <= 0) return "Invalid value for --bridge-flow-ratio"; - + // extruder clearance if (this->extruder_clearance_radius <= 0) return "Invalid value for --extruder-clearance-radius"; @@ -3007,7 +3007,7 @@ std::string FullPrintConfig::validate() if (this->support_material || this->support_material_enforce_layers > 0) return "Spiral vase mode is not compatible with support material"; } - + // extrusion widths { double max_nozzle_diameter = 0.; @@ -3064,7 +3064,7 @@ std::string FullPrintConfig::validate() if (out_of_range) return std::string("Value out of range: " + opt_key); } - + // The configuration is valid. return ""; } @@ -3087,20 +3087,20 @@ StaticPrintConfig::StaticCache SLAFullPrint CLIActionsConfigDef::CLIActionsConfigDef() { ConfigOptionDef* def; - + // Actions: def = this->add("export_obj", coBool); def->label = L("Export OBJ"); def->tooltip = L("Export the model(s) as OBJ."); def->set_default_value(new ConfigOptionBool(false)); - + /* def = this->add("export_svg", coBool); def->label = L("Export SVG"); def->tooltip = L("Slice the model and export solid slices as SVG."); def->set_default_value(new ConfigOptionBool(false)); */ - + def = this->add("export_sla", coBool); def->label = L("Export SLA"); def->tooltip = L("Slice the model and export SLA printing layers as PNG."); @@ -3149,12 +3149,12 @@ CLIActionsConfigDef::CLIActionsConfigDef() def->label = L("Help (SLA options)"); def->tooltip = L("Show the full list of SLA print configuration options."); def->set_default_value(new ConfigOptionBool(false)); - + def = this->add("info", coBool); def->label = L("Output Model Info"); def->tooltip = L("Write information about the model to the console."); def->set_default_value(new ConfigOptionBool(false)); - + def = this->add("save", coString); def->label = L("Save config file"); def->tooltip = L("Save configuration to the specified file."); @@ -3164,35 +3164,35 @@ CLIActionsConfigDef::CLIActionsConfigDef() CLITransformConfigDef::CLITransformConfigDef() { ConfigOptionDef* def; - + // Transform options: def = this->add("align_xy", coPoint); def->label = L("Align XY"); def->tooltip = L("Align the model to the given point."); def->set_default_value(new ConfigOptionPoint(Vec2d(100,100))); - + def = this->add("cut", coFloat); def->label = L("Cut"); def->tooltip = L("Cut model at the given Z."); def->set_default_value(new ConfigOptionFloat(0)); - + /* def = this->add("cut_grid", coFloat); def->label = L("Cut"); def->tooltip = L("Cut model in the XY plane into tiles of the specified max size."); def->set_default_value(new ConfigOptionPoint()); - + def = this->add("cut_x", coFloat); def->label = L("Cut"); def->tooltip = L("Cut model at the given X."); def->set_default_value(new ConfigOptionFloat(0)); - + def = this->add("cut_y", coFloat); def->label = L("Cut"); def->tooltip = L("Cut model at the given Y."); def->set_default_value(new ConfigOptionFloat(0)); */ - + def = this->add("center", coPoint); def->label = L("Center"); def->tooltip = L("Center the print around the given center."); @@ -3201,12 +3201,12 @@ CLITransformConfigDef::CLITransformConfigDef() def = this->add("dont_arrange", coBool); def->label = L("Don't arrange"); def->tooltip = L("Do not rearrange the given models before merging and keep their original XY coordinates."); - + def = this->add("duplicate", coInt); def->label = L("Duplicate"); def->tooltip =L("Multiply copies by this factor."); def->min = 1; - + def = this->add("duplicate_grid", coPoint); def->label = L("Duplicate by grid"); def->tooltip = L("Multiply copies by creating a grid."); @@ -3219,22 +3219,22 @@ CLITransformConfigDef::CLITransformConfigDef() def = this->add("repair", coBool); def->label = L("Repair"); def->tooltip = L("Try to repair any non-manifold meshes (this option is implicitly added whenever we need to slice the model to perform the requested action)."); - + def = this->add("rotate", coFloat); def->label = L("Rotate"); def->tooltip = L("Rotation angle around the Z axis in degrees."); def->set_default_value(new ConfigOptionFloat(0)); - + def = this->add("rotate_x", coFloat); def->label = L("Rotate around X"); def->tooltip = L("Rotation angle around the X axis in degrees."); def->set_default_value(new ConfigOptionFloat(0)); - + def = this->add("rotate_y", coFloat); def->label = L("Rotate around Y"); def->tooltip = L("Rotation angle around the Y axis in degrees."); def->set_default_value(new ConfigOptionFloat(0)); - + def = this->add("scale", coFloatOrPercent); def->label = L("Scale"); def->tooltip = L("Scaling factor or percentage."); @@ -3243,7 +3243,7 @@ CLITransformConfigDef::CLITransformConfigDef() def = this->add("split", coBool); def->label = L("Split"); def->tooltip = L("Detect unconnected parts in the given model(s) and split them into separate objects."); - + def = this->add("scale_to_fit", coPoint3); def->label = L("Scale to Fit"); def->tooltip = L("Scale to fit the given volume."); @@ -3253,26 +3253,26 @@ CLITransformConfigDef::CLITransformConfigDef() CLIMiscConfigDef::CLIMiscConfigDef() { ConfigOptionDef* def; - + def = this->add("ignore_nonexistent_config", coBool); def->label = L("Ignore non-existent config files"); def->tooltip = L("Do not fail if a file supplied to --load does not exist."); - + def = this->add("load", coStrings); def->label = L("Load config file"); def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); - + def = this->add("output", coString); def->label = L("Output File"); def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file)."); def->cli = "output|o"; -/* +/* def = this->add("autosave", coString); def->label = L("Autosave"); def->tooltip = L("Automatically export current configuration to the specified file."); */ - + def = this->add("datadir", coString); def->label = L("Data directory"); def->tooltip = L("Load and store settings at the given directory. This is useful for maintaining different profiles or including configurations from a network storage."); @@ -3282,7 +3282,7 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->tooltip = L("Messages with severity lower or eqal to the loglevel will be printed out. 0:trace, 1:debug, 2:info, 3:warning, 4:error, 5:fatal"); def->min = 0; -#if defined(_MSC_VER) && defined(SLIC3R_GUI) +#if (defined(_MSC_VER) || defined(__MINGW32__)) && defined(SLIC3R_GUI) def = this->add("sw_renderer", coBool); def->label = L("Render with a software renderer"); def->tooltip = L("Render with a software renderer. The bundled MESA software renderer is loaded instead of the default OpenGL driver."); @@ -3298,15 +3298,15 @@ DynamicPrintAndCLIConfig::PrintAndCLIConfigDef DynamicPrintAndCLIConfig::s_def; void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std::string &value) const { - if (cli_actions_config_def .options.find(opt_key) == cli_actions_config_def .options.end() && - cli_transform_config_def.options.find(opt_key) == cli_transform_config_def.options.end() && - cli_misc_config_def .options.find(opt_key) == cli_misc_config_def .options.end()) { - PrintConfigDef::handle_legacy(opt_key, value); - } + if (cli_actions_config_def .options.find(opt_key) == cli_actions_config_def .options.end() && + cli_transform_config_def.options.find(opt_key) == cli_transform_config_def.options.end() && + cli_misc_config_def .options.find(opt_key) == cli_misc_config_def .options.end()) { + PrintConfigDef::handle_legacy(opt_key, value); + } } } #include CEREAL_REGISTER_TYPE(Slic3r::DynamicPrintConfig) -CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::DynamicConfig, Slic3r::DynamicPrintConfig) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::DynamicConfig, Slic3r::DynamicPrintConfig) diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index 78efd08067..36378df395 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -1,5 +1,5 @@ -#include "igl/random_points_on_mesh.h" -#include "igl/AABB.h" +//#include "igl/random_points_on_mesh.h" +//#include "igl/AABB.h" #include @@ -101,7 +101,7 @@ static std::vector make_layers( std::vector layers; layers.reserve(slices.size()); for (size_t i = 0; i < slices.size(); ++ i) - layers.emplace_back(i, heights[i]); + layers.emplace_back(i, heights[i]); // FIXME: calculate actual pixel area from printer config: //const float pixel_area = pow(wxGetApp().preset_bundle->project_config.option("display_width") / wxGetApp().preset_bundle->project_config.option("display_pixels_x"), 2.f); // @@ -114,47 +114,47 @@ static std::vector make_layers( if ((layer_id % 8) == 0) // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. throw_on_cancel(); - SLAAutoSupports::MyLayer &layer = layers[layer_id]; + SLAAutoSupports::MyLayer &layer = layers[layer_id]; const ExPolygons &islands = slices[layer_id]; //FIXME WTF? const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0])); - layer.islands.reserve(islands.size()); + layer.islands.reserve(islands.size()); for (const ExPolygon &island : islands) { float area = float(island.area() * SCALING_FACTOR * SCALING_FACTOR); if (area >= pixel_area) //FIXME this is not a correct centroid of a polygon with holes. - layer.islands.emplace_back(layer, island, get_extents(island.contour), Slic3r::unscale(island.contour.centroid()).cast(), area, height); + layer.islands.emplace_back(layer, island, get_extents(island.contour), Slic3r::unscale(island.contour.centroid()).cast(), area, height); } } }); - // Calculate overlap of successive layers. Link overlapping islands. - tbb::parallel_for(tbb::blocked_range(1, layers.size(), 8), - [&layers, &heights, throw_on_cancel](const tbb::blocked_range& range) { - for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) { - if ((layer_id % 2) == 0) - // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. - throw_on_cancel(); - SLAAutoSupports::MyLayer &layer_above = layers[layer_id]; - SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1]; + // Calculate overlap of successive layers. Link overlapping islands. + tbb::parallel_for(tbb::blocked_range(1, layers.size(), 8), + [&layers, &heights, throw_on_cancel](const tbb::blocked_range& range) { + for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) { + if ((layer_id % 2) == 0) + // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. + throw_on_cancel(); + SLAAutoSupports::MyLayer &layer_above = layers[layer_id]; + SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1]; //FIXME WTF? const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]); const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle))); const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports const float slope_offset = float(scale_(layer_height / std::tan(slope_angle))); - //FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands. - for (SLAAutoSupports::Structure &top : layer_above.islands) { - for (SLAAutoSupports::Structure &bottom : layer_below.islands) { + //FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands. + for (SLAAutoSupports::Structure &top : layer_above.islands) { + for (SLAAutoSupports::Structure &bottom : layer_below.islands) { float overlap_area = top.overlap_area(bottom); if (overlap_area > 0) { - top.islands_below.emplace_back(&bottom, overlap_area); + top.islands_below.emplace_back(&bottom, overlap_area); bottom.islands_above.emplace_back(&top, overlap_area); } } if (! top.islands_below.empty()) { Polygons top_polygons = to_polygons(*top.polygon); - Polygons bottom_polygons = top.polygons_below(); + Polygons bottom_polygons = top.polygons_below(); top.overhangs = diff_ex(top_polygons, bottom_polygons); if (! top.overhangs.empty()) { top.overhangs_area = 0.f; @@ -164,21 +164,21 @@ static std::vector make_layers( expolys_with_areas.emplace_back(&ex, area); top.overhangs_area += area; } - std::sort(expolys_with_areas.begin(), expolys_with_areas.end(), + std::sort(expolys_with_areas.begin(), expolys_with_areas.end(), [](const std::pair &p1, const std::pair &p2) { return p1.second > p2.second; }); ExPolygons overhangs_sorted; for (auto &p : expolys_with_areas) overhangs_sorted.emplace_back(std::move(*p.first)); - top.overhangs = std::move(overhangs_sorted); + top.overhangs = std::move(overhangs_sorted); top.overhangs_area *= float(SCALING_FACTOR * SCALING_FACTOR); top.overhangs_slopes = diff_ex(top_polygons, offset(bottom_polygons, slope_offset)); top.dangling_areas = diff_ex(top_polygons, offset(bottom_polygons, between_layers_offset)); } } - } - } - }); + } + } + }); return layers; } @@ -207,14 +207,14 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: support_force_bottom[i] = layer_bottom->islands[i].supports_force_total(); } for (Structure &top : layer_top->islands) - for (Structure::Link &bottom_link : top.islands_below) { + for (Structure::Link &bottom_link : top.islands_below) { Structure &bottom = *bottom_link.island; //float centroids_dist = (bottom.centroid - top.centroid).norm(); // Penalization resulting from centroid offset: // bottom.supports_force *= std::min(1.f, 1.f - std::min(1.f, (1600.f * layer_height) * centroids_dist * centroids_dist / bottom.area)); float &support_force = support_force_bottom[&bottom - layer_bottom->islands.data()]; //FIXME this condition does not reflect a bifurcation into a one large island and one tiny island well, it incorrectly resets the support force to zero. -// One should rather work with the overlap area vs overhang area. +// One should rather work with the overlap area vs overhang area. // support_force *= std::min(1.f, 1.f - std::min(1.f, 0.1f * centroids_dist * centroids_dist / bottom.area)); // Penalization resulting from increasing polygon area: support_force *= std::min(1.f, 20.f * bottom.area / top.area); @@ -224,10 +224,10 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: for (Structure &below : layer_bottom->islands) { float below_support_force = support_force_bottom[&below - layer_bottom->islands.data()]; float above_overlap_area = 0.f; - for (Structure::Link &above_link : below.islands_above) - above_overlap_area += above_link.overlap_area; - for (Structure::Link &above_link : below.islands_above) - above_link.island->supports_force_inherited += below_support_force * above_link.overlap_area / above_overlap_area; + for (Structure::Link &above_link : below.islands_above) + above_overlap_area += above_link.overlap_area; + for (Structure::Link &above_link : below.islands_above) + above_link.island->supports_force_inherited += below_support_force * above_link.overlap_area / above_overlap_area; } } // Now iterate over all polygons and append new points if needed. @@ -266,7 +266,7 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: } std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng) -{ +{ // Triangulate the polygon with holes into triplets of 3D points. std::vector triangles = Slic3r::triangulate_expolygon_2f(expoly); @@ -347,7 +347,7 @@ static inline std::vector poisson_disk_from_samples(const std::vector(); raw_samples_sorted.emplace_back(sample); } - std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs) + std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs) { return lhs.cell_id.x() < rhs.cell_id.x() || (lhs.cell_id.x() == rhs.cell_id.x() && lhs.cell_id.y() < rhs.cell_id.y()); }); struct PoissonDiskGridEntry { @@ -464,10 +464,10 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru //FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon. std::random_device rd; std::mt19937 rng(rd()); - std::vector raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng); + std::vector raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng); std::vector poisson_samples; for (size_t iter = 0; iter < 4; ++ iter) { - poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius, + poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius, [&structure, &grid3d, min_spacing](const Vec2f &pos) { return grid3d.collides_with(pos, &structure, min_spacing); }); @@ -481,21 +481,21 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru } #ifdef SLA_AUTOSUPPORTS_DEBUG - { - static int irun = 0; - Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands)); + { + static int irun = 0; + Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands)); for (const ExPolygon &island : islands) svg.draw(island); - for (const Vec2f &pt : raw_samples) - svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "red"); - for (const Vec2f &pt : poisson_samples) - svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "blue"); - } + for (const Vec2f &pt : raw_samples) + svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "red"); + for (const Vec2f &pt : poisson_samples) + svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "blue"); + } #endif /* NDEBUG */ // assert(! poisson_samples.empty()); if (poisson_samples_target < poisson_samples.size()) { - std::shuffle(poisson_samples.begin(), poisson_samples.end(), rng); + std::shuffle(poisson_samples.begin(), poisson_samples.end(), rng); poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end()); } for (const Vec2f &pt : poisson_samples) { diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 6f4ac6c21a..5e4820988e 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -85,7 +85,7 @@ using Portion = std::tuple; // Set this to true to enable full parallelism in this module. // Only the well tested parts will be concurrent if this is set to false. -const constexpr bool USE_FULL_CONCURRENCY = false; +const constexpr bool USE_FULL_CONCURRENCY = true; template struct _ccr {}; @@ -580,13 +580,13 @@ struct CompactBridge { double fa = 2*PI/steps; auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); for(auto& p : upperball.points) p += startp; - + if(endball) { auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); for(auto& p : lowerball.points) p += endp; mesh.merge(lowerball); } - + mesh.merge(upperball); } }; @@ -604,15 +604,15 @@ struct Pad { double ground_level, const PoolConfig& pcfg) : cfg(pcfg), - zlevel(ground_level + + zlevel(ground_level + sla::get_pad_fullheight(pcfg) - sla::get_pad_elevation(pcfg)) { Polygons basep; auto &thr = cfg.throw_on_cancel; - + thr(); - + // Get a sample for the pad from the support mesh { ExPolygons platetmp; @@ -626,20 +626,20 @@ struct Pad { for (const ExPolygon &bp : platetmp) basep.emplace_back(std::move(bp.contour)); } - + if(pcfg.embed_object) { - + // If the zero elevation mode is ON, we need to process the model // base silhouette. Create the offsetted version and punch the // breaksticks across its perimeter. - + ExPolygons modelbase_offs = modelbase; - + if (pcfg.embed_object.object_gap_mm > 0.0) modelbase_offs = offset_ex(modelbase_offs, float(scaled(pcfg.embed_object.object_gap_mm))); - + // Create a spatial index of the support silhouette polygons. // This will be used to check for intersections with the model // silhouette polygons. If there is no intersection, then a certain @@ -653,35 +653,35 @@ struct Pad { bindex.insert(bb, idx++); } } - + ExPolygons concaveh = offset_ex( concave_hull(basep, pcfg.max_merge_distance_mm, thr), scaled(pcfg.min_wall_thickness_mm)); - + // Punching the breaksticks across the offsetted polygon perimeters auto pad_stickholes = reserve_vector(modelbase.size()); for(auto& poly : modelbase_offs) { - + bool overlap = false; for (const ExPolygon &p : concaveh) overlap = overlap || poly.overlaps(p); - + auto bb = poly.contour.bounding_box(); bb.offset(scaled(pcfg.min_wall_thickness_mm)); - + std::vector qres = bindex.query(bb, BoxIndex::qtIntersects); - + if (!qres.empty() || overlap) { - + // The model silhouette polygon 'poly' HAS an intersection // with the support silhouettes. Include this polygon // in the pad holes with the breaksticks and merge the // original (offsetted) version with the rest of the pad // base plate. - + basep.emplace_back(poly.contour); - + // The holes of 'poly' will become positive parts of the // pad, so they has to be checked for intersections as well // and erased if there is no intersection with the supports @@ -693,7 +693,7 @@ struct Pad { else ++it; } - + // Punch the breaksticks sla::breakstick_holes( poly, @@ -701,11 +701,11 @@ struct Pad { pcfg.embed_object.stick_stride_mm, pcfg.embed_object.stick_width_mm, pcfg.embed_object.stick_penetration_mm); - + pad_stickholes.emplace_back(poly); } } - + create_base_pool(basep, tmesh, pad_stickholes, cfg); } else { for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); @@ -775,78 +775,78 @@ class SLASupportTree::Impl { // For heads it is beneficial to use the same IDs as for the support points. std::vector m_heads; std::vector m_head_indices; - + std::vector m_pillars; std::vector m_junctions; std::vector m_bridges; std::vector m_compact_bridges; Controller m_ctl; - + Pad m_pad; - + using Mutex = ccr::Mutex; - + mutable Mutex m_mutex; mutable TriangleMesh meshcache; mutable bool meshcache_valid = false; mutable double model_height = 0; // the full height of the model - + public: double ground_level = 0; - + Impl() = default; inline Impl(const Controller& ctl): m_ctl(ctl) {} - + const Controller& ctl() const { return m_ctl; } - + template Head& add_head(unsigned id, Args&&... args) { std::lock_guard lk(m_mutex); m_heads.emplace_back(std::forward(args)...); m_heads.back().id = id; - + if (id >= m_head_indices.size()) m_head_indices.resize(id + 1); m_head_indices[id] = m_heads.size() - 1; - + meshcache_valid = false; return m_heads.back(); } - + template Pillar& add_pillar(unsigned headid, Args&&... args) { std::lock_guard lk(m_mutex); - + assert(headid < m_head_indices.size()); Head &head = m_heads[m_head_indices[headid]]; - + m_pillars.emplace_back(head, std::forward(args)...); Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); head.pillar_id = pillar.id; pillar.start_junction_id = head.id; pillar.starts_from_head = true; - + meshcache_valid = false; return m_pillars.back(); } - + void increment_bridges(const Pillar& pillar) { std::lock_guard lk(m_mutex); assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); - - if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) + + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) m_pillars[size_t(pillar.id)].bridges++; } - + void increment_links(const Pillar& pillar) { std::lock_guard lk(m_mutex); assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); - + if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) - m_pillars[size_t(pillar.id)].links++; + m_pillars[size_t(pillar.id)].links++; } - + template Pillar& add_pillar(Args&&...args) { std::lock_guard lk(m_mutex); @@ -857,30 +857,30 @@ public: meshcache_valid = false; return m_pillars.back(); } - + const Head& pillar_head(long pillar_id) const { std::lock_guard lk(m_mutex); assert(pillar_id >= 0 && pillar_id < long(m_pillars.size())); - + const Pillar& p = m_pillars[size_t(pillar_id)]; assert(p.starts_from_head && p.start_junction_id >= 0); assert(size_t(p.start_junction_id) < m_head_indices.size()); - + return m_heads[m_head_indices[p.start_junction_id]]; } - + const Pillar& head_pillar(unsigned headid) const { std::lock_guard lk(m_mutex); assert(headid < m_head_indices.size()); - + const Head& h = m_heads[m_head_indices[headid]]; assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); - + return m_pillars[size_t(h.pillar_id)]; } - + template const Junction& add_junction(Args&&... args) { std::lock_guard lk(m_mutex); @@ -889,7 +889,7 @@ public: meshcache_valid = false; return m_junctions.back(); } - + template const Bridge& add_bridge(Args&&... args) { std::lock_guard lk(m_mutex); @@ -898,7 +898,7 @@ public: meshcache_valid = false; return m_bridges.back(); } - + template const CompactBridge& add_compact_bridge(Args&&...args) { std::lock_guard lk(m_mutex); @@ -907,30 +907,30 @@ public: meshcache_valid = false; return m_compact_bridges.back(); } - + Head &head(unsigned id) { std::lock_guard lk(m_mutex); assert(id < m_head_indices.size()); - + meshcache_valid = false; return m_heads[m_head_indices[id]]; } - + inline size_t pillarcount() const { std::lock_guard lk(m_mutex); return m_pillars.size(); } - + template inline IntegerOnly pillar(T id) const { std::lock_guard lk(m_mutex); assert(id >= 0 && size_t(id) < m_pillars.size() && size_t(id) < std::numeric_limits::max()); - + return m_pillars[size_t(id)]; } - + const Pad &create_pad(const TriangleMesh &object_supports, const ExPolygons & modelbase, const PoolConfig & cfg) @@ -938,86 +938,88 @@ public: m_pad = Pad(object_supports, modelbase, ground_level, cfg); return m_pad; } - + void remove_pad() { m_pad = Pad(); } - + const Pad& pad() const { return m_pad; } - + // WITHOUT THE PAD!!! const TriangleMesh &merged_mesh() const { if (meshcache_valid) return meshcache; - + + std::cout << "merging mesh" << std::endl; + Contour3D merged; - + for (auto &head : m_heads) { if (m_ctl.stopcondition()) break; if (head.is_valid()) merged.merge(head.mesh); } - + for (auto &stick : m_pillars) { if (m_ctl.stopcondition()) break; merged.merge(stick.mesh); merged.merge(stick.base); } - + for (auto &j : m_junctions) { if (m_ctl.stopcondition()) break; merged.merge(j.mesh); } - + for (auto &cb : m_compact_bridges) { if (m_ctl.stopcondition()) break; merged.merge(cb.mesh); } - + for (auto &bs : m_bridges) { if (m_ctl.stopcondition()) break; merged.merge(bs.mesh); } - + if (m_ctl.stopcondition()) { // In case of failure we have to return an empty mesh meshcache = TriangleMesh(); return meshcache; } - + meshcache = mesh(merged); - + // The mesh will be passed by const-pointer to TriangleMeshSlicer, // which will need this. if (!meshcache.empty()) meshcache.require_shared_vertices(); - + BoundingBoxf3 &&bb = meshcache.bounding_box(); model_height = bb.max(Z) - bb.min(Z); - + meshcache_valid = true; return meshcache; } - + // WITH THE PAD double full_height() const { if (merged_mesh().empty() && !pad().empty()) return get_pad_fullheight(pad().cfg); - + double h = mesh_height(); if (!pad().empty()) h += sla::get_pad_elevation(pad().cfg); return h; } - + // WITHOUT THE PAD!!! double mesh_height() const { if (!meshcache_valid) merged_mesh(); return model_height; } - + // Intended to be called after the generation is fully complete void merge_and_cleanup() { merged_mesh(); // in case the mesh is not generated, it should be... - + // Doing clear() does not garantee to release the memory. m_heads = {}; m_head_indices = {}; @@ -1194,7 +1196,7 @@ class SLASupportTree::Algorithm { // Now a and b vectors are perpendicular to v and to each other. // Together they define the plane where we have to iterate with the // given angles in the 'phis' vector - ccr_par::enumerate(phis.begin(), phis.end(), + ccr_seq::enumerate(phis.begin(), phis.end(), [&hits, &m, sd, r_pin, r_back, s, a, b, c] (double phi, size_t i) { @@ -1296,8 +1298,8 @@ class SLASupportTree::Algorithm { // Hit results std::array hits; - - ccr_par::enumerate(phis.begin(), phis.end(), + + ccr_seq::enumerate(phis.begin(), phis.end(), [&m, a, b, sd, dir, r, s, ins_check, &hits] (double phi, size_t i) { @@ -1431,11 +1433,11 @@ class SLASupportTree::Algorithm { // For connecting a head to a nearby pillar. bool connect_to_nearpillar(const Head& head, long nearpillar_id) { - + auto nearpillar = [this, nearpillar_id]() { return m_result.pillar(nearpillar_id); }; - + if (nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; Vec3d headjp = head.junction_point(); @@ -1539,7 +1541,7 @@ class SLASupportTree::Algorithm { return nearest_id >= 0; } - + // This is a proxy function for pillar creation which will mind the gap // between the pad and the model bottom in zero elevation mode. void create_ground_pillar(const Vec3d &jp, @@ -1594,7 +1596,7 @@ class SLASupportTree::Algorithm { endp = jp + SQR2 * mv * dir; Vec3d pgnd = {endp(X), endp(Y), gndlvl}; can_add_base = result.score > min_dist; - + double gnd_offs = m_mesh.ground_level_offset(); auto abort_in_shame = [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]() @@ -1612,7 +1614,7 @@ class SLASupportTree::Algorithm { if (endp(Z) < gndlvl) endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off else { - + auto hit = bridge_mesh_intersect(endp, DOWN, radius); if (!std::isinf(hit.distance())) abort_in_shame(); @@ -1636,7 +1638,7 @@ class SLASupportTree::Algorithm { m_result.add_pillar(unsigned(head_id), jp, radius); } } - + if (normal_mode) { Pillar &plr = head_id >= 0 ? m_result.add_pillar(unsigned(head_id), @@ -1648,8 +1650,8 @@ class SLASupportTree::Algorithm { plr.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); pillar_id = plr.id; - } - + } + if(pillar_id >= 0) // Save the pillar endpoint in the spatial index m_pillar_index.insert(endp, pillar_id); } @@ -1716,52 +1718,52 @@ public: using libnest2d::opt::initvals; using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::StopCriteria; - + ccr::Mutex mutex; auto addfn = [&mutex](PtIndices &container, unsigned val) { std::lock_guard lk(mutex); container.emplace_back(val); }; - + ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), [this, &nmls, addfn](unsigned fidx, size_t i) { m_thr(); - + auto n = nmls.row(i); - + // for all normals we generate the spherical coordinates and // saturate the polar angle to 45 degrees from the bottom then // convert back to standard coordinates to get the new normal. // Then we just create a quaternion from the two normals // (Quaternion::FromTwoVectors) and apply the rotation to the // arrow head. - + double z = n(2); double r = 1.0; // for normalized vector double polar = std::acos(z / r); double azimuth = std::atan2(n(1), n(0)); - + // skip if the tilt is not sane if(polar >= PI - m_cfg.normal_cutoff_angle) { - + // We saturate the polar angle to 3pi/4 polar = std::max(polar, 3*PI / 4); - + // save the head (pinpoint) position Vec3d hp = m_points.row(fidx); - + double w = m_cfg.head_width_mm + m_cfg.head_back_radius_mm + 2*m_cfg.head_front_radius_mm; - + double pin_r = double(m_support_pts[fidx].head_front_radius); - + // Reassemble the now corrected normal auto nn = Vec3d(std::cos(azimuth) * std::sin(polar), std::sin(azimuth) * std::sin(polar), std::cos(polar)).normalized(); - + // check available distance EigenMesh3D::hit_result t = pinhead_mesh_intersect(hp, // touching point @@ -1769,20 +1771,20 @@ public: pin_r, m_cfg.head_back_radius_mm, w); - + if(t.distance() <= w) { - + // Let's try to optimize this angle, there might be a // viable normal that doesn't collide with the model // geometry and its very close to the default. - + StopCriteria stc; stc.max_iterations = m_cfg.optimizer_max_iterations; stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; stc.stop_score = w; // space greater than w is enough GeneticOptimizer solver(stc); solver.seed(0); // we want deterministic behavior - + auto oresult = solver.optimize_max( [this, pin_r, w, hp](double plr, double azm) { @@ -1799,7 +1801,7 @@ public: bound(3*PI/4, PI), // Must not exceed the tilt limit bound(-PI, PI) // azimuth can be a full search ); - + if(oresult.score > w) { polar = std::get<0>(oresult.optimum); azimuth = std::get<1>(oresult.optimum); @@ -1809,10 +1811,10 @@ public: t = oresult.score; } } - + // save the verified and corrected normal m_support_nmls.row(fidx) = nn; - + if (t.distance() > w) { // Check distance from ground, we might have zero elevation. if (hp(Z) + w * nn(Z) < m_result.ground_level) { @@ -1889,7 +1891,7 @@ public: // from each other in the XY plane to not cross their pillar bases // These clusters of support points will join in one pillar, // possibly in their centroid support point. - + auto pointfn = [this](unsigned i) { return m_result.head(i).junction_point(); }; @@ -2178,7 +2180,7 @@ public: m_pillar_index.insert(pillar.endpoint(), pillid); } } - + // Helper function for interconnect_pillars where pairs of already connected // pillars should be checked for not to be processed again. This can be done // in O(log) or even constant time with a set or an unordered set of hash @@ -2187,17 +2189,17 @@ public: template static I pairhash(I a, I b) { using std::ceil; using std::log2; using std::max; using std::min; - + static_assert(std::is_integral::value, "This function works only for integral types."); I g = min(a, b), l = max(a, b); - + auto bits_g = g ? int(ceil(log2(g))) : 0; // Assume the hash will fit into the output variable assert((l ? (ceil(log2(l))) : 0) + bits_g < int(sizeof(I) * CHAR_BIT)); - + return (l << bits_g) + g; } @@ -2217,7 +2219,7 @@ public: double min_height_ratio = 0.5; std::set pairs; - + // A function to connect one pillar with its neighbors. THe number of // neighbors is given in the configuration. This function if called // for every pillar in the pillar index. A pair of pillar will not @@ -2229,7 +2231,7 @@ public: Vec3d qp = el.first; // endpoint of the pillar const Pillar& pillar = m_result.pillar(el.second); // actual pillar - + // Get the max number of neighbors a pillar should connect to unsigned neighbors = m_cfg.pillar_cascade_neighbors; @@ -2255,10 +2257,10 @@ public: // Get unique hash for the given pair (order doesn't matter) auto hashval = pairhash(a, b); - + // Search for the pair amongst the remembered pairs if(pairs.find(hashval) != pairs.end()) continue; - + const Pillar& neighborpillar = m_result.pillar(re.second); // this neighbor is occupied, skip @@ -2283,10 +2285,10 @@ public: if(pillar.links >= neighbors) break; } }; - + // Run the cascade for the pillars in the index m_pillar_index.foreach(cascadefn); - + // We would be done here if we could allow some pillars to not be // connected with any neighbors. But this might leave the support tree // unprintable. @@ -2294,16 +2296,16 @@ public: // The current solution is to insert additional pillars next to these // lonely pillars. One or even two additional pillar might get inserted // depending on the length of the lonely pillar. - + size_t pillarcount = m_result.pillarcount(); - + // Again, go through all pillars, this time in the whole support tree // not just the index. for(size_t pid = 0; pid < pillarcount; pid++) { auto pillar = [this, pid]() { return m_result.pillar(pid); }; - + // Decide how many additional pillars will be needed: - + unsigned needpillars = 0; if (pillar().bridges > m_cfg.max_bridges_on_pillar) needpillars = 3; @@ -2332,7 +2334,7 @@ public: double gnd = m_result.ground_level; double min_dist = m_cfg.pillar_base_safety_distance_mm + m_cfg.base_radius_mm + EPSILON; - + while(!found && alpha < 2*PI) { for (unsigned n = 0; n < needpillars && (!n || canplace[n - 1]); @@ -2343,11 +2345,11 @@ public: s(X) += std::cos(a) * r; s(Y) += std::sin(a) * r; spts[n] = s; - - // Check the path vertically down + + // Check the path vertically down auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); Vec3d gndsp{s(X), s(Y), gnd}; - + // If the path is clear, check for pillar base collisions canplace[n] = std::isinf(hr.distance()) && std::sqrt(m_mesh.squared_distance(gndsp)) > @@ -2365,7 +2367,7 @@ public: newpills.reserve(needpillars); if(found) for(unsigned n = 0; n < needpillars; n++) { - Vec3d s = spts[n]; + Vec3d s = spts[n]; Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); @@ -2447,8 +2449,8 @@ public: m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist)); } } - - void merge_result() { m_result.merge_and_cleanup(); } + + void merge_result() { /*m_result.merge_and_cleanup();*/ } }; bool SLASupportTree::generate(const std::vector &support_points, @@ -2457,9 +2459,9 @@ bool SLASupportTree::generate(const std::vector &support_points, const Controller &ctl) { if(support_points.empty()) return false; - + Algorithm alg(cfg, mesh, support_points, *m_impl, ctl.cancelfn); - + // Let's define the individual steps of the processing. We can experiment // later with the ordering and the dependencies between them. enum Steps { @@ -2477,41 +2479,41 @@ bool SLASupportTree::generate(const std::vector &support_points, NUM_STEPS //... }; - + // Collect the algorithm steps into a nice sequence std::array, NUM_STEPS> program = { [] () { // Begin... // Potentially clear up the shared data (not needed for now) }, - + std::bind(&Algorithm::filter, &alg), - + std::bind(&Algorithm::add_pinheads, &alg), - + std::bind(&Algorithm::classify, &alg), - + std::bind(&Algorithm::routing_to_ground, &alg), - + std::bind(&Algorithm::routing_to_model, &alg), - + std::bind(&Algorithm::interconnect_pillars, &alg), - + std::bind(&Algorithm::routing_headless, &alg), - + std::bind(&Algorithm::merge_result, &alg), - + [] () { // Done }, - + [] () { // Abort } }; - + Steps pc = BEGIN; - + if(cfg.ground_facing_only) { program[ROUTING_NONGROUND] = []() { BOOST_LOG_TRIVIAL(info) @@ -2522,7 +2524,7 @@ bool SLASupportTree::generate(const std::vector &support_points, " requested."; }; } - + // Let's define a simple automaton that will run our program. auto progress = [&ctl, &pc] () { static const std::array stepstr { @@ -2538,7 +2540,7 @@ bool SLASupportTree::generate(const std::vector &support_points, "Done", "Abort" }; - + static const std::array stepstate { 0, 10, @@ -2552,9 +2554,9 @@ bool SLASupportTree::generate(const std::vector &support_points, 100, 0 }; - + if(ctl.stopcondition()) pc = ABORT; - + switch(pc) { case BEGIN: pc = FILTER; break; case FILTER: pc = PINHEADS; break; @@ -2569,16 +2571,16 @@ bool SLASupportTree::generate(const std::vector &support_points, case ABORT: break; default: ; } - + ctl.statuscb(stepstate[pc], stepstr[pc]); }; - + // Just here we run the computation... while(pc < DONE) { progress(); program[pc](); } - + return pc == ABORT; } @@ -2588,7 +2590,7 @@ SLASupportTree::SLASupportTree(double gnd_lvl): m_impl(new Impl()) { const TriangleMesh &SLASupportTree::merged_mesh() const { - return get().merged_mesh(); + return m_impl->merged_mesh(); } void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { @@ -2601,34 +2603,34 @@ std::vector SLASupportTree::slice( { const TriangleMesh &sup_mesh = m_impl->merged_mesh(); const TriangleMesh &pad_mesh = get_pad(); - + std::vector sup_slices; - if (!sup_mesh.empty()) { + if (!sup_mesh.empty()) { TriangleMeshSlicer sup_slicer(&sup_mesh); sup_slicer.slice(heights, cr, &sup_slices, m_impl->ctl().cancelfn); } - + auto bb = pad_mesh.bounding_box(); auto maxzit = std::upper_bound(heights.begin(), heights.end(), bb.max.z()); - + auto padgrid = reserve_vector(heights.end() - maxzit); std::copy(heights.begin(), maxzit, std::back_inserter(padgrid)); - + std::vector pad_slices; - if (!pad_mesh.empty()) { + if (!pad_mesh.empty()) { TriangleMeshSlicer pad_slicer(&pad_mesh); pad_slicer.slice(padgrid, cr, &pad_slices, m_impl->ctl().cancelfn); } - + size_t len = std::min(heights.size(), pad_slices.size()); len = std::min(len, sup_slices.size()); - + for (size_t i = 0; i < len; ++i) { std::copy(pad_slices[i].begin(), pad_slices[i].end(), std::back_inserter(sup_slices[i])); - pad_slices[i] = {}; + pad_slices[i] = {}; } - + return sup_slices; } diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index d48b5bf739..95d4512710 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -148,9 +148,9 @@ std::vector BoxIndex::query(const BoundingBox &qrbb, BoxIndex::QueryType qt) { namespace bgi = boost::geometry::index; - + std::vector ret; ret.reserve(m_impl->m_store.size()); - + switch (qt) { case qtIntersects: m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); @@ -158,7 +158,7 @@ std::vector BoxIndex::query(const BoundingBox &qrbb, case qtWithin: m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); } - + return ret; } @@ -198,9 +198,9 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { F.resize(stl.stats.number_of_facets, 3); for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) { const stl_facet &facet = stl.facet_start[i]; - V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0].cast(); - V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1].cast(); - V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2].cast(); + V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0].cast(); + V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1].cast(); + V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2].cast(); F(i, 0) = int(3*i+0); F(i, 1) = int(3*i+1); F(i, 2) = int(3*i+2); @@ -306,6 +306,7 @@ PointSet normals(const PointSet& points, PointSet ret(range.size(), 3); +// for (size_t ridx = 0; ridx < range.size(); ++ridx) tbb::parallel_for(size_t(0), range.size(), [&ret, &range, &mesh, &points, thr, eps](size_t ridx) { diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 45f8a0c83a..fa85d497f9 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -16,6 +16,12 @@ // For geometry algorithms with native Clipper types (no copies and conversions) #include +#define SLAPRINT_DO_BENCHMARK + +#ifdef SLAPRINT_DO_BENCHMARK +#include +#endif + //#include //#include "tbb/mutex.h" #include "I18N.hpp" @@ -52,7 +58,7 @@ const std::array OBJ_STEP_LEVELS = }; // Object step to status label. The labels are localized at the time of calling, thus supporting language switching. -std::string OBJ_STEP_LABELS(size_t idx) +std::string OBJ_STEP_LABELS(size_t idx) { switch (idx) { case slaposObjectSlice: return L("Slicing model"); @@ -365,7 +371,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con // Synchronize Object's config. bool object_config_changed = model_object.config != model_object_new.config; if (object_config_changed) - static_cast(model_object.config) = static_cast(model_object_new.config); + static_cast(model_object.config) = static_cast(model_object_new.config); if (! object_diff.empty() || object_config_changed) { SLAPrintObjectConfig new_config = m_default_object_config; normalize_and_apply_config(new_config, model_object.config); @@ -424,10 +430,10 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con print_object->set_trafo(sla_trafo(*this, model_object), model_object.instances.front()->is_left_handed()); print_object->set_instances(std::move(new_instances)); - - SLAPrintObjectConfig new_config = m_default_object_config; - normalize_and_apply_config(new_config, model_object.config); - print_object->config_apply(new_config, true); + + SLAPrintObjectConfig new_config = m_default_object_config; + normalize_and_apply_config(new_config, model_object.config); + print_object->config_apply(new_config, true); print_objects_new.emplace_back(print_object); new_objects = true; } @@ -446,7 +452,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con if (new_objects) update_apply_status(false); } - + if(m_objects.empty()) { m_printer.release(); m_printer_input.clear(); @@ -596,16 +602,16 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { scfg.pillar_base_safety_distance_mm = c.support_base_safety_distance.getFloat() < EPSILON ? scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); - + return scfg; } sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { sla::PoolConfig::EmbedObject ret; - + ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && c.pad_enable.getBool() && c.supports_enable.getBool(); - + if(ret.enabled) { ret.object_gap_mm = c.pad_object_gap.getFloat(); ret.stick_width_mm = c.pad_object_connector_width.getFloat(); @@ -613,7 +619,7 @@ sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { ret.stick_penetration_mm = c.pad_object_connector_penetration .getFloat(); } - + return ret; } @@ -622,16 +628,16 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat(); pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0; - + // We do not support radius for now pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat(); - + pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat(); pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); // set builtin pad implicitly ON pcfg.embed_object = builtin_pad_cfg(c); - + return pcfg; } @@ -657,12 +663,12 @@ std::string SLAPrint::validate() const cfg.head_width_mm + 2 * cfg.head_back_radius_mm - cfg.head_penetration_mm; - + double elv = cfg.object_elevation_mm; if(supports_en && elv > EPSILON && elv < pinhead_width ) return L("Elevation is too low for object."); - + sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config()); if(supports_en && builtinpad.enabled && cfg.pillar_base_safety_distance_mm < builtinpad.object_gap_mm) { @@ -740,15 +746,15 @@ void SLAPrint::process() coord_t maxZs = scaled(maxZ); po.m_slice_index.clear(); - + size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs); po.m_slice_index.reserve(cap); - + po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh); for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) po.m_slice_index.emplace_back(h, unscaled(h) - lh / 2.f, lh); - + // Just get the first record that is form the model: auto slindex_it = po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z))); @@ -781,9 +787,9 @@ void SLAPrint::process() { // We apply the printer correction offset here. if(clpr_offs != 0) - po.m_model_slices[id] = + po.m_model_slices[id] = offset_ex(po.m_model_slices[id], float(clpr_offs)); - + mit->set_model_slice_idx(po, id); ++mit; } @@ -876,7 +882,7 @@ void SLAPrint::process() // removed them on purpose. No calculation will be done. po.m_supportdata->support_points = po.transformed_support_points(); } - + // If the zero elevation mode is engaged, we have to filter out all the // points that are on the bottom of the object if (po.config().support_object_elevation.getFloat() <= EPSILON) { @@ -894,7 +900,7 @@ void SLAPrint::process() double diff = std::abs(gnd - double(sp.pos(Z))); return diff <= tolerance; }); - + // erase all elements after the new end pts.erase(endit, pts.end()); } @@ -904,7 +910,7 @@ void SLAPrint::process() auto support_tree = [this, ostepd](SLAPrintObject& po) { if(!po.m_supportdata) return; - + sla::PoolConfig pcfg = make_pool_config(po.m_config); if (pcfg.embed_object) @@ -912,11 +918,11 @@ void SLAPrint::process() pcfg.min_wall_thickness_mm); if(!po.m_config.supports_enable.getBool()) { - + // Generate empty support tree. It can still host a pad po.m_supportdata->support_tree_ptr.reset( new SLASupportTree(po.m_supportdata->emesh.ground_level())); - + return; } @@ -940,7 +946,7 @@ void SLAPrint::process() ctl.stopcondition = [this](){ return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; - + po.m_supportdata->support_tree_ptr.reset( new SLASupportTree(po.m_supportdata->support_points, po.m_supportdata->emesh, scfg, ctl)); @@ -948,20 +954,20 @@ void SLAPrint::process() throw_if_canceled(); // Create the unified mesh - auto rc = SlicingStatus::RELOAD_SCENE; + // auto rc = SlicingStatus::RELOAD_SCENE; // This is to prevent "Done." being displayed during merged_mesh() - m_report_status(*this, -1, L("Visualizing supports")); - po.m_supportdata->support_tree_ptr->merged_mesh(); + // m_report_status(*this, -1, L("Visualizing supports")); + // po.m_supportdata->support_tree_ptr->merged_mesh(); BOOST_LOG_TRIVIAL(debug) << "Processed support point count " << po.m_supportdata->support_points.size(); // Check the mesh for later troubleshooting. - if(po.support_mesh().empty()) - BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; +// if(po.support_mesh().empty()) +// BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; - m_report_status(*this, -1, L("Visualizing supports"), rc); +// m_report_status(*this, -1, L("Visualizing supports"), rc); }; // This step generates the sla base pad @@ -970,6 +976,10 @@ void SLAPrint::process() // and before the supports had been sliced. (or the slicing has to be // repeated) + std::cout << "Should only merge mesh after this" << std::endl; + po.m_supportdata->support_tree_ptr->merged_mesh(); + m_report_status(*this, -1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE); + if(po.m_config.pad_enable.getBool()) { // Get the distilled pad configuration from the config @@ -1040,7 +1050,7 @@ void SLAPrint::process() if(clpr_offs != 0) sd->support_slices[i] = offset_ex(sd->support_slices[i], float(clpr_offs)); - + po.m_slice_index[i].set_support_slice_idx(po, i); } @@ -1268,7 +1278,7 @@ void SLAPrint::process() const SLAPrintObject *po = record.print_obj(); const ExPolygons &modelslices = record.get_slice(soModel); - + bool is_lefth = record.print_obj()->is_left_handed(); if (!modelslices.empty()) { ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth); @@ -1276,7 +1286,7 @@ void SLAPrint::process() } const ExPolygons &supportslices = record.get_slice(soSupport); - + if (!supportslices.empty()) { ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth); for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp)); @@ -1369,8 +1379,8 @@ void SLAPrint::process() { // create a raster printer for the current print parameters double layerh = m_default_object_config.layer_height.getFloat(); - m_printer.reset(new SLAPrinter(m_printer_config, - m_material_config, + m_printer.reset(new SLAPrinter(m_printer_config, + m_material_config, layerh)); } @@ -1429,7 +1439,7 @@ void SLAPrint::process() if(canceled()) return; // Sequential version (for testing) - // for(unsigned l = 0; l < lvlcnt; ++l) process_level(l); + // for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l); // Print all the layers in parallel tbb::parallel_for(0, lvlcnt, lvlfn); @@ -1446,44 +1456,48 @@ void SLAPrint::process() using slaposFn = std::function; using slapsFn = std::function; - std::array pobj_program = + slaposFn pobj_program[] = { - slice_model, - support_points, - support_tree, - base_pool, - slice_supports + slice_model, support_points, support_tree, base_pool, slice_supports }; - std::array print_program = - { - merge_slices_and_eval_stats, - rasterize + // We want to first process all objects... + std::vector level1_obj_steps = { + slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposBasePool }; + // and then slice all supports to allow preview to be displayed ASAP + std::vector level2_obj_steps = { + slaposSliceSupports + }; + + slapsFn print_program[] = { merge_slices_and_eval_stats, rasterize }; + SLAPrintStep print_steps[] = { slapsMergeSlicesAndEval, slapsRasterize }; + double st = min_objstatus; - unsigned incr = 0; BOOST_LOG_TRIVIAL(info) << "Start slicing process."; - // TODO: this loop could run in parallel but should not exhaust all the CPU - // power available - // Calculate the support structures first before slicing the supports, - // so that the preview will get displayed ASAP for all objects. - std::vector step_ranges = {slaposObjectSlice, - slaposSliceSupports, - slaposCount}; +#ifdef SLAPRINT_DO_BENCHMARK + Benchmark bench; +#else + struct { + void start() {} void stop() {} double getElapsedSec() { return .0; } + } bench; +#endif - for (size_t idx_range = 0; idx_range + 1 < step_ranges.size(); ++idx_range) { + std::array step_times {}; + + auto apply_steps_on_objects = + [this, &st, ostepd, &pobj_program, &step_times, &bench] + (const std::vector &steps) + { + unsigned incr = 0; for (SLAPrintObject *po : m_objects) { - BOOST_LOG_TRIVIAL(info) - << "Slicing object " << po->model_object()->name; + for (SLAPrintObjectStep currentstep : steps) { - for (int s = int(step_ranges[idx_range]); - s < int(step_ranges[idx_range + 1]); - ++s) { - auto currentstep = static_cast(s); + Benchmark bench; // Cancellation checking. Each step will check for // cancellation on its own and return earlier gracefully. @@ -1493,12 +1507,12 @@ void SLAPrint::process() st += incr * ostepd; - if (po->m_stepmask[currentstep] - && po->set_started(currentstep)) { - m_report_status(*this, - st, - OBJ_STEP_LABELS(currentstep)); + if (po->m_stepmask[currentstep] && po->set_started(currentstep)) { + m_report_status(*this, st, OBJ_STEP_LABELS(currentstep)); + bench.start(); pobj_program[currentstep](*po); + bench.stop(); + step_times[currentstep] += bench.getElapsedSec(); throw_if_canceled(); po->set_done(currentstep); } @@ -1506,26 +1520,27 @@ void SLAPrint::process() incr = OBJ_STEP_LEVELS[currentstep]; } } - } - - std::array printsteps = { - slapsMergeSlicesAndEval, slapsRasterize }; + apply_steps_on_objects(level1_obj_steps); + apply_steps_on_objects(level2_obj_steps); + + SLAPrintStep printsteps[] = { slapsMergeSlicesAndEval, slapsRasterize }; + // this would disable the rasterization step - // m_stepmask[slapsRasterize] = false; + // std::fill(m_stepmask.begin(), m_stepmask.end(), false); double pstd = (100 - max_objstatus) / 100.0; st = max_objstatus; - for(size_t s = 0; s < print_program.size(); ++s) { - auto currentstep = printsteps[s]; - + for(SLAPrintStep currentstep : printsteps) { throw_if_canceled(); - if(m_stepmask[currentstep] && set_started(currentstep)) - { + if (m_stepmask[currentstep] && set_started(currentstep)) { m_report_status(*this, st, PRINT_STEP_LABELS(currentstep)); + bench.start(); print_program[currentstep](); + bench.stop(); + step_times[slaposCount + currentstep] += bench.getElapsedSec(); throw_if_canceled(); set_done(currentstep); } @@ -1535,6 +1550,21 @@ void SLAPrint::process() // If everything vent well m_report_status(*this, 100, L("Slicing done")); + +#ifdef SLAPRINT_DO_BENCHMARK + std::string csvbenchstr; + for (size_t i = 0; i < size_t(slaposCount); ++i) + csvbenchstr += OBJ_STEP_LABELS(i) + ";"; + + for (size_t i = 0; i < size_t(slapsCount); ++i) + csvbenchstr += PRINT_STEP_LABELS(i) + ";"; + + csvbenchstr += "\n"; + for (double t : step_times) csvbenchstr += std::to_string(t) + ";"; + + std::cout << "Performance stats: \n" << csvbenchstr << std::endl; +#endif + } bool SLAPrint::invalidate_state_by_config_options(const std::vector &opt_keys, bool &invalidate_all_model_objects) @@ -1756,7 +1786,7 @@ Vec3d SLAPrint::relative_correction() const corr(X) = printer_config().relative_correction.values[0]; corr(Y) = printer_config().relative_correction.values[0]; corr(Z) = printer_config().relative_correction.values.back(); - } + } if(material_config().material_correction.values.size() >= 2) { corr(X) *= material_config().material_correction.values[0]; @@ -1925,7 +1955,7 @@ void SLAPrint::StatusReporter::operator()(SLAPrint & p, BOOST_LOG_TRIVIAL(info) << st << "% " << msg << (logmsg.empty() ? "" : ": ") << logmsg << log_memory_info(); - + p.set_status(int(std::round(st)), msg, flags); } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 2a8d8fccf5..aef0896346 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -11,13 +11,14 @@ #include #include #include +#include #include #include #include namespace Slic3r { - enum class ModelVolumeType : int; + enum class ModelVolumeType : int; }; typedef double coordf_t; @@ -36,11 +37,11 @@ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const std::function cb, const std::string& icon = "", wxEvtHandler* event_handler = nullptr, std::function const cb_condition = []() { return true; }, wxWindow* parent = nullptr); -wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxString& string, const wxString& description, +wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxString& string, const wxString& description, const std::string& icon = "", std::function const cb_condition = []() { return true; }, wxWindow* parent = nullptr); -wxMenuItem* append_menu_radio_item(wxMenu* menu, int id, const wxString& string, const wxString& description, +wxMenuItem* append_menu_radio_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, wxEvtHandler* event_handler); class wxDialog; @@ -48,7 +49,7 @@ void edit_tooltip(wxString& tooltip); void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector& btn_ids); int em_unit(wxWindow* win); -wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name, +wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name, const int px_cnt = 16, const bool is_horizontal = false, const bool grayscale = false); class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup @@ -92,23 +93,23 @@ public: class wxDataViewTreeCtrlComboPopup: public wxDataViewTreeCtrl, public wxComboPopup { - static const unsigned int DefaultWidth; - static const unsigned int DefaultHeight; - static const unsigned int DefaultItemHeight; + static const unsigned int DefaultWidth; + static const unsigned int DefaultHeight; + static const unsigned int DefaultItemHeight; - wxString m_text; - int m_cnt_open_items{0}; + wxString m_text; + int m_cnt_open_items{0}; public: - virtual bool Create(wxWindow* parent); - virtual wxWindow* GetControl() { return this; } - virtual void SetStringValue(const wxString& value) { m_text = value; } - virtual wxString GetStringValue() const { return m_text; } + virtual bool Create(wxWindow* parent); + virtual wxWindow* GetControl() { return this; } + virtual void SetStringValue(const wxString& value) { m_text = value; } + virtual wxString GetStringValue() const { return m_text; } // virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight); - virtual void OnKeyEvent(wxKeyEvent& evt); - void OnDataViewTreeCtrlSelection(wxCommandEvent& evt); - void SetItemsCnt(int cnt) { m_cnt_open_items = cnt; } + virtual void OnKeyEvent(wxKeyEvent& evt); + void OnDataViewTreeCtrlSelection(wxCommandEvent& evt); + void SetItemsCnt(int cnt) { m_cnt_open_items = cnt; } }; @@ -121,7 +122,7 @@ class DataViewBitmapText : public wxObject public: DataViewBitmapText( const wxString &text = wxEmptyString, const wxBitmap& bmp = wxNullBitmap) : - m_text(text), + m_text(text), m_bmp(bmp) { } @@ -177,8 +178,8 @@ WX_DEFINE_ARRAY_PTR(ObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray); class ObjectDataViewModelNode { - ObjectDataViewModelNode* m_parent; - MyObjectTreeModelNodePtrArray m_children; + ObjectDataViewModelNode* m_parent; + MyObjectTreeModelNodePtrArray m_children; wxBitmap m_empty_bmp; size_t m_volumes_cnt = 0; std::vector< std::string > m_opt_categories; @@ -196,7 +197,7 @@ class ObjectDataViewModelNode Slic3r::ModelVolumeType m_volume_type; public: - ObjectDataViewModelNode(const wxString &name, + ObjectDataViewModelNode(const wxString &name, const wxString& extruder): m_parent(NULL), m_name(name), @@ -211,20 +212,20 @@ public: #endif //__WXGTK__ set_action_icon(); - } + } - ObjectDataViewModelNode(ObjectDataViewModelNode* parent, - const wxString& sub_obj_name, - const wxBitmap& bmp, - const wxString& extruder, + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const wxString& sub_obj_name, + const wxBitmap& bmp, + const wxString& extruder, const int idx = -1 ) : m_parent (parent), - m_name (sub_obj_name), - m_type (itVolume), + m_name (sub_obj_name), + m_type (itVolume), m_idx (idx), m_extruder (extruder) { - m_bmp = bmp; + m_bmp = bmp; #ifdef __WXGTK__ // it's necessary on GTK because of control have to know if this item will be container // in another case you couldn't to add subitem for this item @@ -235,125 +236,125 @@ public: set_action_icon(); } - ObjectDataViewModelNode(ObjectDataViewModelNode* parent, - const t_layer_height_range& layer_range, + ObjectDataViewModelNode(ObjectDataViewModelNode* parent, + const t_layer_height_range& layer_range, const int idx = -1, const wxString& extruder = wxEmptyString ); ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const ItemType type); - ~ObjectDataViewModelNode() - { - // free all our children nodes - size_t count = m_children.GetCount(); - for (size_t i = 0; i < count; i++) - { - ObjectDataViewModelNode *child = m_children[i]; - delete child; - } + ~ObjectDataViewModelNode() + { + // free all our children nodes + size_t count = m_children.GetCount(); + for (size_t i = 0; i < count; i++) + { + ObjectDataViewModelNode *child = m_children[i]; + delete child; + } #ifndef NDEBUG - // Indicate that the object was deleted. - m_idx = -2; + // Indicate that the object was deleted. + m_idx = -2; #endif /* NDEBUG */ - } + } - bool IsContainer() const - { - return m_container; - } + bool IsContainer() const + { + return m_container; + } - ObjectDataViewModelNode* GetParent() - { - assert(m_parent == nullptr || m_parent->valid()); - return m_parent; - } - MyObjectTreeModelNodePtrArray& GetChildren() - { - return m_children; - } - ObjectDataViewModelNode* GetNthChild(unsigned int n) - { - return m_children.Item(n); - } - void Insert(ObjectDataViewModelNode* child, unsigned int n) - { - if (!m_container) - m_container = true; - m_children.Insert(child, n); - } - void Append(ObjectDataViewModelNode* child) - { - if (!m_container) - m_container = true; - m_children.Add(child); - } - void RemoveAllChildren() - { - if (GetChildCount() == 0) - return; - for (int id = int(GetChildCount()) - 1; id >= 0; --id) - { - if (m_children.Item(id)->GetChildCount() > 0) - m_children[id]->RemoveAllChildren(); - auto node = m_children[id]; - m_children.RemoveAt(id); - delete node; - } - } + ObjectDataViewModelNode* GetParent() + { + assert(m_parent == nullptr || m_parent->valid()); + return m_parent; + } + MyObjectTreeModelNodePtrArray& GetChildren() + { + return m_children; + } + ObjectDataViewModelNode* GetNthChild(unsigned int n) + { + return m_children.Item(n); + } + void Insert(ObjectDataViewModelNode* child, unsigned int n) + { + if (!m_container) + m_container = true; + m_children.Insert(child, n); + } + void Append(ObjectDataViewModelNode* child) + { + if (!m_container) + m_container = true; + m_children.Add(child); + } + void RemoveAllChildren() + { + if (GetChildCount() == 0) + return; + for (int id = int(GetChildCount()) - 1; id >= 0; --id) + { + if (m_children.Item(id)->GetChildCount() > 0) + m_children[id]->RemoveAllChildren(); + auto node = m_children[id]; + m_children.RemoveAt(id); + delete node; + } + } - size_t GetChildCount() const - { - return m_children.GetCount(); - } + size_t GetChildCount() const + { + return m_children.GetCount(); + } - bool SetValue(const wxVariant &variant, unsigned int col); + bool SetValue(const wxVariant &variant, unsigned int col); - void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } + void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } const wxBitmap& GetBitmap() const { return m_bmp; } const wxString& GetName() const { return m_name; } ItemType GetType() const { return m_type; } - void SetIdx(const int& idx); - int GetIdx() const { return m_idx; } - t_layer_height_range GetLayerRange() const { return m_layer_range; } + void SetIdx(const int& idx); + int GetIdx() const { return m_idx; } + t_layer_height_range GetLayerRange() const { return m_layer_range; } - // use this function only for childrens - void AssignAllVal(ObjectDataViewModelNode& from_node) - { - // ! Don't overwrite other values because of equality of this values for all children -- - m_name = from_node.m_name; + // use this function only for childrens + void AssignAllVal(ObjectDataViewModelNode& from_node) + { + // ! Don't overwrite other values because of equality of this values for all children -- + m_name = from_node.m_name; m_bmp = from_node.m_bmp; m_idx = from_node.m_idx; m_extruder = from_node.m_extruder; m_type = from_node.m_type; - } + } - bool SwapChildrens(int frst_id, int scnd_id) { - if (GetChildCount() < 2 || - frst_id < 0 || (size_t)frst_id >= GetChildCount() || - scnd_id < 0 || (size_t)scnd_id >= GetChildCount()) - return false; + bool SwapChildrens(int frst_id, int scnd_id) { + if (GetChildCount() < 2 || + frst_id < 0 || (size_t)frst_id >= GetChildCount() || + scnd_id < 0 || (size_t)scnd_id >= GetChildCount()) + return false; - ObjectDataViewModelNode new_scnd = *GetNthChild(frst_id); - ObjectDataViewModelNode new_frst = *GetNthChild(scnd_id); + ObjectDataViewModelNode new_scnd = *GetNthChild(frst_id); + ObjectDataViewModelNode new_frst = *GetNthChild(scnd_id); new_scnd.m_idx = m_children.Item(scnd_id)->m_idx; new_frst.m_idx = m_children.Item(frst_id)->m_idx; - m_children.Item(frst_id)->AssignAllVal(new_frst); - m_children.Item(scnd_id)->AssignAllVal(new_scnd); - return true; - } + m_children.Item(frst_id)->AssignAllVal(new_frst); + m_children.Item(scnd_id)->AssignAllVal(new_scnd); + return true; + } - // Set action icons for node + // Set action icons for node void set_action_icon(); void update_settings_digest_bitmaps(); - bool update_settings_digest(const std::vector& categories); + bool update_settings_digest(const std::vector& categories); int volume_type() const { return int(m_volume_type); } void msw_rescale(); #ifndef NDEBUG - bool valid(); + bool valid(); #endif /* NDEBUG */ private: @@ -369,7 +370,7 @@ wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); class ObjectDataViewModel :public wxDataViewModel { - std::vector m_objects; + std::vector m_objects; std::vector m_volume_bmps; wxBitmap* m_warning_bmp; @@ -379,7 +380,7 @@ public: ObjectDataViewModel(); ~ObjectDataViewModel(); - wxDataViewItem Add( const wxString &name, + wxDataViewItem Add( const wxString &name, const int extruder, const bool has_errors = false); wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item, @@ -391,24 +392,24 @@ public: wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num); wxDataViewItem AddLayersRoot(const wxDataViewItem &parent_item); - wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item, + wxDataViewItem AddLayersChild( const wxDataViewItem &parent_item, const t_layer_height_range& layer_range, - const int extruder = 0, + const int extruder = 0, const int index = -1); - wxDataViewItem Delete(const wxDataViewItem &item); - wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); - void DeleteAll(); + wxDataViewItem Delete(const wxDataViewItem &item); + wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num); + void DeleteAll(); void DeleteChildren(wxDataViewItem& parent); void DeleteVolumeChildren(wxDataViewItem& parent); void DeleteSettings(const wxDataViewItem& parent); - wxDataViewItem GetItemById(int obj_idx); + wxDataViewItem GetItemById(int obj_idx); wxDataViewItem GetItemById(const int obj_idx, const int sub_obj_idx, const ItemType parent_type); - wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); - wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); + wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); + wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); wxDataViewItem GetItemByLayerId(int obj_idx, int layer_idx); wxDataViewItem GetItemByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); int GetItemIdByLayerRange(const int obj_idx, const t_layer_height_range& layer_range); - int GetIdByItem(const wxDataViewItem& item) const; + int GetIdByItem(const wxDataViewItem& item) const; int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const; int GetObjectIdByItem(const wxDataViewItem& item) const; int GetVolumeIdByItem(const wxDataViewItem& item) const; @@ -418,53 +419,53 @@ public: int GetRowByItem(const wxDataViewItem& item) const; bool IsEmpty() { return m_objects.empty(); } - // helper method for wxLog + // helper method for wxLog - wxString GetName(const wxDataViewItem &item) const; + wxString GetName(const wxDataViewItem &item) const; wxBitmap& GetBitmap(const wxDataViewItem &item) const; - // helper methods to change the model + // helper methods to change the model - virtual unsigned int GetColumnCount() const override { return 3;} - virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); } + virtual unsigned int GetColumnCount() const override { return 3;} + virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); } - virtual void GetValue( wxVariant &variant, - const wxDataViewItem &item, + virtual void GetValue( wxVariant &variant, + const wxDataViewItem &item, unsigned int col) const override; - virtual bool SetValue( const wxVariant &variant, - const wxDataViewItem &item, + virtual bool SetValue( const wxVariant &variant, + const wxDataViewItem &item, unsigned int col) override; - bool SetValue( const wxVariant &variant, - const int item_idx, + bool SetValue( const wxVariant &variant, + const int item_idx, unsigned int col); - // For parent move child from cur_volume_id place to new_volume_id + // For parent move child from cur_volume_id place to new_volume_id // Remaining items will moved up/down accordingly - wxDataViewItem ReorganizeChildren( const int cur_volume_id, + wxDataViewItem ReorganizeChildren( const int cur_volume_id, const int new_volume_id, const wxDataViewItem &parent); - virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override; + virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override; - virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override; + virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override; // get object item wxDataViewItem GetTopParent(const wxDataViewItem &item) const; - virtual bool IsContainer(const wxDataViewItem &item) const override; - virtual unsigned int GetChildren(const wxDataViewItem &parent, - wxDataViewItemArray &array) const override; + virtual bool IsContainer(const wxDataViewItem &item) const override; + virtual unsigned int GetChildren(const wxDataViewItem &parent, + wxDataViewItemArray &array) const override; void GetAllChildren(const wxDataViewItem &parent,wxDataViewItemArray &array) const; - // Is the container just a header or an item with all columns - // In our case it is an item with all columns - virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } + // Is the container just a header or an item with all columns + // In our case it is an item with all columns + virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } ItemType GetItemType(const wxDataViewItem &item) const ; - wxDataViewItem GetItemByType( const wxDataViewItem &parent_item, + wxDataViewItem GetItemByType( const wxDataViewItem &parent_item, ItemType type) const; wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const; wxDataViewItem GetInstanceRootItem(const wxDataViewItem &item) const; wxDataViewItem GetLayerRootItem(const wxDataViewItem &item) const; bool IsSettingsItem(const wxDataViewItem &item) const; - void UpdateSettingsDigest( const wxDataViewItem &item, + void UpdateSettingsDigest( const wxDataViewItem &item, const std::vector& categories); void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } @@ -475,7 +476,7 @@ public: // Rescale bitmaps for existing Items void Rescale(); - wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, + wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked = false); void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; @@ -520,12 +521,12 @@ public: return false; #else return true; -#endif +#endif } - wxWindow* CreateEditorCtrl(wxWindow* parent, - wxRect labelRect, + wxWindow* CreateEditorCtrl(wxWindow* parent, + wxRect labelRect, const wxVariant& value) override; - bool GetValueFromEditorCtrl( wxWindow* ctrl, + bool GetValueFromEditorCtrl( wxWindow* ctrl, wxVariant& value) override; bool WasCanceled() const { return m_was_unusable_symbol; } @@ -542,88 +543,88 @@ private: class MyCustomRenderer : public wxDataViewCustomRenderer { public: - // This renderer can be either activatable or editable, for demonstration - // purposes. In real programs, you should select whether the user should be - // able to activate or edit the cell and it doesn't make sense to switch - // between the two -- but this is just an example, so it doesn't stop us. - explicit MyCustomRenderer(wxDataViewCellMode mode) - : wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER) - { } + // This renderer can be either activatable or editable, for demonstration + // purposes. In real programs, you should select whether the user should be + // able to activate or edit the cell and it doesn't make sense to switch + // between the two -- but this is just an example, so it doesn't stop us. + explicit MyCustomRenderer(wxDataViewCellMode mode) + : wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER) + { } - virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/ - { - dc->SetBrush(*wxLIGHT_GREY_BRUSH); - dc->SetPen(*wxTRANSPARENT_PEN); + virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/ + { + dc->SetBrush(*wxLIGHT_GREY_BRUSH); + dc->SetPen(*wxTRANSPARENT_PEN); - rect.Deflate(2); - dc->DrawRoundedRectangle(rect, 5); + rect.Deflate(2); + dc->DrawRoundedRectangle(rect, 5); - RenderText(m_value, - 0, // no offset - wxRect(dc->GetTextExtent(m_value)).CentreIn(rect), - dc, - state); - return true; - } + RenderText(m_value, + 0, // no offset + wxRect(dc->GetTextExtent(m_value)).CentreIn(rect), + dc, + state); + return true; + } - virtual bool ActivateCell(const wxRect& WXUNUSED(cell), - wxDataViewModel *WXUNUSED(model), - const wxDataViewItem &WXUNUSED(item), - unsigned int WXUNUSED(col), - const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/ - { - wxString position; - if (mouseEvent) - position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y); - else - position = "from keyboard"; + virtual bool ActivateCell(const wxRect& WXUNUSED(cell), + wxDataViewModel *WXUNUSED(model), + const wxDataViewItem &WXUNUSED(item), + unsigned int WXUNUSED(col), + const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/ + { + wxString position; + if (mouseEvent) + position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y); + else + position = "from keyboard"; // wxLogMessage("MyCustomRenderer ActivateCell() %s", position); - return false; - } + return false; + } - virtual wxSize GetSize() const override/*wxOVERRIDE*/ - { - return wxSize(60, 20); - } + virtual wxSize GetSize() const override/*wxOVERRIDE*/ + { + return wxSize(60, 20); + } - virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/ - { - m_value = value.GetString(); - return true; - } + virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/ + { + m_value = value.GetString(); + return true; + } - virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; } + virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; } - virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; } + virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; } - virtual wxWindow* - CreateEditorCtrl(wxWindow* parent, - wxRect labelRect, - const wxVariant& value) override/*wxOVERRIDE*/ - { - wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value, - labelRect.GetPosition(), - labelRect.GetSize(), - wxTE_PROCESS_ENTER); - text->SetInsertionPointEnd(); + virtual wxWindow* + CreateEditorCtrl(wxWindow* parent, + wxRect labelRect, + const wxVariant& value) override/*wxOVERRIDE*/ + { + wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value, + labelRect.GetPosition(), + labelRect.GetSize(), + wxTE_PROCESS_ENTER); + text->SetInsertionPointEnd(); - return text; - } + return text; + } - virtual bool - GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/ - { - wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl); - if (!text) - return false; + virtual bool + GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/ + { + wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl); + if (!text) + return false; - value = text->GetValue(); + value = text->GetValue(); - return true; - } + return true; + } private: - wxString m_value; + wxString m_value; }; @@ -635,7 +636,7 @@ class ScalableBitmap { public: ScalableBitmap() {}; - ScalableBitmap( wxWindow *parent, + ScalableBitmap( wxWindow *parent, const std::string& icon_name = "", const int px_cnt = 16, const bool is_horizontal = false); @@ -681,9 +682,9 @@ public: DoubleSlider( wxWindow *parent, wxWindowID id, - int lowerValue, - int higherValue, - int minValue, + int lowerValue, + int higherValue, + int minValue, int maxValue, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, @@ -726,8 +727,8 @@ public: EnableTickManipulation(false); } - bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } - bool is_one_layer() const { return m_is_one_layer; } + bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } + bool is_one_layer() const { return m_is_one_layer; } bool is_lower_at_min() const { return m_lower_value == m_min_value; } bool is_higher_at_max() const { return m_higher_value == m_max_value; } bool is_full_span() const { return this->is_lower_at_min() && this->is_higher_at_max(); } @@ -746,7 +747,7 @@ public: void OnRightUp(wxMouseEvent& event); protected: - + void render(); void draw_focus_rect(); void draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end); diff --git a/src/slic3r/Utils/Time.hpp b/src/slic3r/Utils/Time.hpp index 7b670bd3ee..6a1aefa188 100644 --- a/src/slic3r/Utils/Time.hpp +++ b/src/slic3r/Utils/Time.hpp @@ -2,7 +2,7 @@ #define slic3r_Utils_Time_hpp_ #include -#include +#include namespace Slic3r { namespace Utils { From c187a5fb695b59402ccbbb24eb17266226236b67 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 16 Aug 2019 16:31:05 +0200 Subject: [PATCH 559/627] Fix up build and clear dev output --- src/libslic3r/GCode/ToolOrdering.cpp | 37 +++++++------------------- src/libslic3r/SLA/SLASupportTree.cpp | 4 +-- src/libslic3r/SLAPrint.cpp | 39 +++++++++++----------------- 3 files changed, 26 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index f10b45723a..2d7b90442b 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -96,23 +96,6 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool this->collect_extruder_statistics(prime_multi_material); } - -LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) -{ - auto it_layer_tools = std::lower_bound(m_layer_tools.begin(), m_layer_tools.end(), LayerTools(print_z - EPSILON)); - assert(it_layer_tools != m_layer_tools.end()); - coordf_t dist_min = std::abs(it_layer_tools->print_z - print_z); - for (++ it_layer_tools; it_layer_tools != m_layer_tools.end(); ++it_layer_tools) { - coordf_t d = std::abs(it_layer_tools->print_z - print_z); - if (d >= dist_min) - break; - dist_min = d; - } - -- it_layer_tools; - assert(dist_min < EPSILON); - return *it_layer_tools; -} - void ToolOrdering::initialize_layers(std::vector &zs) { sort_remove_duplicates(zs); @@ -151,7 +134,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object) LayerTools &layer_tools = this->tools_for_layer(layer->print_z); // What extruders are required to print this object layer? for (size_t region_id = 0; region_id < object.region_volumes.size(); ++ region_id) { - const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr; + const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr; if (layerm == nullptr) continue; const PrintRegion ®ion = *object.print()->regions()[region_id]; @@ -320,20 +303,20 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ LayerTools lt_new(0.5f * (lt.print_z + lt_object.print_z)); // Find the 1st layer above lt_new. for (j = i + 1; j < m_layer_tools.size() && m_layer_tools[j].print_z < lt_new.print_z - EPSILON; ++ j); - if (std::abs(m_layer_tools[j].print_z - lt_new.print_z) < EPSILON) { - m_layer_tools[j].has_wipe_tower = true; - } else { - LayerTools <_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new); + if (std::abs(m_layer_tools[j].print_z - lt_new.print_z) < EPSILON) { + m_layer_tools[j].has_wipe_tower = true; + } else { + LayerTools <_extra = *m_layer_tools.insert(m_layer_tools.begin() + j, lt_new); LayerTools <_next = m_layer_tools[j + 1]; assert(! m_layer_tools[j - 1].extruders.empty() && ! lt_next.extruders.empty()); // FIXME: Following assert tripped when running combine_infill.t. I decided to comment it out for now. // If it is a bug, it's likely not critical, because this code is unchanged for a long time. It might // still be worth looking into it more and decide if it is a bug or an obsolete assert. //assert(lt_prev.extruders.back() == lt_next.extruders.front()); - lt_extra.has_wipe_tower = true; + lt_extra.has_wipe_tower = true; lt_extra.extruders.push_back(lt_next.extruders.front()); - lt_extra.wipe_tower_partitions = lt_next.wipe_tower_partitions; - } + lt_extra.wipe_tower_partitions = lt_next.wipe_tower_partitions; + } } } break; @@ -375,7 +358,7 @@ void ToolOrdering::collect_extruder_statistics(bool prime_multi_material) // Reorder m_all_printing_extruders in the sequence they will be primed, the last one will be m_first_printing_extruder. // Then set m_first_printing_extruder to the 1st extruder primed. m_all_printing_extruders.erase( - std::remove_if(m_all_printing_extruders.begin(), m_all_printing_extruders.end(), + std::remove_if(m_all_printing_extruders.begin(), m_all_printing_extruders.end(), [ this ](const unsigned int eid) { return eid == m_first_printing_extruder; }), m_all_printing_extruders.end()); m_all_printing_extruders.emplace_back(m_first_printing_extruder); @@ -623,6 +606,6 @@ const std::vector* WipingExtrusions::get_extruder_overrides(const Extrusion return &(entity_map_it->second); } - + } // namespace Slic3r diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 5e4820988e..1838514f31 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -948,8 +948,6 @@ public: { if (meshcache_valid) return meshcache; - std::cout << "merging mesh" << std::endl; - Contour3D merged; for (auto &head : m_heads) { @@ -2450,7 +2448,7 @@ public: } } - void merge_result() { /*m_result.merge_and_cleanup();*/ } + void merge_result() { m_result.merge_and_cleanup(); } }; bool SLASupportTree::generate(const std::vector &support_points, diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index fa85d497f9..46fa281b5e 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -16,7 +16,7 @@ // For geometry algorithms with native Clipper types (no copies and conversions) #include -#define SLAPRINT_DO_BENCHMARK +// #define SLAPRINT_DO_BENCHMARK #ifdef SLAPRINT_DO_BENCHMARK #include @@ -954,20 +954,20 @@ void SLAPrint::process() throw_if_canceled(); // Create the unified mesh - // auto rc = SlicingStatus::RELOAD_SCENE; + auto rc = SlicingStatus::RELOAD_SCENE; // This is to prevent "Done." being displayed during merged_mesh() - // m_report_status(*this, -1, L("Visualizing supports")); - // po.m_supportdata->support_tree_ptr->merged_mesh(); + m_report_status(*this, -1, L("Visualizing supports")); + po.m_supportdata->support_tree_ptr->merged_mesh(); BOOST_LOG_TRIVIAL(debug) << "Processed support point count " << po.m_supportdata->support_points.size(); // Check the mesh for later troubleshooting. -// if(po.support_mesh().empty()) -// BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; + if(po.support_mesh().empty()) + BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; -// m_report_status(*this, -1, L("Visualizing supports"), rc); + m_report_status(*this, -1, L("Visualizing supports"), rc); }; // This step generates the sla base pad @@ -976,10 +976,6 @@ void SLAPrint::process() // and before the supports had been sliced. (or the slicing has to be // repeated) - std::cout << "Should only merge mesh after this" << std::endl; - po.m_supportdata->support_tree_ptr->merged_mesh(); - m_report_status(*this, -1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE); - if(po.m_config.pad_enable.getBool()) { // Get the distilled pad configuration from the config @@ -1494,10 +1490,7 @@ void SLAPrint::process() { unsigned incr = 0; for (SLAPrintObject *po : m_objects) { - - for (SLAPrintObjectStep currentstep : steps) { - - Benchmark bench; + for (SLAPrintObjectStep step : steps) { // Cancellation checking. Each step will check for // cancellation on its own and return earlier gracefully. @@ -1507,17 +1500,17 @@ void SLAPrint::process() st += incr * ostepd; - if (po->m_stepmask[currentstep] && po->set_started(currentstep)) { - m_report_status(*this, st, OBJ_STEP_LABELS(currentstep)); + if (po->m_stepmask[step] && po->set_started(step)) { + m_report_status(*this, st, OBJ_STEP_LABELS(step)); bench.start(); - pobj_program[currentstep](*po); + pobj_program[step](*po); bench.stop(); - step_times[currentstep] += bench.getElapsedSec(); + step_times[step] += bench.getElapsedSec(); throw_if_canceled(); - po->set_done(currentstep); + po->set_done(step); } - incr = OBJ_STEP_LEVELS[currentstep]; + incr = OBJ_STEP_LEVELS[step]; } } }; @@ -1525,14 +1518,12 @@ void SLAPrint::process() apply_steps_on_objects(level1_obj_steps); apply_steps_on_objects(level2_obj_steps); - SLAPrintStep printsteps[] = { slapsMergeSlicesAndEval, slapsRasterize }; - // this would disable the rasterization step // std::fill(m_stepmask.begin(), m_stepmask.end(), false); double pstd = (100 - max_objstatus) / 100.0; st = max_objstatus; - for(SLAPrintStep currentstep : printsteps) { + for(SLAPrintStep currentstep : print_steps) { throw_if_canceled(); if (m_stepmask[currentstep] && set_started(currentstep)) { From eddf9321612e0cde71f466ccbc9e1cc4f6ab4442 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 19 Aug 2019 10:58:44 +0200 Subject: [PATCH 560/627] Set min SLA display resolution to 1x1 (see SPE-1000) --- src/libslic3r/PrintConfig.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 31de80e8be..fd5b699ac5 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2306,13 +2306,13 @@ void PrintConfigDef::init_sla_params() def->full_label = L("Number of pixels in"); def->label = ("X"); def->tooltip = L("Number of pixels in X"); - def->min = 100; + def->min = 1; def->set_default_value(new ConfigOptionInt(2560)); def = this->add("display_pixels_y", coInt); def->label = ("Y"); def->tooltip = L("Number of pixels in Y"); - def->min = 100; + def->min = 1; def->set_default_value(new ConfigOptionInt(1440)); def = this->add("display_mirror_x", coBool); From a66c59941d1ef7e9b18d7a3d3935781c491416f1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 19 Aug 2019 12:55:57 +0200 Subject: [PATCH 561/627] Better error message in case of corrupted PrusaSlicer.ini --- src/slic3r/GUI/AppConfig.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index dfdc79677d..7ca8c3e5eb 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -15,9 +15,13 @@ #include #include #include +#include #include #include +#include +#include "I18N.hpp" + namespace Slic3r { static const std::string VENDOR_PREFIX = "vendor:"; @@ -58,7 +62,7 @@ void AppConfig::set_defaults() if (!get("use_legacy_opengl").empty()) erase("", "use_legacy_opengl"); -#if __APPLE__ +#ifdef __APPLE__ if (get("use_retina_opengl").empty()) set("use_retina_opengl", "1"); #endif @@ -90,7 +94,14 @@ void AppConfig::load() namespace pt = boost::property_tree; pt::ptree tree; boost::nowide::ifstream ifs(AppConfig::config_path()); - pt::read_ini(ifs, tree); + try { + pt::read_ini(ifs, tree); + } catch (pt::ptree_error& ex) { + // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + throw std::runtime_error(wxString::Format(_(L("Error parsing config file, it is probably corrupted. " + "Try to manualy delete the file. Your user profiles will not be affected.\n\n%s\n\n%s")), + AppConfig::config_path(), ex.what()).ToStdString()); + } // 2) Parse the property_tree, extract the sections and key / value pairs. for (const auto §ion : tree) { From 283cee3f27156231a84a278c8ba29d52a0795d4d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 19 Aug 2019 12:58:59 +0200 Subject: [PATCH 562/627] Fixed SPE-1000. Since the value inserted from the keyboard or clipboard is not updated under OSX, we forcibly set the input value for SpinControl every time during editing. Thus we can't set min control value bigger then 0. Otherwise, it couldn't be possible to input from keyboard value less then min_val. --- src/libslic3r/PrintConfig.cpp | 4 ++-- src/slic3r/GUI/Field.cpp | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fd5b699ac5..31de80e8be 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2306,13 +2306,13 @@ void PrintConfigDef::init_sla_params() def->full_label = L("Number of pixels in"); def->label = ("X"); def->tooltip = L("Number of pixels in X"); - def->min = 1; + def->min = 100; def->set_default_value(new ConfigOptionInt(2560)); def = this->add("display_pixels_y", coInt); def->label = ("Y"); def->tooltip = L("Number of pixels in Y"); - def->min = 1; + def->min = 100; def->set_default_value(new ConfigOptionInt(1440)); def = this->add("display_mirror_x", coBool); diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 39924e44c5..252b9d6c5b 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -559,7 +559,16 @@ void SpinCtrl::BUILD() { break; } - const int min_val = m_opt.min == INT_MIN ? 0: m_opt.min; + const int min_val = m_opt.min == INT_MIN +#ifdef __WXOSX__ + // We will forcibly set the input value for SpinControl, since the value + // inserted from the keyboard is not updated under OSX. + // So, we can't set min control value bigger then 0. + // Otherwise, it couldn't be possible to input from keyboard value + // less then min_val. + || m_opt.min > 0 +#endif + ? 0 : m_opt.min; const int max_val = m_opt.max < 2147483647 ? m_opt.max : 2147483647; auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, @@ -631,6 +640,14 @@ void SpinCtrl::propagate_value() if (tmp_value == UNDEF_VALUE) { on_kill_focus(); } else { +#ifdef __WXOSX__ + // check input value for minimum + if (m_opt.min > 0 && tmp_value < m_opt.min) { + wxSpinCtrl* spin = static_cast(window); + spin->SetValue(m_opt.min); + spin->GetText()->SetInsertionPointEnd(); + } +#endif on_change_field(); } } From 7008853f0861201971fc21cf4da1d2cd8a8b401b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 19 Aug 2019 14:34:12 +0200 Subject: [PATCH 563/627] Add AppImage credits #2747 --- src/slic3r/GUI/AboutDialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index d2c76366b7..ca7c2c22d3 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -110,7 +110,9 @@ void CopyrightsDialog::fill_entries() , "Based on original by fabian \"ryg\" giesen v1.04. " "Custom version, modified by Yann Collet" , "https://github.com/Cyan4973/RygsDXTc" }, { "Icons for STL and GCODE files." - , "Akira Yasuda" , "http://3dp0.com/icons-for-stl-and-gcode/" } + , "Akira Yasuda" , "http://3dp0.com/icons-for-stl-and-gcode/" }, + { "AppImage packaging for Linux using AppImageKit" + , "2004-2019 Simon Peter and contributors" , "https://appimage.org/" } }; } From 0becfa0a0486daed4509f6688e7458b868b19d0a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 19 Aug 2019 15:44:22 +0200 Subject: [PATCH 564/627] Fix of #2739 --- src/slic3r/GUI/3DBed.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 97e200b38e..5b7473980f 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -609,10 +609,12 @@ void Bed3D::render_default(bool bottom) const if (!has_model && !bottom) { // draw background + glsafe(::glDepthMask(GL_FALSE)); glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f)); glsafe(::glNormal3d(0.0f, 0.0f, 1.0f)); glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data())); glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount)); + glsafe(::glDepthMask(GL_TRUE)); } // draw grid From 92bdb68e1126a1e6dc4001f50e02f30ce252ea74 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 19 Aug 2019 15:50:49 +0200 Subject: [PATCH 565/627] Extended the error message when empty layers are detected --- src/libslic3r/GCode.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index b691203c97..24be7fd605 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -474,8 +474,10 @@ std::vector GCode::collect_layers_to_print(const PrintObjec if (layer_to_print.print_z() > maximal_print_z + EPSILON) throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + - _(L("Object name: ")) + object.model_object()->name + "\n" + _(L("Print z: ")) + - std::to_string(layers_to_print.back().print_z())); + _(L("Object name: ")) + object.model_object()->name + "\n" + _(L("Print z: ")) + + std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is " + "usually caused by negligibly small extrusions or by a faulty model. Try to repair " + " the model or change its orientation on the bed."))); // Remember last layer with extrusions. last_extrusion_layer = &layers_to_print.back(); } From e30a17beb3c2d1d9faf64fc7c73b40ed853cdedc Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 19 Aug 2019 17:01:39 +0200 Subject: [PATCH 566/627] Fixed memory issues of BedShapeHintwhen using unions of non-trivial objects --- src/libslic3r/Arrange.cpp | 4 +--- src/libslic3r/Arrange.hpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index f04e051624..99645f29d8 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -507,9 +507,7 @@ BedShapeHint::BedShapeHint(const Polyline &bed) { m_type = BedShapes::bsCircle; m_bed.circ = c; } else { - if (m_type == BedShapes::bsIrregular) - m_bed.polygon.Slic3r::Polyline::~Polyline(); - + assert(m_type != BedShapes::bsIrregular); m_type = BedShapes::bsIrregular; ::new (&m_bed.polygon) Polyline(bed); } diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 3d405145e6..a0e4c043f0 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -39,6 +39,9 @@ enum BedShapes { class BedShapeHint { BedShapes m_type = BedShapes::bsInfinite; + // The union neither calls constructors nor destructors of its members. + // The only member with non-trivial constructor / destructor is the polygon, + // a placement new / delete needs to be called over it. union BedShape_u { // TODO: use variant from cpp17? CircleBed circ; BoundingBox box; @@ -80,6 +83,12 @@ public: BedShapeHint &operator=(const BedShapeHint &cpy) { + if (m_type != cpy.m_type) { + if (m_type == bsIrregular) + m_bed.polygon.Slic3r::Polyline::~Polyline(); + else if (cpy.m_type == bsIrregular) + ::new (&m_bed.polygon) Polyline(); + } m_type = cpy.m_type; switch(m_type) { case bsBox: m_bed.box = cpy.m_bed.box; break; @@ -94,6 +103,12 @@ public: BedShapeHint& operator=(BedShapeHint &&cpy) { + if (m_type != cpy.m_type) { + if (m_type == bsIrregular) + m_bed.polygon.Slic3r::Polyline::~Polyline(); + else if (cpy.m_type == bsIrregular) + ::new (&m_bed.polygon) Polyline(); + } m_type = cpy.m_type; switch(m_type) { case bsBox: m_bed.box = std::move(cpy.m_bed.box); break; From b5dd13b9879eb1d149ed1c4f3f15fb10abd112a5 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 5 Aug 2019 17:28:02 +0200 Subject: [PATCH 567/627] PresetUpdater: Fix index installation having broken incompatibility check This fixes a problem where old slicer having found newer incompatible bundle would not report an incompatibility. The installed index check was performed too early before bundle compatibility check. This fix moves the installed index check to the point where a bundle would've been update (as it should be). --- src/slic3r/Utils/PresetUpdater.cpp | 56 ++++++++++++++++++------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index bc600fcad9..3f3139cadf 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -101,6 +101,17 @@ struct Incompat , vendor(std::move(vendor)) {} + void remove() { + // Remove the bundle file + fs::remove(bundle); + + // Look for an installed index and remove it too if any + const fs::path installed_idx = bundle.replace_extension("idx"); + if (fs::exists(installed_idx)) { + fs::remove(installed_idx); + } + } + friend std::ostream& operator<<(std::ostream& os , const Incompat &self) { os << "Incompat(" << self.bundle.string() << ')'; return os; @@ -383,25 +394,6 @@ Updates PresetUpdater::priv::get_config_updates() const continue; } - // Load 'installed' idx, if any. - // 'Installed' indices are kept alongside the bundle in the `vendor` subdir - // for bookkeeping to remember a cancelled update and not offer it again. - if (fs::exists(bundle_path_idx)) { - Index existing_idx; - try { - existing_idx.load(bundle_path_idx); - - const auto existing_recommended = existing_idx.recommended(); - if (existing_recommended != existing_idx.end() && recommended->config_version == existing_recommended->config_version) { - // The user has already seen (and presumably rejected) this update - BOOST_LOG_TRIVIAL(info) << boost::format("Downloaded index for `%1%` is the same as installed one, not offering an update.") % idx.vendor(); - continue; - } - } catch (const std::exception & /* err */) { - BOOST_LOG_TRIVIAL(error) << boost::format("Could nto load installed index %1%") % bundle_path_idx; - } - } - const auto ver_current = idx.find(vp.config_version); const bool ver_current_found = ver_current != idx.end(); @@ -424,6 +416,25 @@ Updates PresetUpdater::priv::get_config_updates() const } else if (recommended->config_version > vp.config_version) { // Config bundle update situation + // Load 'installed' idx, if any. + // 'Installed' indices are kept alongside the bundle in the `vendor` subdir + // for bookkeeping to remember a cancelled update and not offer it again. + if (fs::exists(bundle_path_idx)) { + Index existing_idx; + try { + existing_idx.load(bundle_path_idx); + + const auto existing_recommended = existing_idx.recommended(); + if (existing_recommended != existing_idx.end() && recommended->config_version == existing_recommended->config_version) { + // The user has already seen (and presumably rejected) this update + BOOST_LOG_TRIVIAL(info) << boost::format("Downloaded index for `%1%` is the same as installed one, not offering an update.") % idx.vendor(); + continue; + } + } catch (const std::exception &err) { + BOOST_LOG_TRIVIAL(error) << boost::format("Could not load installed index at `%1%`: %2%") % bundle_path_idx % err.what(); + } + } + // Check if the update is already present in a snapshot const auto recommended_snap = SnapshotDB::singleton().snapshot_with_vendor_preset(vp.name, recommended->config_version); if (recommended_snap != SnapshotDB::singleton().end()) { @@ -485,12 +496,11 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons BOOST_LOG_TRIVIAL(info) << boost::format("Deleting %1% incompatible bundles") % updates.incompats.size(); - for (const auto &incompat : updates.incompats) { + for (auto &incompat : updates.incompats) { BOOST_LOG_TRIVIAL(info) << '\t' << incompat; - fs::remove(incompat.bundle); + incompat.remove(); } - } - else if (updates.updates.size() > 0) { + } else if (updates.updates.size() > 0) { if (snapshot) { BOOST_LOG_TRIVIAL(info) << "Taking a snapshot..."; SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE); From 745182988d5edc06ef3d9c0ea1a2265a04d79a88 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Fri, 9 Aug 2019 17:01:37 +0200 Subject: [PATCH 568/627] Refactor: Move Semver from slice3r to libslic3r A static symbol Slic3r::SEMVER is introduced, which holds the running slicer's Semver object. This is mainly done to make testing updater behaviour _much_ easier. Additionaly to cleanup some questionable code (Semver was being parsed multiple times / in multiple places in the frontend.) --- src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/Semver.cpp | 7 +++++++ src/{slic3r/Utils => libslic3r}/Semver.hpp | 0 src/libslic3r/libslic3r.h | 3 +++ src/slic3r/Config/Snapshot.cpp | 2 +- src/slic3r/Config/Snapshot.hpp | 2 +- src/slic3r/Config/Version.cpp | 3 +-- src/slic3r/Config/Version.hpp | 2 +- src/slic3r/GUI/AppConfig.hpp | 2 +- src/slic3r/GUI/MsgDialog.hpp | 2 -- src/slic3r/GUI/Preset.hpp | 2 +- src/slic3r/GUI/UpdateDialogs.hpp | 2 +- src/slic3r/Utils/PresetUpdater.cpp | 20 +++----------------- 13 files changed, 21 insertions(+), 27 deletions(-) create mode 100644 src/libslic3r/Semver.cpp rename src/{slic3r/Utils => libslic3r}/Semver.hpp (100%) diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 1ebd922e20..a5abf43eca 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -139,6 +139,7 @@ add_library(libslic3r STATIC PrintConfig.hpp PrintObject.cpp PrintRegion.cpp + Semver.cpp SLAPrint.cpp SLAPrint.hpp SLA/SLAAutoSupports.hpp diff --git a/src/libslic3r/Semver.cpp b/src/libslic3r/Semver.cpp new file mode 100644 index 0000000000..5d36b39f72 --- /dev/null +++ b/src/libslic3r/Semver.cpp @@ -0,0 +1,7 @@ +#include "libslic3r.h" + +namespace Slic3r { + +Semver SEMVER { SLIC3R_VERSION }; + +} diff --git a/src/slic3r/Utils/Semver.hpp b/src/libslic3r/Semver.hpp similarity index 100% rename from src/slic3r/Utils/Semver.hpp rename to src/libslic3r/Semver.hpp diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index dc2b6a4ec0..afbf94fa39 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -19,6 +19,7 @@ #include #include "Technologies.hpp" +#include "Semver.hpp" typedef int32_t coord_t; typedef double coordf_t; @@ -92,6 +93,8 @@ inline std::string debug_out_path(const char *name, ...) namespace Slic3r { +extern Semver SEMVER; + template inline T unscale(Q v) { return T(v) * T(SCALING_FACTOR); } diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index b208554b50..3757ec25b3 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -366,7 +366,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: // Snapshot header. snapshot.time_captured = Slic3r::Utils::get_current_time_utc(); snapshot.id = Slic3r::Utils::format_time_ISO8601Z(snapshot.time_captured); - snapshot.slic3r_version_captured = *Semver::parse(SLIC3R_VERSION); // XXX: have Semver Slic3r version + snapshot.slic3r_version_captured = Slic3r::SEMVER; snapshot.comment = comment; snapshot.reason = reason; // Active presets at the time of the snapshot. diff --git a/src/slic3r/Config/Snapshot.hpp b/src/slic3r/Config/Snapshot.hpp index a916dfe92a..9a73916916 100644 --- a/src/slic3r/Config/Snapshot.hpp +++ b/src/slic3r/Config/Snapshot.hpp @@ -8,8 +8,8 @@ #include +#include "libslic3r/Semver.hpp" #include "Version.hpp" -#include "../Utils/Semver.hpp" namespace Slic3r { diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp index 865884c6fe..175abff69a 100644 --- a/src/slic3r/Config/Version.cpp +++ b/src/slic3r/Config/Version.cpp @@ -15,7 +15,6 @@ namespace Slic3r { namespace GUI { namespace Config { -static const Semver s_current_slic3r_semver(SLIC3R_VERSION); // Optimized lexicographic compare of two pre-release versions, ignoring the numeric suffix. static int compare_prerelease(const char *p1, const char *p2) @@ -64,7 +63,7 @@ bool Version::is_slic3r_supported(const Semver &slic3r_version) const bool Version::is_current_slic3r_supported() const { - return this->is_slic3r_supported(s_current_slic3r_semver); + return this->is_slic3r_supported(Slic3r::SEMVER); } #if 0 diff --git a/src/slic3r/Config/Version.hpp b/src/slic3r/Config/Version.hpp index 560bc29c21..19c565ffb4 100644 --- a/src/slic3r/Config/Version.hpp +++ b/src/slic3r/Config/Version.hpp @@ -7,7 +7,7 @@ #include #include "libslic3r/FileParserError.hpp" -#include "../Utils/Semver.hpp" +#include "libslic3r/Semver.hpp" namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/AppConfig.hpp b/src/slic3r/GUI/AppConfig.hpp index 230a922940..8ad17b9db8 100644 --- a/src/slic3r/GUI/AppConfig.hpp +++ b/src/slic3r/GUI/AppConfig.hpp @@ -6,7 +6,7 @@ #include #include "libslic3r/Config.hpp" -#include "slic3r/Utils/Semver.hpp" +#include "libslic3r/Semver.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/MsgDialog.hpp b/src/slic3r/GUI/MsgDialog.hpp index ad4bbcc971..5a49298495 100644 --- a/src/slic3r/GUI/MsgDialog.hpp +++ b/src/slic3r/GUI/MsgDialog.hpp @@ -8,8 +8,6 @@ #include #include -#include "slic3r/Utils/Semver.hpp" - class wxBoxSizer; class wxCheckBox; class wxStaticBitmap; diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp index 8fd1652a83..e1efdc1ef0 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/slic3r/GUI/Preset.hpp @@ -8,7 +8,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" -#include "slic3r/Utils/Semver.hpp" +#include "libslic3r/Semver.hpp" class wxBitmap; class wxBitmapComboBox; diff --git a/src/slic3r/GUI/UpdateDialogs.hpp b/src/slic3r/GUI/UpdateDialogs.hpp index 2a580e2513..4b61b84c23 100644 --- a/src/slic3r/GUI/UpdateDialogs.hpp +++ b/src/slic3r/GUI/UpdateDialogs.hpp @@ -5,7 +5,7 @@ #include #include -#include "slic3r/Utils/Semver.hpp" +#include "libslic3r/Semver.hpp" #include "MsgDialog.hpp" class wxBoxSizer; diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 3f3139cadf..589db36dce 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -124,25 +124,12 @@ struct Updates std::vector updates; }; -static Semver get_slic3r_version() -{ - auto res = Semver::parse(SLIC3R_VERSION); - - if (! res) { - const char *error = "Could not parse Slic3r version string: " SLIC3R_VERSION; - BOOST_LOG_TRIVIAL(error) << error; - throw std::runtime_error(error); - } - - return *res; -} wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent); struct PresetUpdater::priv { - const Semver ver_slic3r; std::vector index_db; bool enabled_version_check; @@ -170,8 +157,7 @@ struct PresetUpdater::priv }; PresetUpdater::priv::priv() - : ver_slic3r(get_slic3r_version()) - , cache_path(fs::path(Slic3r::data_dir()) / "cache") + : cache_path(fs::path(Slic3r::data_dir()) / "cache") , rsrc_path(fs::path(resources_dir()) / "profiles") , vendor_path(fs::path(Slic3r::data_dir()) / "vendor") , cancel(false) @@ -594,8 +580,8 @@ void PresetUpdater::slic3r_update_notify() if (ver_online) { // Only display the notification if the version available online is newer AND if we haven't seen it before - if (*ver_online > p->ver_slic3r && (! ver_online_seen || *ver_online_seen < *ver_online)) { - GUI::MsgUpdateSlic3r notification(p->ver_slic3r, *ver_online); + if (*ver_online > Slic3r::SEMVER && (! ver_online_seen || *ver_online_seen < *ver_online)) { + GUI::MsgUpdateSlic3r notification(Slic3r::SEMVER, *ver_online); notification.ShowModal(); if (notification.disable_version_check()) { app_config->set("version_check", "0"); From baaf66d138f3be357bbc4beb12c804c84b743c48 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 19 Aug 2019 10:35:18 +0200 Subject: [PATCH 569/627] avrdude: conf-generate: Fix line endings - always generate LF endings avrdude configuration embedding tool was generating platform specific line endings in avrdude-slic3r.conf.h --- src/avrdude/CMakeLists.txt | 2 +- src/avrdude/conf-generate.cpp | 36 ++++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/avrdude/CMakeLists.txt b/src/avrdude/CMakeLists.txt index a1930ad5f9..f2204db0c8 100644 --- a/src/avrdude/CMakeLists.txt +++ b/src/avrdude/CMakeLists.txt @@ -86,7 +86,7 @@ add_executable(avrdude-conf-gen conf-generate.cpp) add_custom_command( DEPENDS avrdude-conf-gen ${CMAKE_CURRENT_SOURCE_DIR}/avrdude-slic3r.conf OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/avrdude-slic3r.conf.h - COMMAND $ avrdude-slic3r.conf avrdude_slic3r_conf > avrdude-slic3r.conf.h + COMMAND $ avrdude-slic3r.conf avrdude_slic3r_conf avrdude-slic3r.conf.h WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) diff --git a/src/avrdude/conf-generate.cpp b/src/avrdude/conf-generate.cpp index 4aa80ae0af..1e05db5cea 100644 --- a/src/avrdude/conf-generate.cpp +++ b/src/avrdude/conf-generate.cpp @@ -6,36 +6,42 @@ int main(int argc, char const *argv[]) { - if (argc != 3) { - std::cerr << "Usage: " << argv[0] << " " << std::endl; + if (argc != 4) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; return -1; } - const char* filename = argv[1]; + const char* filename_in = argv[1]; const char* symbol = argv[2]; + const char* filename_out = argv[3]; size_t size = 0; - std::fstream file(filename); + std::fstream file(filename_in, std::ios::in | std::ios::binary); if (!file.good()) { - std::cerr << "Cannot read file: " << filename << std::endl; + std::cerr << "Cannot read file: " << filename_in << std::endl; } - std::cout << "/* WARN: This file is auto-generated from `" << filename << "` */" << std::endl; - std::cout << "const unsigned char " << symbol << "[] = {"; + std::fstream output(filename_out, std::ios::out | std::ios::trunc | std::ios::binary); + if (!output.good()) { + std::cerr << "Cannot open output file: " << filename_out << std::endl; + } + + output << "/* WARN: This file is auto-generated from `" << filename_in << "` */" << std::endl; + output << "const unsigned char " << symbol << "[] = {"; char c; - std::cout << std::hex; - std::cout.fill('0'); + output << std::hex; + output.fill('0'); for (file.get(c); !file.eof(); size++, file.get(c)) { - if (size % 12 == 0) { std::cout << "\n "; } - std::cout << "0x" << std::setw(2) << (unsigned)c << ", "; + if (size % 12 == 0) { output << "\n "; } + output << "0x" << std::setw(2) << (unsigned)c << ", "; } - std::cout << "\n 0, 0\n};\n"; + output << "\n 0, 0\n};\n"; - std::cout << std::dec; - std::cout << "const size_t " << symbol << "_size = " << size << ";" << std::endl; - std::cout << "const size_t " << symbol << "_size_yy = " << size + 2 << ";" << std::endl; + output << std::dec; + output << "const size_t " << symbol << "_size = " << size << ";" << std::endl; + output << "const size_t " << symbol << "_size_yy = " << size + 2 << ";" << std::endl; return 0; } From f937209619e7a32ba4f3a5571d647aa4e695b5e5 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 19 Aug 2019 10:55:11 +0200 Subject: [PATCH 570/627] Refactor catch(...) handlers in Http, OctoPrint, PrintHost, and Serial --- src/slic3r/Utils/Http.cpp | 3 ++- src/slic3r/Utils/OctoPrint.cpp | 3 ++- src/slic3r/Utils/PrintHost.cpp | 2 -- src/slic3r/Utils/Serial.cpp | 12 +++++------- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 79c4ecfa91..69301547c3 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -165,7 +166,7 @@ size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, v try { stream->read(buffer, size * nitems); - } catch (...) { + } catch (const std::exception &) { return CURL_READFUNC_ABORT; } diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index cafa69c554..09ca02071a 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -69,7 +70,7 @@ bool OctoPrint::test(wxString &msg) const msg = wxString::Format(_(L("Mismatched type of print host: %s")), text ? *text : "OctoPrint"); } } - catch (...) { + catch (const std::exception &) { res = false; msg = "Could not parse server response"; } diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index e9e39e6957..ab52b23443 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -170,8 +170,6 @@ void PrintHostJobQueue::priv::bg_thread_main() } } catch (const std::exception &e) { emit_error(e.what()); - } catch (...) { - emit_error("Unknown exception"); } // Cleanup leftover files, if any diff --git a/src/slic3r/Utils/Serial.cpp b/src/slic3r/Utils/Serial.cpp index acfd5fafd0..5944646926 100644 --- a/src/slic3r/Utils/Serial.cpp +++ b/src/slic3r/Utils/Serial.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -71,13 +72,10 @@ void parse_hardware_id(const std::string &hardware_id, SerialPortInfo &spi) std::regex pattern("USB\\\\.*VID_([[:xdigit:]]+)&PID_([[:xdigit:]]+).*"); std::smatch matches; if (std::regex_match(hardware_id, matches, pattern)) { - try { - vid = std::stoul(matches[1].str(), 0, 16); - pid = std::stoul(matches[2].str(), 0, 16); - spi.id_vendor = vid; - spi.id_product = pid; - } - catch (...) {} + vid = std::stoul(matches[1].str(), 0, 16); + pid = std::stoul(matches[2].str(), 0, 16); + spi.id_vendor = vid; + spi.id_product = pid; } } #endif From 0ded335488b1bc84f808cf335ef9751edcd9276f Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 19 Aug 2019 12:25:18 +0200 Subject: [PATCH 571/627] build: Add source file encoding check Source files are checked using a small utility in src/build-utils This is done to prevent bugs in build and localization caused by weird non-UTF-8 encodings interpreted by MSVC in terms of local codepages rather than UTF-8. --- src/CMakeLists.txt | 1 + src/avrdude/CMakeLists.txt | 3 + src/build-utils/CMakeLists.txt | 39 ++++++++++ src/build-utils/encoding-check.cpp | 119 +++++++++++++++++++++++++++++ src/libslic3r/CMakeLists.txt | 2 + src/semver/CMakeLists.txt | 2 + src/slic3r/CMakeLists.txt | 2 + 7 files changed, 168 insertions(+) create mode 100644 src/build-utils/CMakeLists.txt create mode 100644 src/build-utils/encoding-check.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9f3dbcec89..31cb24f24a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,6 @@ project(PrusaSlicer-native) +add_subdirectory(build-utils) add_subdirectory(admesh) add_subdirectory(avrdude) # boost/nowide diff --git a/src/avrdude/CMakeLists.txt b/src/avrdude/CMakeLists.txt index f2204db0c8..e6748a5aa2 100644 --- a/src/avrdude/CMakeLists.txt +++ b/src/avrdude/CMakeLists.txt @@ -100,6 +100,9 @@ add_dependencies(avrdude gen_conf_h) add_executable(avrdude-slic3r main-standalone.cpp) target_link_libraries(avrdude-slic3r avrdude) +encoding_check(avrdude) +encoding_check(avrdude-slic3r) + if (WIN32) target_compile_definitions(avrdude PRIVATE WIN32NATIVE=1) if(MSVC) diff --git a/src/build-utils/CMakeLists.txt b/src/build-utils/CMakeLists.txt new file mode 100644 index 0000000000..3b3961b562 --- /dev/null +++ b/src/build-utils/CMakeLists.txt @@ -0,0 +1,39 @@ + +add_executable(encoding-check encoding-check.cpp) + +# A global no-op target which depends on all encodings checks, +# and on which in turn all checked targets depend. +# This is done to make encoding checks the first thing to be +# performed before actually compiling any sources of the checked targets +# to make the check fail as early as possible. +add_custom_target(global-encoding-check + ALL + DEPENDS encoding-check +) + +# Function that adds source file encoding check to a target +# using the above encoding-check binary + +function(encoding_check TARGET) + # Obtain target source files + get_target_property(T_SOURCES ${TARGET} SOURCES) + + # Define top-level encoding check target for this ${TARGET} + add_custom_target(encoding-check-${TARGET} + DEPENDS encoding-check ${T_SOURCES} + COMMENT "Checking source files encodings for target ${TARGET}" + ) + + # Add checking of each source file as a subcommand of encoding-check-${TARGET} + foreach(file ${T_SOURCES}) + add_custom_command(TARGET encoding-check-${TARGET} + COMMAND $ ${TARGET} ${file} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + endforeach() + + # This adds dependency on encoding-check-${TARGET} to ${TARET} + # via the global-encoding-check + add_dependencies(global-encoding-check encoding-check-${TARGET}) + add_dependencies(${TARGET} global-encoding-check) +endfunction() diff --git a/src/build-utils/encoding-check.cpp b/src/build-utils/encoding-check.cpp new file mode 100644 index 0000000000..89f225572b --- /dev/null +++ b/src/build-utils/encoding-check.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include + + +/* + * The utf8_check() function scans the '\0'-terminated string starting + * at s. It returns a pointer to the first byte of the first malformed + * or overlong UTF-8 sequence found, or NULL if the string contains + * only correct UTF-8. It also spots UTF-8 sequences that could cause + * trouble if converted to UTF-16, namely surrogate characters + * (U+D800..U+DFFF) and non-Unicode positions (U+FFFE..U+FFFF). This + * routine is very likely to find a malformed sequence if the input + * uses any other encoding than UTF-8. It therefore can be used as a + * very effective heuristic for distinguishing between UTF-8 and other + * encodings. + * + * I wrote this code mainly as a specification of functionality; there + * are no doubt performance optimizations possible for certain CPUs. + * + * Markus Kuhn -- 2005-03-30 + * License: http://www.cl.cam.ac.uk/~mgk25/short-license.html + */ + +unsigned char *utf8_check(unsigned char *s) +{ + while (*s) { + if (*s < 0x80) { + // 0xxxxxxx + s++; + } else if ((s[0] & 0xe0) == 0xc0) { + // 110xxxxx 10xxxxxx + if ((s[1] & 0xc0) != 0x80 || + (s[0] & 0xfe) == 0xc0) { // overlong? + return s; + } else { + s += 2; + } + } else if ((s[0] & 0xf0) == 0xe0) { + // 1110xxxx 10xxxxxx 10xxxxxx + if ((s[1] & 0xc0) != 0x80 || + (s[2] & 0xc0) != 0x80 || + (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || // overlong? + (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || // surrogate? + (s[0] == 0xef && s[1] == 0xbf && + (s[2] & 0xfe) == 0xbe)) { // U+FFFE or U+FFFF? + return s; + } else { + s += 3; + } + } else if ((s[0] & 0xf8) == 0xf0) { + // 11110xxX 10xxxxxx 10xxxxxx 10xxxxxx + if ((s[1] & 0xc0) != 0x80 || + (s[2] & 0xc0) != 0x80 || + (s[3] & 0xc0) != 0x80 || + (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || // overlong? + (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) { // > U+10FFFF? + return s; + } else { + s += 4; + } + } else { + return s; + } + } + + return NULL; +} + + +int main(int argc, char const *argv[]) +{ + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return -1; + } + + const char* target = argv[1]; + const char* filename = argv[2]; + + const auto error_exit = [=](const char* error) { + std::cerr << "\n\tError: " << error << ": " << filename << "\n" + << "\tTarget: " << target << "\n" + << std::endl; + std::exit(-2); + }; + + std::ifstream file(filename, std::ios::binary | std::ios::ate); + const auto size = file.tellg(); + + if (size == 0) { + return 0; + } + + file.seekg(0, std::ios::beg); + std::vector buffer(size); + + if (file.read(buffer.data(), size)) { + buffer.push_back('\0'); + + // Check UTF-8 validity + if (utf8_check(reinterpret_cast(buffer.data())) != nullptr) { + error_exit("Source file does not contain (valid) UTF-8"); + } + + // Check against a BOM mark + if (buffer.size() >= 3 + && buffer[0] == '\xef' + && buffer[1] == '\xbb' + && buffer[2] == '\xbf') { + error_exit("Source file is valid UTF-8 but contains a BOM mark"); + } + } else { + error_exit("Could not read source file"); + } + + return 0; +} diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index a5abf43eca..1a9a153b94 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -187,6 +187,8 @@ add_library(libslic3r STATIC SLA/SLARasterWriter.cpp ) +encoding_check(libslic3r) + if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE) endif () diff --git a/src/semver/CMakeLists.txt b/src/semver/CMakeLists.txt index e3457bf291..c273121d49 100644 --- a/src/semver/CMakeLists.txt +++ b/src/semver/CMakeLists.txt @@ -5,3 +5,5 @@ add_library(semver STATIC semver.c semver.h ) + +encoding_check(semver) diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index e3a910d6d2..b3e2990f91 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -163,6 +163,8 @@ endif () add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES}) +encoding_check(libslic3r_gui) + target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui ${GLEW_LIBRARIES}) if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) From ef4ff55e55b5384be04e12b7dfcbf223f0f71ae8 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Mon, 19 Aug 2019 12:28:25 +0200 Subject: [PATCH 572/627] Fix encoding of a few files in GUI GUI/AboutDialog.cpp GUI/MainFrame.hpp GUI/OptionsGroup.cpp --- src/slic3r/GUI/AboutDialog.cpp | 4 ++-- src/slic3r/GUI/MainFrame.hpp | 2 +- src/slic3r/GUI/OptionsGroup.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index ca7c2c22d3..a4453c73ea 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -76,9 +76,9 @@ void CopyrightsDialog::fill_entries() { m_entries = { { "wxWidgets" , "2019 wxWidgets" , "https://www.wxwidgets.org/" }, - { "OpenGL" , "1997-2019 The Khronos™ Group Inc" , "https://www.opengl.org/" }, + { "OpenGL" , "1997-2019 The Khronosâ„¢ Group Inc" , "https://www.opengl.org/" }, { "GNU gettext" , "1998, 2019 Free Software Foundation, Inc." , "https://www.gnu.org/software/gettext/" }, - { "PoEdit" , "2019 Václav Slavík" , "https://poedit.net/" }, + { "PoEdit" , "2019 Václav Slavík" , "https://poedit.net/" }, { "ImGUI" , "2014-2019 Omar Cornut" , "https://github.com/ocornut/imgui" }, { "Eigen" , "" , "http://eigen.tuxfamily.org" }, { "ADMesh" , "1995, 1996 Anthony D. Martin; " diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 5d34be48ef..0e8a053e0f 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -1,4 +1,4 @@ -#ifndef slic3r_MainFrame_hpp_ +#ifndef slic3r_MainFrame_hpp_ #define slic3r_MainFrame_hpp_ #include "libslic3r/PrintConfig.hpp" diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 656e07a0e3..656df86ff2 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -1,4 +1,4 @@ -#include "OptionsGroup.hpp" +#include "OptionsGroup.hpp" #include "ConfigExceptions.hpp" #include From 4fbee3216b9a5067e83e3ae1d90cfc397ab2604e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 19 Aug 2019 19:48:07 +0200 Subject: [PATCH 573/627] Fix of Zoom by trackpad does not update until click #2750 For an unknown reason, if the scrolling is performed on Windows with the two finger gesture on touch pad, there is no Idle event generated on some computers. The Idle is not generated on Vojtech's laptop, it is generated on Enrico's laptop. evt.Skip() solves the issue on Vojtech's laptop. --- src/slic3r/GUI/GLCanvas3D.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a5906f619b..981cb38a93 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2474,6 +2474,13 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) evt.SetY(evt.GetY() * scale); #endif +#ifdef __WXMSW__ + // For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad, + // if the event is not allowed to be passed further. + // https://github.com/prusa3d/PrusaSlicer/issues/2750 + evt.Skip(); +#endif /* __WXMSW__ */ + // Performs layers editing updates, if enabled if (is_layers_editing_enabled()) { From 1f6aab312b544ff2c0225cc3516c543a3454f078 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 20 Aug 2019 09:01:09 +0200 Subject: [PATCH 574/627] 1st installment of export of gcode toolpaths to obj file --- src/slic3r/GUI/3DScene.cpp | 102 +++++++++++++++++++++++++++++++++ src/slic3r/GUI/3DScene.hpp | 3 + src/slic3r/GUI/GLCanvas3D.cpp | 5 ++ src/slic3r/GUI/GLCanvas3D.hpp | 2 + src/slic3r/GUI/GUI_Preview.hpp | 2 + src/slic3r/GUI/MainFrame.cpp | 11 +++- src/slic3r/GUI/MainFrame.hpp | 1 + src/slic3r/GUI/Plater.cpp | 30 ++++++++++ src/slic3r/GUI/Plater.hpp | 5 ++ 9 files changed, 160 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index ad2d9305f8..6d9302ba6c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -853,6 +853,108 @@ std::string GLVolumeCollection::log_memory_info() const return " (GLVolumeCollection RAM: " + format_memsize_MB(this->cpu_memory_used()) + " GPU: " + format_memsize_MB(this->gpu_memory_used()) + " Both: " + format_memsize_MB(this->gpu_memory_used()) + ")"; } +void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const +{ + if (filename == nullptr) + return; + + FILE* fp = boost::nowide::fopen(filename, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "GLVolumeCollection::export_toolpaths_to_obj: Couldn't open " << filename << " for writing"; + return; + } + + fprintf(fp, "# G-Code Toolpaths\n"); + fprintf(fp, "# Generated by %s based on Slic3r\n\n", SLIC3R_BUILD_ID); + + unsigned int vertices_count = 0; + unsigned int volume_count = 0; + + for (const GLVolume* volume : this->volumes) + { + if (!volume->is_extrusion_path) + continue; + + std::vector vertices_and_normals_interleaved; + std::vector triangle_indices; + std::vector quad_indices; + + if (!volume->indexed_vertex_array.vertices_and_normals_interleaved.empty()) + vertices_and_normals_interleaved = volume->indexed_vertex_array.vertices_and_normals_interleaved; + else if ((volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id != 0) && (volume->indexed_vertex_array.vertices_and_normals_interleaved_size != 0)) + { + vertices_and_normals_interleaved = std::vector(volume->indexed_vertex_array.vertices_and_normals_interleaved_size, 0.0f); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)); + glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, vertices_and_normals_interleaved.size() * sizeof(float), vertices_and_normals_interleaved.data())); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } + else + continue; + + if (!volume->indexed_vertex_array.triangle_indices.empty()) + triangle_indices = volume->indexed_vertex_array.triangle_indices; + else if ((volume->indexed_vertex_array.triangle_indices_VBO_id != 0) && (volume->indexed_vertex_array.triangle_indices_size != 0)) + { + triangle_indices = std::vector(volume->indexed_vertex_array.triangle_indices_size, 0); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.triangle_indices_VBO_id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, triangle_indices.size() * sizeof(int), triangle_indices.data())); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + + if (!volume->indexed_vertex_array.quad_indices.empty()) + quad_indices = volume->indexed_vertex_array.quad_indices; + if ((volume->indexed_vertex_array.quad_indices_VBO_id != 0) && (volume->indexed_vertex_array.quad_indices_size != 0)) + { + quad_indices = std::vector(volume->indexed_vertex_array.quad_indices_size, 0); + + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.quad_indices_VBO_id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, quad_indices.size() * sizeof(int), quad_indices.data())); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + + if (triangle_indices.empty() && quad_indices.empty()) + continue; + + fprintf(fp, "\n# vertices volume %d\n", volume_count); + for (unsigned int i = 0; i < vertices_and_normals_interleaved.size(); i += 6) + { + fprintf(fp, "v %f %f %f\n", vertices_and_normals_interleaved[i + 3], vertices_and_normals_interleaved[i + 4], vertices_and_normals_interleaved[i + 5]); + } + + fprintf(fp, "\n# normals volume %d\n", volume_count); + for (unsigned int i = 0; i < vertices_and_normals_interleaved.size(); i += 6) + { + fprintf(fp, "vn %f %f %f\n", vertices_and_normals_interleaved[i + 0], vertices_and_normals_interleaved[i + 1], vertices_and_normals_interleaved[i + 2]); + } + + fprintf(fp, "\n# triangular facets volume %d\n", volume_count); + for (unsigned int i = 0; i < triangle_indices.size(); i += 3) + { + int id_v1 = vertices_count + 1 + triangle_indices[i + 0]; + int id_v2 = vertices_count + 1 + triangle_indices[i + 1]; + int id_v3 = vertices_count + 1 + triangle_indices[i + 2]; + fprintf(fp, "f %d//%d %d//%d %d//%d\n", id_v1, id_v1, id_v2, id_v2, id_v3, id_v3); + } + + fprintf(fp, "\n# quadrangular facets volume %d\n", volume_count); + for (unsigned int i = 0; i < quad_indices.size(); i += 4) + { + int id_v1 = vertices_count + 1 + quad_indices[i + 0]; + int id_v2 = vertices_count + 1 + quad_indices[i + 1]; + int id_v3 = vertices_count + 1 + quad_indices[i + 2]; + int id_v4 = vertices_count + 1 + quad_indices[i + 3]; + fprintf(fp, "f %d//%d %d//%d %d//%d %d//%d\n", id_v1, id_v1, id_v2, id_v2, id_v3, id_v3, id_v4, id_v4); + } + + ++volume_count; + vertices_count += vertices_and_normals_interleaved.size() / 6; + } + + fclose(fp); +} + // caller is responsible for supplying NO lines with zero length static void thick_lines_to_indexed_vertex_array( const Lines &lines, diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 482c2f5805..7791947201 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -564,6 +564,9 @@ public: // Return CPU, GPU and total memory log line. std::string log_memory_info() const; + // Export the geometry of the GLVolumes toolpaths of this collection into the file with the given path, in obj format + void export_toolpaths_to_obj(const char* filename) const; + private: GLVolumeCollection(const GLVolumeCollection &other); GLVolumeCollection& operator=(const GLVolumeCollection &); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 981cb38a93..2a87efab9c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3403,6 +3403,11 @@ void GLCanvas3D::msw_rescale() m_warning_texture.msw_rescale(*this); } +void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const +{ + m_volumes.export_toolpaths_to_obj(filename); +} + bool GLCanvas3D::_is_shown_on_screen() const { return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 577682fe22..4fd37e9b19 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -644,6 +644,8 @@ public: void get_undoredo_toolbar_additional_tooltip(unsigned int item_id, std::string& text) { return m_undoredo_toolbar.get_additional_tooltip(item_id, text); } void set_undoredo_toolbar_additional_tooltip(unsigned int item_id, const std::string& text) { m_undoredo_toolbar.set_additional_tooltip(item_id, text); } + void export_toolpaths_to_obj(const char* filename) const; + private: bool _is_shown_on_screen() const; diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index b626bd7bde..401b17a4b2 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -130,6 +130,8 @@ public: void update_view_type(); + bool is_loaded() const { return m_loaded; } + private: bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b0945aea83..2609926edf 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -245,6 +245,11 @@ bool MainFrame::can_export_model() const return (m_plater != nullptr) && !m_plater->model().objects.empty(); } +bool MainFrame::can_export_toolpaths() const +{ + return (m_plater != nullptr) && (m_plater->printer_technology() == ptFFF) && m_plater->is_preview_shown() && m_plater->is_preview_loaded(); +} + bool MainFrame::can_export_supports() const { if ((m_plater == nullptr) || (m_plater->printer_technology() != ptSLA) || m_plater->model().objects.empty()) @@ -471,13 +476,17 @@ void MainFrame::init_menubar() append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &STL")) + dots, _(L("Export current plate as STL")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, menu_icon("export_plater"), nullptr, [this](){return can_export_model(); }, this); - append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL including supports")) + dots, _(L("Export current plate as STL including supports")), + append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL &including supports")) + dots, _(L("Export current plate as STL including supports")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(true); }, menu_icon("export_plater"), nullptr, [this](){return can_export_supports(); }, this); append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &AMF")) + dots, _(L("Export current plate as AMF")), [this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, menu_icon("export_plater"), nullptr, [this](){return can_export_model(); }, this); export_menu->AppendSeparator(); + append_menu_item(export_menu, wxID_ANY, _(L("Export &toolpaths as OBJ")) + dots, _(L("Export toolpaths as OBJ")), + [this](wxCommandEvent&) { if (m_plater) m_plater->export_toolpaths_to_obj(); }, menu_icon("export_plater"), nullptr, + [this]() {return can_export_toolpaths(); }, this); + export_menu->AppendSeparator(); append_menu_item(export_menu, wxID_ANY, _(L("Export &Config")) +dots +"\tCtrl+E", _(L("Export current configuration to file")), [this](wxCommandEvent&) { export_config(); }, menu_icon("export_config")); append_menu_item(export_menu, wxID_ANY, _(L("Export Config &Bundle")) + dots, _(L("Export all presets to file")), diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 0e8a053e0f..aa1e3d5002 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -65,6 +65,7 @@ class MainFrame : public DPIFrame bool can_start_new_project() const; bool can_save() const; bool can_export_model() const; + bool can_export_toolpaths() const; bool can_export_supports() const; bool can_export_gcode() const; bool can_send_gcode() const; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 99f63da8a1..2eb5729318 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1764,6 +1764,11 @@ struct Plater::priv void select_view(const std::string& direction); void select_view_3D(const std::string& name); void select_next_view_3D(); + + bool is_preview_shown() const { return current_panel == preview; } + bool is_preview_loaded() const { return preview->is_loaded(); } + bool is_view3D_shown() const { return current_panel == view3D; } + void reset_all_gizmos(); void update_ui_from_settings(); ProgressStatusBar* statusbar(); @@ -2458,6 +2463,7 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) case FT_AMF: case FT_3MF: case FT_GCODE: + case FT_OBJ: wildcard = file_wildcards(file_type); break; default: @@ -2508,6 +2514,12 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) dlg_title = _(L("Save file as:")); break; } + case FT_OBJ: + { + output_file.replace_extension("obj"); + dlg_title = _(L("Export OBJ file:")); + break; + } default: break; } @@ -4097,6 +4109,10 @@ void Plater::select_view(const std::string& direction) { p->select_view(directio void Plater::select_view_3D(const std::string& name) { p->select_view_3D(name); } +bool Plater::is_preview_shown() const { return p->is_preview_shown(); } +bool Plater::is_preview_loaded() const { return p->is_preview_loaded(); } +bool Plater::is_view3D_shown() const { return p->is_view3D_shown(); } + void Plater::select_all() { p->select_all(); } void Plater::deselect_all() { p->deselect_all(); } @@ -4420,6 +4436,20 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) } } +void Plater::export_toolpaths_to_obj() +{ + if ((printer_technology() != ptFFF) || !is_preview_loaded()) + return; + + wxString path = p->get_export_file(FT_OBJ); + if (path.empty()) + return; + + wxBusyCursor wait; + + p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str()); +} + void Plater::reslice() { // Stop arrange and (or) optimize rotation tasks. diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index d7f7f37911..40961ae467 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -156,6 +156,10 @@ public: void select_view(const std::string& direction); void select_view_3D(const std::string& name); + bool is_preview_shown() const; + bool is_preview_loaded() const; + bool is_view3D_shown() const; + // Called after the Preferences dialog is closed and the program settings are saved. // Update the UI based on the current preferences. void update_ui_from_settings(); @@ -179,6 +183,7 @@ public: void export_stl(bool extended = false, bool selection_only = false); void export_amf(); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); + void export_toolpaths_to_obj(); void reslice(); void reslice_SLA_supports(const ModelObject &object); void changed_object(int obj_idx); From 730283a9e9dc06426b1927e60c1dca52ef2da571 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 20 Aug 2019 09:51:25 +0200 Subject: [PATCH 575/627] Export to obj file only toolpaths visible in 3D scene --- src/slic3r/GUI/3DScene.cpp | 26 +++++++++++++++++--------- src/slic3r/GUI/3DScene.hpp | 2 +- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 6d9302ba6c..d5fed6d426 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -872,7 +872,7 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const for (const GLVolume* volume : this->volumes) { - if (!volume->is_extrusion_path) + if (!volume->is_active || !volume->is_extrusion_path) continue; std::vector vertices_and_normals_interleaved; @@ -896,22 +896,30 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const triangle_indices = volume->indexed_vertex_array.triangle_indices; else if ((volume->indexed_vertex_array.triangle_indices_VBO_id != 0) && (volume->indexed_vertex_array.triangle_indices_size != 0)) { - triangle_indices = std::vector(volume->indexed_vertex_array.triangle_indices_size, 0); + size_t size = std::min(volume->indexed_vertex_array.triangle_indices_size, volume->tverts_range.second - volume->tverts_range.first); + if (size != 0) + { + triangle_indices = std::vector(size, 0); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.triangle_indices_VBO_id)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, triangle_indices.size() * sizeof(int), triangle_indices.data())); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.triangle_indices_VBO_id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, volume->tverts_range.first * sizeof(int), size * sizeof(int), triangle_indices.data())); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } } if (!volume->indexed_vertex_array.quad_indices.empty()) quad_indices = volume->indexed_vertex_array.quad_indices; if ((volume->indexed_vertex_array.quad_indices_VBO_id != 0) && (volume->indexed_vertex_array.quad_indices_size != 0)) { - quad_indices = std::vector(volume->indexed_vertex_array.quad_indices_size, 0); + size_t size = std::min(volume->indexed_vertex_array.quad_indices_size, volume->qverts_range.second - volume->qverts_range.first); + if (size != 0) + { + quad_indices = std::vector(size, 0); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.quad_indices_VBO_id)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, quad_indices.size() * sizeof(int), quad_indices.data())); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, volume->indexed_vertex_array.quad_indices_VBO_id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, volume->qverts_range.first * sizeof(int), size * sizeof(int), quad_indices.data())); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } } if (triangle_indices.empty() && quad_indices.empty()) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 7791947201..9c0e50cad6 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -439,7 +439,7 @@ public: bool empty() const { return this->indexed_vertex_array.empty(); } - void set_range(coordf_t low, coordf_t high); + void set_range(double low, double high); void render() const; void render(int color_id, int detection_id, int worldmatrix_id) const; From 3e62d7ae64f1f09f08a21708f5b21794ce2b12b5 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 13 Aug 2019 09:37:44 +0200 Subject: [PATCH 576/627] Implemented button "Reset to Filament Color" --- src/slic3r/GUI/Field.cpp | 55 +++++++++++++++++++++++++++++++++++----- src/slic3r/GUI/Field.hpp | 10 +++----- src/slic3r/GUI/Tab.cpp | 28 +++++++++++++++++++- src/slic3r/GUI/Tab.hpp | 1 + 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 19a54016e5..2bee0018c9 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1033,11 +1033,12 @@ void ColourPicker::BUILD() // Validate the color wxString clr_str(m_opt.get_default_value()->get_at(m_opt_idx)); wxColour clr(clr_str); - if (! clr.IsOk()) { + if (clr_str.IsEmpty() || !clr.IsOk()) { clr = wxTransparentColour; } auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size); + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); temp->SetBackgroundStyle(wxBG_STYLE_PAINT); // // recast as a wxWindow to fit the calling convention @@ -1048,17 +1049,59 @@ void ColourPicker::BUILD() temp->SetToolTip(get_tooltip_text(clr_str)); } +void ColourPicker::set_undef_value(wxColourPickerCtrl* field) +{ + field->SetColour(wxTransparentColour); + + wxButton* btn = dynamic_cast(field->GetPickerCtrl()); + wxBitmap bmp = btn->GetBitmap(); + wxMemoryDC dc(bmp); + dc.SetTextForeground(*wxWHITE); + dc.SetFont(wxGetApp().normal_font()); + + const wxRect rect = wxRect(0, 0, bmp.GetWidth(), bmp.GetHeight()); + dc.DrawLabel("undef", rect, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL); + + dc.SelectObject(wxNullBitmap); + btn->SetBitmapLabel(bmp); +} + +void ColourPicker::set_value(const boost::any& value, bool change_event) +{ + m_disable_change_event = !change_event; + const wxString clr_str(boost::any_cast(value)); + auto field = dynamic_cast(window); + + wxColour clr(clr_str); + if (clr_str.IsEmpty() || !clr.IsOk()) + set_undef_value(field); + else + field->SetColour(clr); + + m_disable_change_event = false; +} + boost::any& ColourPicker::get_value() { -// boost::any m_value; - auto colour = static_cast(window)->GetColour(); - auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); - m_value = clr_str.ToStdString(); - + if (colour == wxTransparentColour) + m_value = std::string(""); + else { + auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue()); + m_value = clr_str.ToStdString(); + } return m_value; } +void ColourPicker::msw_rescale() +{ + Field::msw_rescale(); + + wxColourPickerCtrl* field = dynamic_cast(window); + if (field->GetColour() == wxTransparentColour) + set_undef_value(field); +} + void PointCtrl::BUILD() { auto temp = new wxBoxSizer(wxHORIZONTAL); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 6c16f90f27..761b99ed83 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -404,6 +404,8 @@ public: class ColourPicker : public Field { using Field::Field; + + void set_undef_value(wxColourPickerCtrl* field); public: ColourPicker(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} ColourPicker(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} @@ -417,13 +419,9 @@ public: dynamic_cast(window)->SetColour(value); m_disable_change_event = false; } - void set_value(const boost::any& value, bool change_event = false) { - m_disable_change_event = !change_event; - dynamic_cast(window)->SetColour(boost::any_cast(value)); - m_disable_change_event = false; - } - + void set_value(const boost::any& value, bool change_event = false) override; boost::any& get_value() override; + void msw_rescale() override; void enable() override { dynamic_cast(window)->Enable(); }; void disable() override{ dynamic_cast(window)->Disable(); }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a7d178e724..4afd3a1163 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2558,7 +2558,33 @@ void TabPrinter::build_unregular_pages() optgroup->append_single_option_line("retract_restart_extra_toolchange", extruder_idx); optgroup = page->new_optgroup(_(L("Preview"))); - optgroup->append_single_option_line("extruder_colour", extruder_idx); + + auto reset_to_filament_color = [this, extruder_idx](wxWindow* parent) { + add_scaled_button(parent, &m_reset_to_filament_color, "undo", + _(L("Reset to Filament Color")), wxBU_LEFT | wxBU_EXACTFIT); + ScalableButton* btn = m_reset_to_filament_color; + btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(btn); + + btn->Bind(wxEVT_BUTTON, [this, extruder_idx](wxCommandEvent& e) + { + std::vector colors = static_cast(m_config->option("extruder_colour"))->values; + colors[extruder_idx] = ""; + + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("extruder_colour", new ConfigOptionStrings(colors)); + load_config(new_conf); + + update_dirty(); + update(); + }); + + return sizer; + }; + line = optgroup->create_single_option_line("extruder_colour", extruder_idx); + line.append_widget(reset_to_filament_color); + optgroup->append_line(line); #ifdef __WXMSW__ layout_page(page); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index efefc47c56..0a90707005 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -371,6 +371,7 @@ public: wxButton* m_serial_test_btn = nullptr; ScalableButton* m_print_host_test_btn = nullptr; ScalableButton* m_printhost_browse_btn = nullptr; + ScalableButton* m_reset_to_filament_color = nullptr; size_t m_extruders_count; size_t m_extruders_count_old = 0; From 6780e74521111e4e08d8f3fdefab684f3e3e445b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 13 Aug 2019 14:56:02 +0200 Subject: [PATCH 577/627] Update 3D-scene after filament's color change --- src/slic3r/GUI/Tab.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4afd3a1163..22c5a548a5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1621,6 +1621,24 @@ void TabFilament::build() optgroup->append_single_option_line("filament_density"); optgroup->append_single_option_line("filament_cost"); + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + this->update_dirty(); + this->on_value_change(opt_key, value); + + if (opt_key == "filament_colour") + { + const Preset& printer_preset = m_preset_bundle->printers.get_edited_preset(); + const std::vector& colors = static_cast( + printer_preset.config.option("extruder_colour"))->values; + for (const std::string& color : colors) + if (color.empty()) { + // update scene + wxGetApp().plater()->update(); + break; + } + } + }; + optgroup = page->new_optgroup(_(L("Temperature")) + wxString(" °C", wxConvUTF8)); Line line = { _(L("Extruder")), "" }; line.append_option(optgroup->get_option("first_layer_temperature")); From 7706a5be3e596a8271f7fb69e76f4aacfbcaf292 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 19 Aug 2019 17:19:21 +0200 Subject: [PATCH 578/627] Fixed #2738 + Added update for plater config option "filament_colour", when we have multiple extruder print. --- src/slic3r/GUI/Plater.cpp | 24 ++++++++++++++++++++++++ src/slic3r/GUI/Tab.cpp | 18 ------------------ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 97a1da2728..71d8e39102 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4602,6 +4602,30 @@ void Plater::on_config_change(const DynamicPrintConfig &config) bool update_scheduled = false; bool bed_shape_changed = false; for (auto opt_key : p->config->diff(config)) { + if (opt_key == "filament_colour") + { + update_scheduled = true; // update should be scheduled (for update 3DScene) #2738 + + /* There is a case, when we use filament_color instead of extruder_color (when extruder_color == ""). + * Thus plater config option "filament_colour" should be filled with filament_presets values. + * Otherwise, on 3dScene will be used last edited filament color for all volumes with extruder_color == "". + */ + const std::vector filament_presets = wxGetApp().preset_bundle->filament_presets; + if (filament_presets.size() > 1 && + p->config->option(opt_key)->values.size() != config.option(opt_key)->values.size()) + { + const PresetCollection& filaments = wxGetApp().preset_bundle->filaments; + std::vector filament_colors; + filament_colors.reserve(filament_presets.size()); + + for (const std::string& filament_preset : filament_presets) + filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0)); + + p->config->option(opt_key)->values = filament_colors; + continue; + } + } + p->config->set_key_value(opt_key, config.option(opt_key)->clone()); if (opt_key == "printer_technology") this->set_printer_technology(config.opt_enum(opt_key)); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 22c5a548a5..4afd3a1163 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1621,24 +1621,6 @@ void TabFilament::build() optgroup->append_single_option_line("filament_density"); optgroup->append_single_option_line("filament_cost"); - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - this->update_dirty(); - this->on_value_change(opt_key, value); - - if (opt_key == "filament_colour") - { - const Preset& printer_preset = m_preset_bundle->printers.get_edited_preset(); - const std::vector& colors = static_cast( - printer_preset.config.option("extruder_colour"))->values; - for (const std::string& color : colors) - if (color.empty()) { - // update scene - wxGetApp().plater()->update(); - break; - } - } - }; - optgroup = page->new_optgroup(_(L("Temperature")) + wxString(" °C", wxConvUTF8)); Line line = { _(L("Extruder")), "" }; line.append_option(optgroup->get_option("first_layer_temperature")); From 58473f84eedac0556e11ab54db6f58ac0d1353a3 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 20 Aug 2019 11:33:58 +0200 Subject: [PATCH 579/627] Check for existence of gcode toolpaths that can be exported to obj file --- src/slic3r/GUI/3DScene.cpp | 42 +++++++++++++++++++++++++++++------ src/slic3r/GUI/3DScene.hpp | 1 + src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 10 ++++++--- src/slic3r/GUI/Plater.hpp | 3 ++- 7 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index d5fed6d426..60928c78b9 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -853,11 +853,39 @@ std::string GLVolumeCollection::log_memory_info() const return " (GLVolumeCollection RAM: " + format_memsize_MB(this->cpu_memory_used()) + " GPU: " + format_memsize_MB(this->gpu_memory_used()) + " Both: " + format_memsize_MB(this->gpu_memory_used()) + ")"; } +bool can_export_to_obj(const GLVolume& volume) +{ + if (!volume.is_active || !volume.is_extrusion_path) + return false; + + if (volume.indexed_vertex_array.triangle_indices.empty() && (std::min(volume.indexed_vertex_array.triangle_indices_size, volume.tverts_range.second - volume.tverts_range.first) == 0)) + return false; + + if (volume.indexed_vertex_array.quad_indices.empty() && (std::min(volume.indexed_vertex_array.quad_indices_size, volume.qverts_range.second - volume.qverts_range.first) == 0)) + return false; + + return true; +} + +bool GLVolumeCollection::has_toolpaths_to_export() const +{ + for (const GLVolume* volume : this->volumes) + { + if (can_export_to_obj(*volume)) + return true; + } + + return false; +} + void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const { if (filename == nullptr) return; + if (!has_toolpaths_to_export()) + return; + FILE* fp = boost::nowide::fopen(filename, "w"); if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "GLVolumeCollection::export_toolpaths_to_obj: Couldn't open " << filename << " for writing"; @@ -868,11 +896,11 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const fprintf(fp, "# Generated by %s based on Slic3r\n\n", SLIC3R_BUILD_ID); unsigned int vertices_count = 0; - unsigned int volume_count = 0; + unsigned int volumes_count = 0; for (const GLVolume* volume : this->volumes) { - if (!volume->is_active || !volume->is_extrusion_path) + if (!can_export_to_obj(*volume)) continue; std::vector vertices_and_normals_interleaved; @@ -925,19 +953,19 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const if (triangle_indices.empty() && quad_indices.empty()) continue; - fprintf(fp, "\n# vertices volume %d\n", volume_count); + fprintf(fp, "\n# vertices volume %d\n", volumes_count); for (unsigned int i = 0; i < vertices_and_normals_interleaved.size(); i += 6) { fprintf(fp, "v %f %f %f\n", vertices_and_normals_interleaved[i + 3], vertices_and_normals_interleaved[i + 4], vertices_and_normals_interleaved[i + 5]); } - fprintf(fp, "\n# normals volume %d\n", volume_count); + fprintf(fp, "\n# normals volume %d\n", volumes_count); for (unsigned int i = 0; i < vertices_and_normals_interleaved.size(); i += 6) { fprintf(fp, "vn %f %f %f\n", vertices_and_normals_interleaved[i + 0], vertices_and_normals_interleaved[i + 1], vertices_and_normals_interleaved[i + 2]); } - fprintf(fp, "\n# triangular facets volume %d\n", volume_count); + fprintf(fp, "\n# triangular facets volume %d\n", volumes_count); for (unsigned int i = 0; i < triangle_indices.size(); i += 3) { int id_v1 = vertices_count + 1 + triangle_indices[i + 0]; @@ -946,7 +974,7 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const fprintf(fp, "f %d//%d %d//%d %d//%d\n", id_v1, id_v1, id_v2, id_v2, id_v3, id_v3); } - fprintf(fp, "\n# quadrangular facets volume %d\n", volume_count); + fprintf(fp, "\n# quadrangular facets volume %d\n", volumes_count); for (unsigned int i = 0; i < quad_indices.size(); i += 4) { int id_v1 = vertices_count + 1 + quad_indices[i + 0]; @@ -956,7 +984,7 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const fprintf(fp, "f %d//%d %d//%d %d//%d %d//%d\n", id_v1, id_v1, id_v2, id_v2, id_v3, id_v3, id_v4, id_v4); } - ++volume_count; + ++volumes_count; vertices_count += vertices_and_normals_interleaved.size() / 6; } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 9c0e50cad6..e0603ebc0a 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -564,6 +564,7 @@ public: // Return CPU, GPU and total memory log line. std::string log_memory_info() const; + bool has_toolpaths_to_export() const; // Export the geometry of the GLVolumes toolpaths of this collection into the file with the given path, in obj format void export_toolpaths_to_obj(const char* filename) const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2a87efab9c..67f30c6e45 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3403,6 +3403,11 @@ void GLCanvas3D::msw_rescale() m_warning_texture.msw_rescale(*this); } +bool GLCanvas3D::has_toolpaths_to_export() const +{ + return m_volumes.has_toolpaths_to_export(); +} + void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const { m_volumes.export_toolpaths_to_obj(filename); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 4fd37e9b19..d23e29478e 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -644,6 +644,7 @@ public: void get_undoredo_toolbar_additional_tooltip(unsigned int item_id, std::string& text) { return m_undoredo_toolbar.get_additional_tooltip(item_id, text); } void set_undoredo_toolbar_additional_tooltip(unsigned int item_id, const std::string& text) { m_undoredo_toolbar.set_additional_tooltip(item_id, text); } + bool has_toolpaths_to_export() const; void export_toolpaths_to_obj(const char* filename) const; private: diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b3a87f635e..800ac55a9d 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -247,7 +247,7 @@ bool MainFrame::can_export_model() const bool MainFrame::can_export_toolpaths() const { - return (m_plater != nullptr) && (m_plater->printer_technology() == ptFFF) && m_plater->is_preview_shown() && m_plater->is_preview_loaded(); + return (m_plater != nullptr) && (m_plater->printer_technology() == ptFFF) && m_plater->is_preview_shown() && m_plater->is_preview_loaded() && m_plater->has_toolpaths_to_export(); } bool MainFrame::can_export_supports() const diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 773557ccf1..b804c24793 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4434,7 +4434,12 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) } } -void Plater::export_toolpaths_to_obj() +bool Plater::has_toolpaths_to_export() const +{ + return p->preview->get_canvas3d()->has_toolpaths_to_export(); +} + +void Plater::export_toolpaths_to_obj() const { if ((printer_technology() != ptFFF) || !is_preview_loaded()) return; @@ -4442,9 +4447,8 @@ void Plater::export_toolpaths_to_obj() wxString path = p->get_export_file(FT_OBJ); if (path.empty()) return; - + wxBusyCursor wait; - p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str()); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 40961ae467..0bd01835e7 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -183,7 +183,8 @@ public: void export_stl(bool extended = false, bool selection_only = false); void export_amf(); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); - void export_toolpaths_to_obj(); + bool has_toolpaths_to_export() const; + void export_toolpaths_to_obj() const; void reslice(); void reslice_SLA_supports(const ModelObject &object); void changed_object(int obj_idx); From 1fc05bbf00c8c5574f4ba06b9ec735a9954c9869 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Sun, 11 Aug 2019 18:57:33 +0200 Subject: [PATCH 580/627] ConfigWizard: Fix: Snapshot not being taken on user-requested Wizard run If the user launched Wizard from the menu and checked the reset checkbox, snapshot was not taken in case no new bundles were to be installed from resources (ie. most of the time). Snapshot is now taken as appropriate. --- src/slic3r/GUI/ConfigWizard.cpp | 33 +++++++++++++++++++++++++----- src/slic3r/Utils/PresetUpdater.cpp | 10 +++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 6a70cb9fdb..0115ff5f9c 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -25,6 +25,7 @@ #include "PresetBundle.hpp" #include "GUI.hpp" #include "GUI_Utils.hpp" +#include "slic3r/Config/Snapshot.hpp" #include "slic3r/Utils/PresetUpdater.hpp" @@ -32,6 +33,10 @@ namespace Slic3r { namespace GUI { +using Config::Snapshot; +using Config::SnapshotDB; + + // Printer model picker GUI control struct PrinterPickerEvent : public wxEvent @@ -1025,15 +1030,33 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese // Decide whether to create snapshot based on run_reason and the reset profile checkbox bool snapshot = true; + Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE; switch (run_reason) { - case ConfigWizard::RR_DATA_EMPTY: snapshot = false; break; - case ConfigWizard::RR_DATA_LEGACY: snapshot = true; break; - case ConfigWizard::RR_DATA_INCOMPAT: snapshot = false; break; // In this case snapshot is done by PresetUpdater with the appropriate reason - case ConfigWizard::RR_USER: snapshot = page_welcome->reset_user_profile(); break; + case ConfigWizard::RR_DATA_EMPTY: + snapshot = false; + break; + case ConfigWizard::RR_DATA_LEGACY: + snapshot = true; + break; + case ConfigWizard::RR_DATA_INCOMPAT: + // In this case snapshot has already been taken by + // PresetUpdater with the appropriate reason + snapshot = false; + break; + case ConfigWizard::RR_USER: + snapshot = page_welcome->reset_user_profile(); + snapshot_reason = Snapshot::SNAPSHOT_USER; + break; } + + if (snapshot) { + SnapshotDB::singleton().take_snapshot(*app_config, snapshot_reason); + } + if (install_bundles.size() > 0) { // Install bundles from resources. - updater->install_bundles_rsrc(std::move(install_bundles), snapshot); + // Don't create snapshot - we've already done that above if applicable. + updater->install_bundles_rsrc(std::move(install_bundles), false); } else { BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources"; } diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 589db36dce..d55063c7b5 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -35,6 +35,10 @@ using Slic3r::GUI::Config::Snapshot; using Slic3r::GUI::Config::SnapshotDB; + +// FIXME: Incompat bundle resolution doesn't deal with inherited user presets + + namespace Slic3r { @@ -624,11 +628,17 @@ PresetUpdater::UpdateResult PresetUpdater::config_update() const const auto res = dlg.ShowModal(); if (res == wxID_REPLACE) { BOOST_LOG_TRIVIAL(info) << "User wants to re-configure..."; + + // This effectively removes the incompatible bundles: + // (snapshot is taken beforehand) p->perform_updates(std::move(updates)); + GUI::ConfigWizard wizard(nullptr, GUI::ConfigWizard::RR_DATA_INCOMPAT); + if (! wizard.run(GUI::wxGetApp().preset_bundle, this)) { return R_INCOMPAT_EXIT; } + GUI::wxGetApp().load_current_presets(); return R_INCOMPAT_CONFIGURED; } else { From f63b3ba600d26efcd31db87979849d6fc25fc88e Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 20 Aug 2019 12:10:15 +0200 Subject: [PATCH 581/627] Fixed gcode toolpaths data for export to obj file when taken from cpu --- src/slic3r/GUI/3DScene.cpp | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 60928c78b9..88a7edee6c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -908,9 +908,11 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const std::vector quad_indices; if (!volume->indexed_vertex_array.vertices_and_normals_interleaved.empty()) + // data are in CPU memory vertices_and_normals_interleaved = volume->indexed_vertex_array.vertices_and_normals_interleaved; else if ((volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id != 0) && (volume->indexed_vertex_array.vertices_and_normals_interleaved_size != 0)) { + // data are in GPU memory vertices_and_normals_interleaved = std::vector(volume->indexed_vertex_array.vertices_and_normals_interleaved_size, 0.0f); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, volume->indexed_vertex_array.vertices_and_normals_interleaved_VBO_id)); @@ -921,9 +923,19 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const continue; if (!volume->indexed_vertex_array.triangle_indices.empty()) - triangle_indices = volume->indexed_vertex_array.triangle_indices; + { + // data are in CPU memory + size_t size = std::min(volume->indexed_vertex_array.triangle_indices.size(), volume->tverts_range.second - volume->tverts_range.first); + if (size != 0) + { + std::vector::const_iterator it_begin = volume->indexed_vertex_array.triangle_indices.begin() + volume->tverts_range.first; + std::vector::const_iterator it_end = volume->indexed_vertex_array.triangle_indices.begin() + volume->tverts_range.first + size; + std::copy(it_begin, it_end, std::back_inserter(triangle_indices)); + } + } else if ((volume->indexed_vertex_array.triangle_indices_VBO_id != 0) && (volume->indexed_vertex_array.triangle_indices_size != 0)) { + // data are in GPU memory size_t size = std::min(volume->indexed_vertex_array.triangle_indices_size, volume->tverts_range.second - volume->tverts_range.first); if (size != 0) { @@ -936,9 +948,19 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const } if (!volume->indexed_vertex_array.quad_indices.empty()) - quad_indices = volume->indexed_vertex_array.quad_indices; - if ((volume->indexed_vertex_array.quad_indices_VBO_id != 0) && (volume->indexed_vertex_array.quad_indices_size != 0)) { + // data are in CPU memory + size_t size = std::min(volume->indexed_vertex_array.quad_indices.size(), volume->qverts_range.second - volume->qverts_range.first); + if (size != 0) + { + std::vector::const_iterator it_begin = volume->indexed_vertex_array.quad_indices.begin() + volume->qverts_range.first; + std::vector::const_iterator it_end = volume->indexed_vertex_array.quad_indices.begin() + volume->qverts_range.first + size; + std::copy(it_begin, it_end, std::back_inserter(quad_indices)); + } + } + else if ((volume->indexed_vertex_array.quad_indices_VBO_id != 0) && (volume->indexed_vertex_array.quad_indices_size != 0)) + { + // data are in GPU memory size_t size = std::min(volume->indexed_vertex_array.quad_indices_size, volume->qverts_range.second - volume->qverts_range.first); if (size != 0) { From 52c24a16624dc93d10fc01bcbb9abcb3630bbadb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 20 Aug 2019 13:01:01 +0200 Subject: [PATCH 582/627] Set list manipulation action on LeftButton too Note: Doesn't work under OSX --- src/slic3r/GUI/GUI_ObjectList.cpp | 45 ++++++++++++++++++++----------- src/slic3r/GUI/GUI_ObjectList.hpp | 1 + 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 3d5efe63fc..0f1861550f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -134,7 +134,11 @@ ObjectList::ObjectList(wxWindow* parent) : selection_changed(); #ifndef __WXMSW__ set_tooltip_for_item(get_mouse_position_in_control()); -#endif //__WXMSW__ +#endif //__WXMSW__ + +#ifndef __WXOSX__ + list_manipulation(); +#endif //__WXOSX__ }); #ifdef __WXOSX__ @@ -169,7 +173,7 @@ ObjectList::ObjectList(wxWindow* parent) : #ifdef __WXMSW__ GetMainWindow()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { - set_tooltip_for_item(/*event.GetPosition()*/get_mouse_position_in_control()); + set_tooltip_for_item(get_mouse_position_in_control()); event.Skip(); }); #endif //__WXMSW__ @@ -330,28 +334,34 @@ void ObjectList::set_tooltip_for_item(const wxPoint& pt) * Just this->SetToolTip(tooltip) => has no effect. */ - if (!item) + if (!item || GetSelectedItemsCount() > 1) { GetMainWindow()->SetToolTip(""); // hide tooltip return; } - if (col->GetTitle() == _(L("Editing")) && GetSelectedItemsCount()<2) - GetMainWindow()->SetToolTip(_(L("Right button click the icon to change the object settings"))); - else if (col->GetTitle() == _("Name")) - { -#ifdef __WXMSW__ - if (pt.x < 2 * wxGetApp().em_unit() || pt.x > 4 * wxGetApp().em_unit()) { - GetMainWindow()->SetToolTip(""); // hide tooltip - return; - } + wxString tooltip = ""; + + if (col->GetTitle() == _(L("Editing"))) +#ifdef __WXOSX__ + tooltip = _(L("Right button click the icon to change the object settings")); +#else + tooltip = _(L("Click the icon to change the object settings")); #endif //__WXMSW__ + else if (col->GetTitle() == " ") +#ifdef __WXOSX__ + tooltip = _(L("Right button click the icon to change the object printable property")); +#else + tooltip = _(L("Click the icon to change the object printable property")); +#endif //__WXMSW__ + else if (col->GetTitle() == _("Name") && (pt.x >= 2 * wxGetApp().em_unit() && pt.x <= 4 * wxGetApp().em_unit())) + { int obj_idx, vol_idx; get_selected_item_indexes(obj_idx, vol_idx, item); - GetMainWindow()->SetToolTip(get_mesh_errors_list(obj_idx, vol_idx)); + tooltip = get_mesh_errors_list(obj_idx, vol_idx); } - else - GetMainWindow()->SetToolTip(""); // hide tooltip + + GetMainWindow()->SetToolTip(tooltip); } wxPoint ObjectList::get_mouse_position_in_control() @@ -744,6 +754,11 @@ void ObjectList::OnChar(wxKeyEvent& event) #endif /* __WXOSX__ */ void ObjectList::OnContextMenu(wxDataViewEvent&) +{ + list_manipulation(); +} + +void ObjectList::list_manipulation() { wxDataViewItem item; wxDataViewColumn* col; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index bdec060818..62003e5571 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -358,6 +358,7 @@ private: // void OnChar(wxKeyEvent& event); #endif /* __WXOSX__ */ void OnContextMenu(wxDataViewEvent &event); + void list_manipulation(); void OnBeginDrag(wxDataViewEvent &event); void OnDropPossible(wxDataViewEvent &event); From 53939796a653d99961fff05f32be275ef05e01ce Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 20 Aug 2019 14:22:31 +0200 Subject: [PATCH 583/627] GCode.cpp: Fix of temperature change before print Function set_extruder can be called before moving to the first layer, m_layer_index is then -1. We definitely don't want to set temperature for second layer in that case. --- src/libslic3r/GCode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7a6f686dfc..433167e895 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2887,7 +2887,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) // Set the temperature if the wipe tower didn't (not needed for non-single extruder MM) if (m_config.single_extruder_multi_material && !m_config.wipe_tower) { - int temp = (m_layer_index == 0 ? m_config.first_layer_temperature.get_at(extruder_id) : + int temp = (m_layer_index <= 0 ? m_config.first_layer_temperature.get_at(extruder_id) : m_config.temperature.get_at(extruder_id)); gcode += m_writer.set_temperature(temp, false); From 1b6490af4cded5d0fc4a1f25bac430d5aa4a9416 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 20 Aug 2019 14:35:23 +0200 Subject: [PATCH 584/627] Export materials file for gcode toolpaths when exported to obj file --- src/slic3r/GUI/3DScene.cpp | 47 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 88a7edee6c..d29e41d358 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -886,14 +886,51 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const if (!has_toolpaths_to_export()) return; - FILE* fp = boost::nowide::fopen(filename, "w"); + // collect color information to generate materials + std::set> colors; + for (const GLVolume* volume : this->volumes) + { + if (!can_export_to_obj(*volume)) + continue; + + std::array color; + ::memcpy((void*)color.data(), (const void*)volume->color, 4 * sizeof(float)); + colors.insert(color); + } + + // save materials file + boost::filesystem::path mat_filename(filename); + mat_filename.replace_extension("mtl"); + FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "GLVolumeCollection::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing"; + return; + } + + fprintf(fp, "# G-Code Toolpaths Materials\n"); + fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID); + + unsigned int colors_count = 1; + for (const std::array& color : colors) + { + fprintf(fp, "\nnewmtl material_%d\n", colors_count++); + fprintf(fp, "Ka 1 1 1\n"); + fprintf(fp, "Kd %f %f %f\n", color[0], color[1], color[2]); + fprintf(fp, "Ks 0 0 0\n"); + } + + fclose(fp); + + // save geometry file + fp = boost::nowide::fopen(filename, "w"); if (fp == nullptr) { BOOST_LOG_TRIVIAL(error) << "GLVolumeCollection::export_toolpaths_to_obj: Couldn't open " << filename << " for writing"; return; } fprintf(fp, "# G-Code Toolpaths\n"); - fprintf(fp, "# Generated by %s based on Slic3r\n\n", SLIC3R_BUILD_ID); + fprintf(fp, "# Generated by %s based on Slic3r\n", SLIC3R_BUILD_ID); + fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str()); unsigned int vertices_count = 0; unsigned int volumes_count = 0; @@ -987,6 +1024,12 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const fprintf(fp, "vn %f %f %f\n", vertices_and_normals_interleaved[i + 0], vertices_and_normals_interleaved[i + 1], vertices_and_normals_interleaved[i + 2]); } + std::array color; + ::memcpy((void*)color.data(), (const void*)volume->color, 4 * sizeof(float)); + colors.insert(color); + fprintf(fp, "\n# material volume %d\n", volumes_count); + fprintf(fp, "usemtl material_%lld\n", 1 + std::distance(colors.begin(), colors.find(color))); + fprintf(fp, "\n# triangular facets volume %d\n", volumes_count); for (unsigned int i = 0; i < triangle_indices.size(); i += 3) { From 6e522cea15833b0b525c8dddcf56fe9b9dceed40 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 20 Aug 2019 14:58:27 +0200 Subject: [PATCH 585/627] Added missing include --- src/slic3r/GUI/3DScene.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index d29e41d358..8d1a154b4d 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -25,6 +25,8 @@ #include #include +#include + #include #include From 03079d49282fa9328ec78862372ec1cba855f8f2 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 20 Aug 2019 15:12:32 +0200 Subject: [PATCH 586/627] avrdude: Fix: Generate the embedded conf in the bin dir, remove from repo The generated file avrdude-slic3r.conf.h is not kept in repo any longer - it was causing trouble for git diffing. It's now generated in $CMAKE_CURRENT_BINARY_DIR and included from there. The file embeds avrdude-slic3r.conf so that the conf doesn't need to be loaded from disk. --- src/avrdude/CMakeLists.txt | 9 +- src/avrdude/avrdude-slic3r.conf.h | 1188 ----------------------------- src/avrdude/conf-generate.cpp | 2 +- 3 files changed, 7 insertions(+), 1192 deletions(-) delete mode 100644 src/avrdude/avrdude-slic3r.conf.h diff --git a/src/avrdude/CMakeLists.txt b/src/avrdude/CMakeLists.txt index e6748a5aa2..8897200211 100644 --- a/src/avrdude/CMakeLists.txt +++ b/src/avrdude/CMakeLists.txt @@ -85,13 +85,13 @@ add_executable(avrdude-conf-gen conf-generate.cpp) # Config file embedding add_custom_command( DEPENDS avrdude-conf-gen ${CMAKE_CURRENT_SOURCE_DIR}/avrdude-slic3r.conf - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/avrdude-slic3r.conf.h - COMMAND $ avrdude-slic3r.conf avrdude_slic3r_conf avrdude-slic3r.conf.h + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/avrdude-slic3r.conf.h + COMMAND $ avrdude-slic3r.conf avrdude_slic3r_conf ${CMAKE_CURRENT_BINARY_DIR}/avrdude-slic3r.conf.h WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) add_custom_target(gen_conf_h - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/avrdude-slic3r.conf.h + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/avrdude-slic3r.conf.h ) add_library(avrdude STATIC ${AVRDUDE_SOURCES}) @@ -103,6 +103,9 @@ target_link_libraries(avrdude-slic3r avrdude) encoding_check(avrdude) encoding_check(avrdude-slic3r) +# Make avrdude-slic3r.conf.h includable: +target_include_directories(avrdude SYSTEM PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + if (WIN32) target_compile_definitions(avrdude PRIVATE WIN32NATIVE=1) if(MSVC) diff --git a/src/avrdude/avrdude-slic3r.conf.h b/src/avrdude/avrdude-slic3r.conf.h deleted file mode 100644 index 905b14ee8c..0000000000 --- a/src/avrdude/avrdude-slic3r.conf.h +++ /dev/null @@ -1,1188 +0,0 @@ -/* WARN: This file is auto-generated from `avrdude-slic3r.conf` */ -const unsigned char avrdude_slic3r_conf[] = { - 0x0a, 0x23, 0x0a, 0x23, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, - 0x20, 0x61, 0x20, 0x62, 0x61, 0x73, 0x69, 0x63, 0x20, 0x6d, 0x69, 0x6e, - 0x69, 0x6d, 0x61, 0x6c, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, - 0x66, 0x69, 0x6c, 0x65, 0x20, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, - 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, - 0x76, 0x72, 0x64, 0x75, 0x64, 0x65, 0x2d, 0x73, 0x6c, 0x69, 0x63, 0x33, - 0x72, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x0a, 0x23, 0x20, 0x73, - 0x6f, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x69, 0x74, 0x20, 0x63, 0x61, - 0x6e, 0x20, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, - 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x6c, 0x6f, 0x6e, 0x65, 0x20, 0x6d, - 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x0a, 0x23, 0x0a, 0x23, 0x20, 0x4f, - 0x6e, 0x6c, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x69, 0x74, 0x73, - 0x20, 0x75, 0x73, 0x65, 0x66, 0x75, 0x6c, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x50, 0x72, 0x75, 0x73, 0x61, 0x33, 0x44, 0x20, 0x64, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x20, 0x77, 0x65, 0x72, 0x65, 0x20, 0x63, 0x6f, 0x70, - 0x69, 0x65, 0x64, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x66, 0x72, 0x6f, - 0x6d, 0x20, 0x61, 0x76, 0x72, 0x64, 0x75, 0x64, 0x65, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x0a, 0x23, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x65, 0x65, 0x64, - 0x65, 0x64, 0x2c, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, - 0x61, 0x6e, 0x20, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, - 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, - 0x61, 0x76, 0x72, 0x64, 0x75, 0x64, 0x65, 0x2d, 0x73, 0x6c, 0x69, 0x63, - 0x33, 0x72, 0x0a, 0x23, 0x20, 0x76, 0x69, 0x61, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x2d, 0x43, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x2d, - 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2e, - 0x0a, 0x23, 0x0a, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, - 0x6d, 0x65, 0x72, 0x0a, 0x20, 0x20, 0x69, 0x64, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x77, 0x69, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x3b, 0x0a, - 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x57, - 0x69, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x74, 0x79, - 0x70, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x77, 0x69, 0x72, 0x69, 0x6e, - 0x67, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x20, - 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x3b, 0x0a, 0x3b, 0x0a, 0x0a, 0x70, - 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x0a, 0x20, 0x20, - 0x69, 0x64, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x61, 0x72, 0x64, - 0x75, 0x69, 0x6e, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x65, 0x73, - 0x63, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x41, 0x72, 0x64, 0x75, 0x69, 0x6e, - 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x22, 0x3b, - 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x72, - 0x69, 0x61, 0x6c, 0x3b, 0x0a, 0x3b, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x6d, 0x65, 0x72, 0x0a, 0x20, 0x20, 0x69, 0x64, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x61, 0x76, 0x72, 0x31, 0x30, 0x39, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x20, 0x20, 0x3d, - 0x20, 0x22, 0x41, 0x74, 0x6d, 0x65, 0x6c, 0x20, 0x41, 0x70, 0x70, 0x4e, - 0x6f, 0x74, 0x65, 0x20, 0x41, 0x56, 0x52, 0x31, 0x30, 0x39, 0x20, 0x42, - 0x6f, 0x6f, 0x74, 0x20, 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x72, 0x22, 0x3b, - 0x0a, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x22, - 0x62, 0x75, 0x74, 0x74, 0x65, 0x72, 0x66, 0x6c, 0x79, 0x22, 0x3b, 0x0a, - 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x20, 0x73, 0x65, 0x72, 0x69, - 0x61, 0x6c, 0x3b, 0x0a, 0x3b, 0x0a, 0x0a, 0x0a, 0x23, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x23, 0x20, - 0x41, 0x54, 0x6d, 0x65, 0x67, 0x61, 0x32, 0x35, 0x36, 0x30, 0x0a, 0x23, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x6d, 0x32, 0x35, 0x36, 0x30, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x41, 0x54, 0x6d, 0x65, 0x67, 0x61, 0x32, 0x35, 0x36, - 0x30, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x30, 0x78, 0x31, 0x65, 0x20, 0x30, 0x78, 0x39, 0x38, - 0x20, 0x30, 0x78, 0x30, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, - 0x61, 0x73, 0x5f, 0x6a, 0x74, 0x61, 0x67, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x79, 0x65, 0x73, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x73, 0x74, 0x6b, 0x35, 0x30, 0x30, 0x5f, 0x64, 0x65, - 0x76, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, - 0x42, 0x32, 0x3b, 0x0a, 0x23, 0x20, 0x20, 0x20, 0x20, 0x61, 0x76, 0x72, - 0x39, 0x31, 0x30, 0x5f, 0x64, 0x65, 0x76, 0x63, 0x6f, 0x64, 0x65, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x34, 0x33, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, 0x5f, 0x65, 0x72, 0x61, 0x73, 0x65, - 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x6c, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x30, 0x78, 0x44, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x62, 0x73, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x41, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x65, 0x74, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x64, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x67, 0x6d, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, - 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, - 0x20, 0x31, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x31, 0x20, 0x31, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, - 0x69, 0x70, 0x5f, 0x65, 0x72, 0x61, 0x73, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x73, 0x74, 0x61, 0x62, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x63, 0x6d, 0x64, 0x65, 0x78, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x73, 0x79, 0x6e, 0x63, 0x68, 0x6c, 0x6f, 0x6f, 0x70, 0x73, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x62, 0x79, 0x74, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, - 0x6c, 0x6c, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x6c, 0x6c, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x35, - 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x65, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x73, 0x74, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x70, 0x6f, 0x6c, 0x6c, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x73, - 0x74, 0x61, 0x63, 0x6b, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x78, 0x30, 0x45, 0x2c, - 0x20, 0x30, 0x78, 0x31, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x46, 0x2c, - 0x20, 0x30, 0x78, 0x31, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x32, 0x45, 0x2c, - 0x20, 0x30, 0x78, 0x33, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x32, 0x46, 0x2c, - 0x20, 0x30, 0x78, 0x33, 0x46, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x78, 0x34, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x35, - 0x45, 0x2c, 0x20, 0x30, 0x78, 0x34, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x35, - 0x46, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x37, - 0x45, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x37, - 0x46, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, - 0x78, 0x36, 0x36, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x36, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x37, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x37, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x41, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x41, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x42, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x42, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x78, 0x42, 0x45, 0x2c, - 0x20, 0x30, 0x78, 0x46, 0x44, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, - 0x20, 0x30, 0x78, 0x30, 0x31, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, - 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, - 0x20, 0x30, 0x78, 0x30, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, - 0x76, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x74, 0x61, 0x62, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x30, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x6d, 0x6f, - 0x64, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, - 0x61, 0x74, 0x63, 0x68, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x76, 0x74, - 0x67, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x77, - 0x65, 0x72, 0x6f, 0x66, 0x66, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x35, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x65, 0x74, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x6d, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x65, - 0x74, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x75, 0x73, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x68, 0x76, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x74, 0x61, 0x62, - 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, - 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, 0x65, - 0x72, 0x61, 0x73, 0x65, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, 0x69, 0x64, - 0x74, 0x68, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x63, 0x68, 0x69, 0x70, 0x65, 0x72, 0x61, 0x73, 0x65, 0x70, 0x6f, 0x6c, - 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x31, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, - 0x61, 0x6d, 0x66, 0x75, 0x73, 0x65, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, - 0x69, 0x64, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x66, 0x75, 0x73, - 0x65, 0x70, 0x6f, 0x6c, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, - 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6c, 0x6f, 0x63, 0x6b, 0x70, 0x75, 0x6c, - 0x73, 0x65, 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, - 0x6c, 0x6f, 0x63, 0x6b, 0x70, 0x6f, 0x6c, 0x6c, 0x74, 0x69, 0x6d, 0x65, - 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x69, 0x64, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x78, 0x33, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x70, - 0x6d, 0x63, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x35, 0x37, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x61, 0x6d, 0x70, 0x7a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x33, 0x62, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x66, 0x75, 0x6c, 0x6c, 0x70, - 0x61, 0x67, 0x65, 0x62, 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x20, 0x3d, 0x20, 0x6e, 0x6f, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x6f, 0x63, 0x64, 0x72, 0x65, 0x76, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x34, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, - 0x20, 0x22, 0x65, 0x65, 0x70, 0x72, 0x6f, 0x6d, 0x22, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x64, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x6e, 0x6f, 0x3b, 0x20, 0x2f, 0x2a, 0x20, 0x6c, 0x65, 0x61, 0x76, 0x65, - 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x22, 0x6e, 0x6f, 0x22, 0x20, 0x2a, - 0x2f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x38, 0x3b, 0x20, 0x20, 0x2f, 0x2a, 0x20, 0x66, - 0x6f, 0x72, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x20, - 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x20, - 0x2a, 0x2f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x34, 0x30, 0x39, 0x36, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, - 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, - 0x65, 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x31, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, - 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, - 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, - 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, - 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, - 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, - 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, - 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, - 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x20, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x61, 0x64, - 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x6f, 0x20, 0x3d, 0x20, 0x22, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, - 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, - 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x77, 0x72, 0x69, - 0x74, 0x65, 0x70, 0x61, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, - 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, - 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x6f, 0x64, 0x65, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x34, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x72, - 0x65, 0x61, 0x64, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x32, - 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, - 0x22, 0x66, 0x6c, 0x61, 0x73, 0x68, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x64, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x79, 0x65, - 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x36, 0x32, 0x31, 0x34, 0x34, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x6e, 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x67, 0x65, - 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x30, - 0x32, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x34, 0x35, 0x30, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, - 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, - 0x3d, 0x20, 0x34, 0x35, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, - 0x5f, 0x70, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, - 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x72, 0x65, 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x32, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, - 0x5f, 0x6c, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x31, 0x35, 0x20, 0x61, - 0x31, 0x34, 0x20, 0x61, 0x31, 0x33, 0x20, 0x61, 0x31, 0x32, 0x20, 0x20, - 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, - 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, - 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, - 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, - 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, - 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x68, 0x69, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x61, 0x31, 0x35, 0x20, 0x61, 0x31, 0x34, 0x20, 0x61, 0x31, 0x33, 0x20, - 0x61, 0x31, 0x32, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, - 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, - 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, - 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x6f, 0x20, - 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, - 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x6c, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, - 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, - 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, - 0x6f, 0x61, 0x64, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x68, 0x69, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, - 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, - 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, - 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, - 0x20, 0x20, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x70, 0x61, 0x67, - 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x61, 0x31, 0x35, 0x20, 0x61, 0x31, 0x34, 0x20, 0x61, - 0x31, 0x33, 0x20, 0x61, 0x31, 0x32, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, - 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, - 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x61, - 0x64, 0x5f, 0x65, 0x78, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x61, 0x31, 0x36, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x6f, 0x64, 0x65, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x34, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, - 0x20, 0x72, 0x65, 0x61, 0x64, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x3d, - 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, - 0x79, 0x20, 0x22, 0x6c, 0x66, 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, - 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, - 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x68, 0x66, 0x75, - 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, - 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x69, 0x20, - 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, - 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, - 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, - 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, - 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, - 0x79, 0x20, 0x22, 0x65, 0x66, 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, - 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x6c, 0x6f, 0x63, - 0x6b, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x30, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, - 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x31, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, 0x31, 0x20, - 0x31, 0x20, 0x69, 0x20, 0x69, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, - 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, - 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, - 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, - 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, - 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x63, 0x61, 0x6c, 0x69, 0x62, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x30, 0x20, 0x30, 0x20, 0x31, 0x20, 0x31, 0x20, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, - 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x73, 0x69, 0x67, - 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x61, 0x31, 0x20, 0x61, 0x30, 0x20, 0x20, - 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, - 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x20, - 0x20, 0x3b, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x23, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x0a, 0x23, 0x20, - 0x41, 0x54, 0x6d, 0x65, 0x67, 0x61, 0x33, 0x32, 0x75, 0x34, 0x0a, 0x23, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, - 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x6d, 0x33, 0x32, 0x75, 0x34, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x22, 0x41, 0x54, 0x6d, 0x65, 0x67, 0x61, 0x33, 0x32, 0x55, - 0x34, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x30, 0x78, 0x31, 0x65, 0x20, 0x30, 0x78, 0x39, 0x35, - 0x20, 0x30, 0x78, 0x38, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x75, - 0x73, 0x62, 0x70, 0x69, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x32, 0x66, 0x66, 0x34, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x61, 0x73, 0x5f, 0x6a, 0x74, - 0x61, 0x67, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x79, 0x65, 0x73, 0x3b, 0x0a, 0x23, 0x20, 0x20, 0x20, 0x20, 0x73, - 0x74, 0x6b, 0x35, 0x30, 0x30, 0x5f, 0x64, 0x65, 0x76, 0x63, 0x6f, 0x64, - 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x42, 0x32, 0x3b, 0x0a, - 0x23, 0x20, 0x20, 0x20, 0x20, 0x61, 0x76, 0x72, 0x39, 0x31, 0x30, 0x5f, - 0x64, 0x65, 0x76, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x78, 0x34, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, - 0x69, 0x70, 0x5f, 0x65, 0x72, 0x61, 0x73, 0x65, 0x5f, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x6c, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, - 0x44, 0x37, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x73, 0x32, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x30, 0x78, 0x41, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x72, 0x65, 0x73, 0x65, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x64, 0x65, 0x64, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x64, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, - 0x67, 0x6d, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x31, 0x20, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, 0x5f, 0x65, - 0x72, 0x61, 0x73, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x32, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, - 0x61, 0x62, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x31, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6d, 0x64, - 0x65, 0x78, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x32, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x79, 0x6e, - 0x63, 0x68, 0x6c, 0x6f, 0x6f, 0x70, 0x73, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x33, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x79, 0x74, - 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x6f, 0x6c, 0x6c, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x35, 0x33, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x72, 0x65, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x70, 0x6f, 0x73, 0x74, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x6c, - 0x6c, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x70, 0x5f, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x73, 0x74, 0x61, 0x63, 0x6b, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x78, 0x30, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x31, - 0x45, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x31, - 0x46, 0x2c, 0x20, 0x30, 0x78, 0x32, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x33, - 0x45, 0x2c, 0x20, 0x30, 0x78, 0x32, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x33, - 0x46, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, - 0x78, 0x34, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x35, 0x45, 0x2c, 0x20, 0x30, - 0x78, 0x34, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x35, 0x46, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x45, 0x2c, 0x20, 0x30, - 0x78, 0x36, 0x46, 0x2c, 0x20, 0x30, 0x78, 0x37, 0x46, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x78, 0x36, 0x36, 0x2c, - 0x20, 0x30, 0x78, 0x37, 0x36, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x37, 0x2c, - 0x20, 0x30, 0x78, 0x37, 0x37, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x41, 0x2c, - 0x20, 0x30, 0x78, 0x37, 0x41, 0x2c, 0x20, 0x30, 0x78, 0x36, 0x42, 0x2c, - 0x20, 0x30, 0x78, 0x37, 0x42, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x78, 0x42, 0x45, 0x2c, 0x20, 0x30, 0x78, 0x46, - 0x44, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, - 0x31, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, - 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, 0x30, 0x2c, 0x20, 0x30, 0x78, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x76, 0x65, 0x6e, 0x74, - 0x65, 0x72, 0x73, 0x74, 0x61, 0x62, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x6d, 0x6f, 0x64, 0x65, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x61, 0x74, 0x63, 0x68, - 0x63, 0x79, 0x63, 0x6c, 0x65, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x76, 0x74, 0x67, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x6f, 0x66, - 0x66, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x31, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, - 0x65, 0x73, 0x65, 0x74, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x6d, 0x73, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x65, 0x74, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x75, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x76, 0x6c, - 0x65, 0x61, 0x76, 0x65, 0x73, 0x74, 0x61, 0x62, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x35, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, 0x65, 0x72, 0x61, 0x73, 0x65, - 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x3d, - 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x68, 0x69, 0x70, - 0x65, 0x72, 0x61, 0x73, 0x65, 0x70, 0x6f, 0x6c, 0x6c, 0x74, 0x69, 0x6d, - 0x65, 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x31, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x66, 0x75, - 0x73, 0x65, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, 0x69, 0x64, 0x74, 0x68, - 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, - 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x66, 0x75, 0x73, 0x65, 0x70, 0x6f, 0x6c, - 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x20, 0x3d, 0x20, 0x35, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, - 0x6d, 0x6c, 0x6f, 0x63, 0x6b, 0x70, 0x75, 0x6c, 0x73, 0x65, 0x77, 0x69, - 0x64, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6c, 0x6f, 0x63, 0x6b, - 0x70, 0x6f, 0x6c, 0x6c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x20, - 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x64, - 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x33, 0x31, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x70, 0x6d, 0x63, 0x72, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x35, 0x37, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x72, 0x61, 0x6d, 0x70, 0x7a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x78, 0x33, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61, 0x6c, - 0x6c, 0x6f, 0x77, 0x66, 0x75, 0x6c, 0x6c, 0x70, 0x61, 0x67, 0x65, 0x62, - 0x69, 0x74, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x3d, 0x20, 0x6e, - 0x6f, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x63, 0x64, 0x72, - 0x65, 0x76, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x65, 0x65, - 0x70, 0x72, 0x6f, 0x6d, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x6e, 0x6f, 0x3b, 0x20, - 0x2f, 0x2a, 0x20, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x20, 0x74, 0x68, 0x69, - 0x73, 0x20, 0x22, 0x6e, 0x6f, 0x22, 0x20, 0x2a, 0x2f, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x34, 0x3b, 0x20, 0x20, 0x2f, 0x2a, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x70, - 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x20, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x6d, 0x69, 0x6e, 0x67, 0x20, 0x2a, 0x2f, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x30, 0x32, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, - 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, - 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x5f, - 0x70, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x30, - 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, - 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x61, 0x31, 0x30, - 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, - 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, - 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, - 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, - 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, - 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x20, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x6c, 0x6f, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x31, 0x20, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x31, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, - 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x70, 0x61, - 0x67, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, - 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, - 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, - 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x30, 0x78, 0x34, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x65, - 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x30, 0x3b, 0x0a, - 0x20, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x3d, 0x20, 0x34, 0x3b, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x73, - 0x69, 0x7a, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x66, 0x6c, 0x61, - 0x73, 0x68, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x70, 0x61, 0x67, 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x79, 0x65, 0x73, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x33, 0x32, 0x37, 0x36, 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x32, - 0x38, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, - 0x75, 0x6d, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x73, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, - 0x34, 0x35, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, - 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x34, 0x35, 0x30, 0x30, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, - 0x61, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x70, 0x31, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x62, 0x61, - 0x63, 0x6b, 0x5f, 0x70, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x30, 0x78, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x5f, 0x6c, 0x6f, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x20, 0x20, 0x30, 0x20, 0x61, 0x31, 0x34, 0x20, 0x61, 0x31, 0x33, - 0x20, 0x61, 0x31, 0x32, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, - 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, - 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, - 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, - 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x5f, - 0x68, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x61, 0x31, - 0x34, 0x20, 0x61, 0x31, 0x33, 0x20, 0x61, 0x31, 0x32, 0x20, 0x20, 0x20, - 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, 0x20, 0x20, 0x61, 0x39, - 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, - 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, - 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, - 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, - 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x20, 0x6f, - 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x6c, 0x6f, 0x61, 0x64, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6c, 0x6f, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, - 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, 0x61, 0x31, 0x20, 0x20, 0x61, - 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, - 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x68, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x61, 0x35, 0x20, 0x20, 0x61, 0x34, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x61, 0x33, 0x20, 0x20, 0x61, 0x32, 0x20, 0x20, - 0x61, 0x31, 0x20, 0x20, 0x61, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, - 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, - 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x20, 0x20, 0x20, 0x69, 0x22, 0x3b, - 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, - 0x69, 0x74, 0x65, 0x70, 0x61, 0x67, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, - 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, - 0x20, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x61, 0x31, - 0x35, 0x20, 0x61, 0x31, 0x34, 0x20, 0x61, 0x31, 0x33, 0x20, 0x61, 0x31, - 0x32, 0x20, 0x20, 0x20, 0x20, 0x61, 0x31, 0x31, 0x20, 0x61, 0x31, 0x30, - 0x20, 0x20, 0x61, 0x39, 0x20, 0x20, 0x61, 0x38, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x20, 0x61, 0x37, 0x20, 0x20, 0x61, 0x36, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x20, 0x78, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x6f, - 0x64, 0x65, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x30, 0x78, 0x34, 0x31, - 0x3b, 0x0a, 0x20, 0x20, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x20, 0x20, - 0x3d, 0x20, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x73, 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x31, 0x32, 0x38, 0x3b, 0x0a, - 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, - 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, - 0x72, 0x79, 0x20, 0x22, 0x6c, 0x66, 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, - 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, - 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, - 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, - 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, - 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x68, 0x66, - 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, - 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x69, - 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, - 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, - 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, - 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, - 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, - 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, - 0x72, 0x79, 0x20, 0x22, 0x65, 0x66, 0x75, 0x73, 0x65, 0x22, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, - 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, 0x20, 0x30, 0x20, 0x31, - 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, - 0x31, 0x20, 0x30, 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, - 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, - 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, - 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, - 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x6c, 0x6f, - 0x63, 0x6b, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, - 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x31, 0x20, 0x20, 0x31, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x22, - 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, - 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x22, 0x31, - 0x20, 0x30, 0x20, 0x31, 0x20, 0x30, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x31, 0x20, 0x31, 0x20, 0x31, 0x20, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x22, 0x2c, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x20, 0x31, - 0x20, 0x31, 0x20, 0x69, 0x20, 0x69, 0x20, 0x20, 0x69, 0x20, 0x69, 0x20, - 0x69, 0x20, 0x69, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x69, 0x6e, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, - 0x64, 0x65, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, - 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x61, - 0x79, 0x20, 0x3d, 0x20, 0x39, 0x30, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, - 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x63, 0x61, 0x6c, 0x69, 0x62, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, - 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x30, 0x20, 0x31, 0x20, 0x31, 0x20, - 0x20, 0x31, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, - 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x78, - 0x20, 0x78, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x30, 0x20, - 0x30, 0x20, 0x30, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x30, 0x20, 0x30, - 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, - 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x20, 0x6f, 0x22, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x20, 0x22, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x33, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, - 0x61, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x3d, 0x20, 0x22, 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, 0x31, - 0x20, 0x20, 0x31, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, - 0x30, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, - 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x20, - 0x20, 0x78, 0x20, 0x20, 0x78, 0x20, 0x61, 0x31, 0x20, 0x61, 0x30, 0x20, - 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, - 0x20, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, 0x6f, 0x20, 0x20, - 0x6f, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3b, 0x0a, - 0x20, 0x20, 0x3b, 0x0a, 0x0a, 0x0a, - 0, 0 -}; -const size_t avrdude_slic3r_conf_size = 14178; -const size_t avrdude_slic3r_conf_size_yy = 14180; diff --git a/src/avrdude/conf-generate.cpp b/src/avrdude/conf-generate.cpp index 1e05db5cea..e61f42eb4b 100644 --- a/src/avrdude/conf-generate.cpp +++ b/src/avrdude/conf-generate.cpp @@ -21,7 +21,7 @@ int main(int argc, char const *argv[]) std::cerr << "Cannot read file: " << filename_in << std::endl; } - std::fstream output(filename_out, std::ios::out | std::ios::trunc | std::ios::binary); + std::fstream output(filename_out, std::ios::out | std::ios::trunc); if (!output.good()) { std::cerr << "Cannot open output file: " << filename_out << std::endl; } From 448d773da0e6d9ee31448d8a8817971269f59d7f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 20 Aug 2019 15:26:57 +0200 Subject: [PATCH 587/627] Fixed default transparency for ColorPicker from sidebar (was appeared under OSX for empty extruder color). + Fixed wrong getting of instance printable value inside add_object_to_list() --- src/slic3r/GUI/GUI_ObjectList.cpp | 4 ++-- src/slic3r/GUI/Plater.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 0f1861550f..e0bdd26df9 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2306,14 +2306,14 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) { std::vector print_idicator(model_object->instances.size()); for (int i = 0; i < model_object->instances.size(); ++i) - print_idicator[i] = model_object->instances[i]->is_printable(); + print_idicator[i] = model_object->instances[i]->printable; const wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx); m_objects_model->AddInstanceChild(object_item, print_idicator); Expand(m_objects_model->GetInstanceRootItem(object_item)); } else - m_objects_model->SetPrintableState(model_object->instances[0]->is_printable() ? piPrintable : piUnprintable, obj_idx); + m_objects_model->SetPrintableState(model_object->instances[0]->printable ? piPrintable : piUnprintable, obj_idx); // add settings to the object, if it has those add_settings_item(item, &model_object->config); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 71d8e39102..476caead14 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -290,7 +290,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * auto colors = static_cast(cfg->option("extruder_colour")->clone()); wxColour clr(colors->values[extruder_idx]); if (!clr.IsOk()) - clr = wxTransparentColour; + clr = wxColour(0,0,0); // Don't set alfa to transparence auto data = new wxColourData(); data->SetChooseFull(1); From 7c94db063488092607877a54ed3e4f0cdac02745 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 20 Aug 2019 15:49:32 +0200 Subject: [PATCH 588/627] Adding new sla material parameters: (initial) exposition min/max --- src/libslic3r/PrintConfig.cpp | 32 ++++++++++++++++++++++ src/libslic3r/PrintConfig.hpp | 8 ++++++ src/libslic3r/SLAPrint.cpp | 18 +++++++++++++ src/slic3r/GUI/Preset.cpp | 3 ++- src/slic3r/GUI/Tab.cpp | 51 +++++++++++++++++++++++++++++++++++ 5 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 31de80e8be..037b248007 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2412,6 +2412,22 @@ void PrintConfigDef::init_sla_params() def->mode = comExpert; def->set_default_value(new ConfigOptionInt(10)); + def = this->add("exposure_time_min", coFloat); + def->label = L("Minimum exposure time"); + def->tooltip = L("Minimum exposure time"); + def->sidetext = L("s"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("exposure_time_max", coFloat); + def->label = L("Maximum exposure time"); + def->tooltip = L("Maximum exposure time"); + def->sidetext = L("s"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(100)); + def = this->add("exposure_time", coFloat); def->label = L("Exposure time"); def->tooltip = L("Exposure time"); @@ -2419,6 +2435,22 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->set_default_value(new ConfigOptionFloat(10)); + def = this->add("initial_exposure_time_min", coFloat); + def->label = L("Minimum initial exposure time"); + def->tooltip = L("Minimum initial exposure time"); + def->sidetext = L("s"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("initial_exposure_time_max", coFloat); + def->label = L("Maximum initial exposure time"); + def->tooltip = L("Maximum initial exposure time"); + def->sidetext = L("s"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(150)); + def = this->add("initial_exposure_time", coFloat); def->label = L("Initial exposure time"); def->tooltip = L("Initial exposure time"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 081f670e15..ca2a210f26 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1098,14 +1098,22 @@ class SLAMaterialConfig : public StaticPrintConfig STATIC_PRINT_CONFIG_CACHE(SLAMaterialConfig) public: ConfigOptionFloat initial_layer_height; + ConfigOptionFloat exposure_time_min; + ConfigOptionFloat exposure_time_max; ConfigOptionFloat exposure_time; + ConfigOptionFloat initial_exposure_time_min; + ConfigOptionFloat initial_exposure_time_max; ConfigOptionFloat initial_exposure_time; ConfigOptionFloats material_correction; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { OPT_PTR(initial_layer_height); + OPT_PTR(exposure_time_min); + OPT_PTR(exposure_time_max); OPT_PTR(exposure_time); + OPT_PTR(initial_exposure_time_min); + OPT_PTR(initial_exposure_time_max); OPT_PTR(initial_exposure_time); OPT_PTR(material_correction); } diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 1529f4bafc..8e99d6ddc5 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -692,6 +692,20 @@ std::string SLAPrint::validate() const } } + double expt_max = m_material_config.exposure_time_max.getFloat(); + double expt_min = m_material_config.exposure_time_min.getFloat(); + double expt_cur = m_material_config.exposure_time.getFloat(); + + if (expt_cur < expt_min || expt_cur > expt_max) + return L("Exposition time is out of predefined bounds."); + + double iexpt_max = m_material_config.initial_exposure_time_max.getFloat(); + double iexpt_min = m_material_config.initial_exposure_time_min.getFloat(); + double iexpt_cur = m_material_config.initial_exposure_time.getFloat(); + + if (iexpt_cur < iexpt_min || iexpt_cur > iexpt_max) + return L("Initial exposition time is out of predefined bounds."); + return ""; } @@ -1586,7 +1600,11 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector steps_rasterize = { + "exposure_time_min", + "exposure_time_max", "exposure_time", + "initial_exposure_time_min", + "initial_exposure_time_max", "initial_exposure_time", "display_width", "display_height", diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 833da238a5..82124761a5 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -500,7 +500,8 @@ const std::vector& Preset::sla_material_options() if (s_opts.empty()) { s_opts = { "initial_layer_height", - "exposure_time", "initial_exposure_time", + "exposure_time_min", "exposure_time_max", "exposure_time", + "initial_exposure_time_min", "initial_exposure_time_max", "initial_exposure_time", "material_correction", "material_notes", "default_sla_material_profile", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 4afd3a1163..188bb50cf0 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3614,7 +3614,11 @@ void TabSLAMaterial::build() optgroup->append_single_option_line("initial_layer_height"); optgroup = page->new_optgroup(_(L("Exposure"))); + optgroup->append_single_option_line("exposure_time_min"); + optgroup->append_single_option_line("exposure_time_max"); optgroup->append_single_option_line("exposure_time"); + optgroup->append_single_option_line("initial_exposure_time_min"); + optgroup->append_single_option_line("initial_exposure_time_max"); optgroup->append_single_option_line("initial_exposure_time"); optgroup = page->new_optgroup(_(L("Corrections"))); @@ -3679,11 +3683,58 @@ void TabSLAMaterial::reload_config() Tab::reload_config(); } + +namespace { + +enum e_cmp {EQUAL = 1, SMALLER = 2, GREATER = 4, SMALLER_EQ = 3, GREATER_EQ = 5}; + +void bound_check(Tab &tb, e_cmp cmp, const char *id, const char *boundid) +{ + double bound = tb.m_config->opt_float(boundid); + double value = tb.m_config->opt_float(id); + + auto boundlabel = tb.m_config->def()->get(boundid)->label; + auto valuelabel = tb.m_config->def()->get(id)->label; + + double ddiff = value - bound; + int diff = ddiff < 0 ? SMALLER : (std::abs(ddiff) < EPSILON ? EQUAL : GREATER); + + wxString fmt; + switch (cmp) { + case EQUAL: fmt = _(L("%s should be equal to %s")); break; + case SMALLER: fmt = _(L("%s should be smaller than %s")); break; + case GREATER: fmt = _(L("%s should be greater than %s")); break; + case SMALLER_EQ: fmt = _(L("%s should be smaller or equal to %s")); break; + case GREATER_EQ: fmt = _(L("%s should be greater or equal to %s")); break; + } + + if ((cmp | diff) != cmp) { + wxString msg_text = wxString::Format(fmt, valuelabel, boundlabel); + + wxMessageDialog dialog(tb.parent(), msg_text, + _(L("Value outside bounds")), + wxICON_WARNING | wxOK); + + DynamicPrintConfig new_conf = *tb.m_config; + if (dialog.ShowModal() == wxID_OK) + new_conf.set_key_value(id, new ConfigOptionFloat(bound)); + + tb.load_config(new_conf); + } +}; + +} + void TabSLAMaterial::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) return; + bound_check(*this, e_cmp::GREATER_EQ, "exposure_time", "exposure_time_min"); + bound_check(*this, e_cmp::SMALLER_EQ, "exposure_time", "exposure_time_max"); + bound_check(*this, e_cmp::GREATER_EQ, "initial_exposure_time", "initial_exposure_time_min"); + bound_check(*this, e_cmp::SMALLER_EQ, "initial_exposure_time", "initial_exposure_time_max"); + // #ys_FIXME. Just a template for this function // m_update_cnt++; // ! something to update From bafa4d6d1909c3e9541b349129e6929ee962368b Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 20 Aug 2019 16:00:26 +0200 Subject: [PATCH 589/627] Follow up: Adding new sla material parameters... Small fix for redundant operations. --- src/slic3r/GUI/Tab.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 188bb50cf0..0301653371 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3699,16 +3699,17 @@ void bound_check(Tab &tb, e_cmp cmp, const char *id, const char *boundid) double ddiff = value - bound; int diff = ddiff < 0 ? SMALLER : (std::abs(ddiff) < EPSILON ? EQUAL : GREATER); - wxString fmt; - switch (cmp) { - case EQUAL: fmt = _(L("%s should be equal to %s")); break; - case SMALLER: fmt = _(L("%s should be smaller than %s")); break; - case GREATER: fmt = _(L("%s should be greater than %s")); break; - case SMALLER_EQ: fmt = _(L("%s should be smaller or equal to %s")); break; - case GREATER_EQ: fmt = _(L("%s should be greater or equal to %s")); break; - } - if ((cmp | diff) != cmp) { + wxString fmt; + + switch (cmp) { + case EQUAL: fmt = _(L("%s should be equal to %s")); break; + case SMALLER: fmt = _(L("%s should be smaller than %s")); break; + case GREATER: fmt = _(L("%s should be greater than %s")); break; + case SMALLER_EQ: fmt = _(L("%s should be smaller or equal to %s")); break; + case GREATER_EQ: fmt = _(L("%s should be greater or equal to %s")); break; + } + wxString msg_text = wxString::Format(fmt, valuelabel, boundlabel); wxMessageDialog dialog(tb.parent(), msg_text, From fd3fe75d1ca4ddf1a36eb89d7210a4ec0e8f4afa Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 20 Aug 2019 16:19:30 +0200 Subject: [PATCH 590/627] Reworked the rename_file() function on Windows to work reliably and atomically. The code was taken from the llvm project, it is complex and hopefully it covers all the Windows file system quirks. Vojtech has highest hopes, that this will fix the various PrusaSlicer.ini file corruptions. Enabled the locales switching and error handling on Linux as well, where now the missing locales are reported and running the locale-gen tool is recommended. --- src/PrusaSlicer.cpp | 9 +- src/libslic3r/GCode.cpp | 2 +- src/libslic3r/GCodeTimeEstimator.cpp | 2 +- src/libslic3r/Utils.hpp | 2 +- src/libslic3r/utils.cpp | 288 ++++++++++++++++++++++----- src/slic3r/GUI/AppConfig.cpp | 7 +- 6 files changed, 248 insertions(+), 62 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 72cbe150fc..92ff65a843 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -66,7 +66,12 @@ int CLI::run(int argc, char **argv) boost::nowide::nowide_filesystem(); } catch (const std::runtime_error& ex) { std::string caption = std::string(SLIC3R_APP_NAME) + " Error"; - std::string text = std::string("An error occured while setting up locale.\n") + SLIC3R_APP_NAME + " will now terminate.\n\n" + ex.what(); + std::string text = std::string("An error occured while setting up locale.\n") + ( +#if !defined(_WIN32) && !defined(__APPLE__) + // likely some linux system + "You may need to reconfigure the missing locales, likely by running the \"locale-gen\"" and \"dpkg-reconfigure locales\" commands.\n" +#endif + SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what(); #ifdef SLIC3R_GUI if (m_actions.empty()) MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR); @@ -426,7 +431,7 @@ int CLI::run(int argc, char **argv) outfile_final = sla_print.print_statistics().finalize_output_path(outfile); sla_print.export_raster(outfile_final); } - if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final) != 0) { + if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final)) { boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; return 1; } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 7a6f686dfc..8a007023bb 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -607,7 +607,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ m_analyzer.reset(); } - if (rename_file(path_tmp, path) != 0) + if (rename_file(path_tmp, path)) throw std::runtime_error( std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "Is " + path_tmp + " locked?" + '\n'); diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index 8a2e1266a8..a03d3c7c85 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -390,7 +390,7 @@ namespace Slic3r { fclose(out); in.close(); - if (rename_file(path_tmp, filename) != 0) + if (rename_file(path_tmp, filename)) throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' + "Is " + path_tmp + " locked?" + '\n'); diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index b19027826b..2b1fdb2417 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -61,7 +61,7 @@ extern std::string normalize_utf8_nfc(const char *src); // Safely rename a file even if the target exists. // On Windows, the file explorer (or anti-virus or whatever else) often locks the file // for a short while, so the file may not be movable. Retry while we see recoverable errors. -extern int rename_file(const std::string &from, const std::string &to); +extern std::error_code rename_file(const std::string &from, const std::string &to); // Copy a file, adjust the access attributes, so that the target is writable. extern int copy_file(const std::string &from, const std::string &to); diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 8fcd611acc..e26ed38394 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -173,67 +173,247 @@ const std::string& data_dir() return g_data_dir; } +#ifdef _WIN32 +// The following helpers are borrowed from the LLVM project https://github.com/llvm +namespace WindowsSupport +{ + template + class ScopedHandle { + typedef typename HandleTraits::handle_type handle_type; + handle_type Handle; + ScopedHandle(const ScopedHandle &other) = delete; + void operator=(const ScopedHandle &other) = delete; + public: + ScopedHandle() : Handle(HandleTraits::GetInvalid()) {} + explicit ScopedHandle(handle_type h) : Handle(h) {} + ~ScopedHandle() { if (HandleTraits::IsValid(Handle)) HandleTraits::Close(Handle); } + handle_type take() { + handle_type t = Handle; + Handle = HandleTraits::GetInvalid(); + return t; + } + ScopedHandle &operator=(handle_type h) { + if (HandleTraits::IsValid(Handle)) + HandleTraits::Close(Handle); + Handle = h; + return *this; + } + // True if Handle is valid. + explicit operator bool() const { return HandleTraits::IsValid(Handle) ? true : false; } + operator handle_type() const { return Handle; } + }; + + struct CommonHandleTraits { + typedef HANDLE handle_type; + static handle_type GetInvalid() { return INVALID_HANDLE_VALUE; } + static void Close(handle_type h) { ::CloseHandle(h); } + static bool IsValid(handle_type h) { return h != GetInvalid(); } + }; + + typedef ScopedHandle ScopedFileHandle; + + std::error_code map_windows_error(unsigned windows_error_code) + { + #define MAP_ERR_TO_COND(x, y) case x: return std::make_error_code(std::errc::y) + switch (windows_error_code) { + MAP_ERR_TO_COND(ERROR_ACCESS_DENIED, permission_denied); + MAP_ERR_TO_COND(ERROR_ALREADY_EXISTS, file_exists); + MAP_ERR_TO_COND(ERROR_BAD_UNIT, no_such_device); + MAP_ERR_TO_COND(ERROR_BUFFER_OVERFLOW, filename_too_long); + MAP_ERR_TO_COND(ERROR_BUSY, device_or_resource_busy); + MAP_ERR_TO_COND(ERROR_BUSY_DRIVE, device_or_resource_busy); + MAP_ERR_TO_COND(ERROR_CANNOT_MAKE, permission_denied); + MAP_ERR_TO_COND(ERROR_CANTOPEN, io_error); + MAP_ERR_TO_COND(ERROR_CANTREAD, io_error); + MAP_ERR_TO_COND(ERROR_CANTWRITE, io_error); + MAP_ERR_TO_COND(ERROR_CURRENT_DIRECTORY, permission_denied); + MAP_ERR_TO_COND(ERROR_DEV_NOT_EXIST, no_such_device); + MAP_ERR_TO_COND(ERROR_DEVICE_IN_USE, device_or_resource_busy); + MAP_ERR_TO_COND(ERROR_DIR_NOT_EMPTY, directory_not_empty); + MAP_ERR_TO_COND(ERROR_DIRECTORY, invalid_argument); + MAP_ERR_TO_COND(ERROR_DISK_FULL, no_space_on_device); + MAP_ERR_TO_COND(ERROR_FILE_EXISTS, file_exists); + MAP_ERR_TO_COND(ERROR_FILE_NOT_FOUND, no_such_file_or_directory); + MAP_ERR_TO_COND(ERROR_HANDLE_DISK_FULL, no_space_on_device); + MAP_ERR_TO_COND(ERROR_INVALID_ACCESS, permission_denied); + MAP_ERR_TO_COND(ERROR_INVALID_DRIVE, no_such_device); + MAP_ERR_TO_COND(ERROR_INVALID_FUNCTION, function_not_supported); + MAP_ERR_TO_COND(ERROR_INVALID_HANDLE, invalid_argument); + MAP_ERR_TO_COND(ERROR_INVALID_NAME, invalid_argument); + MAP_ERR_TO_COND(ERROR_LOCK_VIOLATION, no_lock_available); + MAP_ERR_TO_COND(ERROR_LOCKED, no_lock_available); + MAP_ERR_TO_COND(ERROR_NEGATIVE_SEEK, invalid_argument); + MAP_ERR_TO_COND(ERROR_NOACCESS, permission_denied); + MAP_ERR_TO_COND(ERROR_NOT_ENOUGH_MEMORY, not_enough_memory); + MAP_ERR_TO_COND(ERROR_NOT_READY, resource_unavailable_try_again); + MAP_ERR_TO_COND(ERROR_OPEN_FAILED, io_error); + MAP_ERR_TO_COND(ERROR_OPEN_FILES, device_or_resource_busy); + MAP_ERR_TO_COND(ERROR_OUTOFMEMORY, not_enough_memory); + MAP_ERR_TO_COND(ERROR_PATH_NOT_FOUND, no_such_file_or_directory); + MAP_ERR_TO_COND(ERROR_BAD_NETPATH, no_such_file_or_directory); + MAP_ERR_TO_COND(ERROR_READ_FAULT, io_error); + MAP_ERR_TO_COND(ERROR_RETRY, resource_unavailable_try_again); + MAP_ERR_TO_COND(ERROR_SEEK, io_error); + MAP_ERR_TO_COND(ERROR_SHARING_VIOLATION, permission_denied); + MAP_ERR_TO_COND(ERROR_TOO_MANY_OPEN_FILES, too_many_files_open); + MAP_ERR_TO_COND(ERROR_WRITE_FAULT, io_error); + MAP_ERR_TO_COND(ERROR_WRITE_PROTECT, permission_denied); + MAP_ERR_TO_COND(WSAEACCES, permission_denied); + MAP_ERR_TO_COND(WSAEBADF, bad_file_descriptor); + MAP_ERR_TO_COND(WSAEFAULT, bad_address); + MAP_ERR_TO_COND(WSAEINTR, interrupted); + MAP_ERR_TO_COND(WSAEINVAL, invalid_argument); + MAP_ERR_TO_COND(WSAEMFILE, too_many_files_open); + MAP_ERR_TO_COND(WSAENAMETOOLONG, filename_too_long); + default: + return std::error_code(windows_error_code, std::system_category()); + } + #undef MAP_ERR_TO_COND + } + + static std::error_code rename_internal(HANDLE from_handle, const std::wstring &wide_to, bool replace_if_exists) + { + std::vector rename_info_buf(sizeof(FILE_RENAME_INFO) - sizeof(wchar_t) + (wide_to.size() * sizeof(wchar_t))); + FILE_RENAME_INFO &rename_info = *reinterpret_cast(rename_info_buf.data()); + rename_info.ReplaceIfExists = replace_if_exists; + rename_info.RootDirectory = 0; + rename_info.FileNameLength = DWORD(wide_to.size() * sizeof(wchar_t)); + std::copy(wide_to.begin(), wide_to.end(), &rename_info.FileName[0]); + + ::SetLastError(ERROR_SUCCESS); + if (! ::SetFileInformationByHandle(from_handle, FileRenameInfo, &rename_info, (DWORD)rename_info_buf.size())) { + unsigned Error = GetLastError(); + if (Error == ERROR_SUCCESS) + Error = ERROR_CALL_NOT_IMPLEMENTED; // Wine doesn't always set error code. + return map_windows_error(Error); + } + + return std::error_code(); + } + + static std::error_code real_path_from_handle(HANDLE H, std::wstring &buffer) + { + buffer.resize(MAX_PATH + 1); + DWORD CountChars = ::GetFinalPathNameByHandleW(H, (LPWSTR)buffer.data(), (DWORD)buffer.size() - 1, FILE_NAME_NORMALIZED); + if (CountChars > buffer.size()) { + // The buffer wasn't big enough, try again. In this case the return value *does* indicate the size of the null terminator. + buffer.resize((size_t)CountChars); + CountChars = ::GetFinalPathNameByHandleW(H, (LPWSTR)buffer.data(), (DWORD)buffer.size() - 1, FILE_NAME_NORMALIZED); + } + if (CountChars == 0) + return map_windows_error(GetLastError()); + buffer.resize(CountChars); + return std::error_code(); + } + + std::error_code rename(const std::string &from, const std::string &to) + { + // Convert to utf-16. + std::wstring wide_from = boost::nowide::widen(from); + std::wstring wide_to = boost::nowide::widen(to); + + ScopedFileHandle from_handle; + // Retry this a few times to defeat badly behaved file system scanners. + for (unsigned retry = 0; retry != 200; ++ retry) { + if (retry != 0) + ::Sleep(10); + from_handle = ::CreateFileW((LPWSTR)wide_from.data(), GENERIC_READ | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (from_handle) + break; + } + if (! from_handle) + return map_windows_error(GetLastError()); + + // We normally expect this loop to succeed after a few iterations. If it + // requires more than 200 tries, it's more likely that the failures are due to + // a true error, so stop trying. + for (unsigned retry = 0; retry != 200; ++ retry) { + auto errcode = rename_internal(from_handle, wide_to, true); + + if (errcode == std::error_code(ERROR_CALL_NOT_IMPLEMENTED, std::system_category())) { + // Wine doesn't support SetFileInformationByHandle in rename_internal. + // Fall back to MoveFileEx. + if (std::error_code errcode2 = real_path_from_handle(from_handle, wide_from)) + return errcode2; + if (::MoveFileExW((LPCWSTR)wide_from.data(), (LPCWSTR)wide_to.data(), MOVEFILE_REPLACE_EXISTING)) + return std::error_code(); + return map_windows_error(GetLastError()); + } + + if (! errcode || errcode != std::errc::permission_denied) + return errcode; + + // The destination file probably exists and is currently open in another + // process, either because the file was opened without FILE_SHARE_DELETE or + // it is mapped into memory (e.g. using MemoryBuffer). Rename it in order to + // move it out of the way of the source file. Use FILE_FLAG_DELETE_ON_CLOSE + // to arrange for the destination file to be deleted when the other process + // closes it. + ScopedFileHandle to_handle(::CreateFileW((LPCWSTR)wide_to.data(), GENERIC_READ | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL)); + if (! to_handle) { + auto errcode = map_windows_error(GetLastError()); + // Another process might have raced with us and moved the existing file + // out of the way before we had a chance to open it. If that happens, try + // to rename the source file again. + if (errcode == std::errc::no_such_file_or_directory) + continue; + return errcode; + } + + BY_HANDLE_FILE_INFORMATION FI; + if (! ::GetFileInformationByHandle(to_handle, &FI)) + return map_windows_error(GetLastError()); + + // Try to find a unique new name for the destination file. + for (unsigned unique_id = 0; unique_id != 200; ++ unique_id) { + std::wstring tmp_filename = wide_to + L".tmp" + std::to_wstring(unique_id); + std::error_code errcode = rename_internal(to_handle, tmp_filename, false); + if (errcode) { + if (errcode == std::make_error_code(std::errc::file_exists) || errcode == std::make_error_code(std::errc::permission_denied)) { + // Again, another process might have raced with us and moved the file + // before we could move it. Check whether this is the case, as it + // might have caused the permission denied error. If that was the + // case, we don't need to move it ourselves. + ScopedFileHandle to_handle2(::CreateFileW((LPCWSTR)wide_to.data(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + if (! to_handle2) { + auto errcode = map_windows_error(GetLastError()); + if (errcode == std::errc::no_such_file_or_directory) + break; + return errcode; + } + BY_HANDLE_FILE_INFORMATION FI2; + if (! ::GetFileInformationByHandle(to_handle2, &FI2)) + return map_windows_error(GetLastError()); + if (FI.nFileIndexHigh != FI2.nFileIndexHigh || FI.nFileIndexLow != FI2.nFileIndexLow || FI.dwVolumeSerialNumber != FI2.dwVolumeSerialNumber) + break; + continue; + } + return errcode; + } + break; + } + + // Okay, the old destination file has probably been moved out of the way at + // this point, so try to rename the source file again. Still, another + // process might have raced with us to create and open the destination + // file, so we need to keep doing this until we succeed. + } + + // The most likely root cause. + return std::make_error_code(std::errc::permission_denied); + } +} // namespace WindowsSupport +#endif /* _WIN32 */ // borrowed from LVVM lib/Support/Windows/Path.inc -int rename_file(const std::string &from, const std::string &to) +std::error_code rename_file(const std::string &from, const std::string &to) { - int ec = 0; - #ifdef _WIN32 - - // Convert to utf-16. - std::wstring wide_from = boost::nowide::widen(from); - std::wstring wide_to = boost::nowide::widen(to); - - // Retry while we see recoverable errors. - // System scanners (eg. indexer) might open the source file when it is written - // and closed. - bool TryReplace = true; - - // This loop may take more than 2000 x 1ms to finish. - for (int i = 0; i < 2000; ++ i) { - if (i > 0) - // Sleep 1ms - ::Sleep(1); - if (TryReplace) { - // Try ReplaceFile first, as it is able to associate a new data stream - // with the destination even if the destination file is currently open. - if (::ReplaceFileW(wide_to.data(), wide_from.data(), NULL, 0, NULL, NULL)) - return 0; - DWORD ReplaceError = ::GetLastError(); - ec = -1; // ReplaceError - // If ReplaceFileW returned ERROR_UNABLE_TO_MOVE_REPLACEMENT or - // ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, retry but only use MoveFileExW(). - if (ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT || - ReplaceError == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) { - TryReplace = false; - continue; - } - // If ReplaceFileW returned ERROR_UNABLE_TO_REMOVE_REPLACED, retry - // using ReplaceFileW(). - if (ReplaceError == ERROR_UNABLE_TO_REMOVE_REPLACED) - continue; - // We get ERROR_FILE_NOT_FOUND if the destination file is missing. - // MoveFileEx can handle this case. - if (ReplaceError != ERROR_ACCESS_DENIED && ReplaceError != ERROR_FILE_NOT_FOUND && ReplaceError != ERROR_SHARING_VIOLATION) - break; - } - if (::MoveFileExW(wide_from.c_str(), wide_to.c_str(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING)) - return 0; - DWORD MoveError = ::GetLastError(); - ec = -1; // MoveError - if (MoveError != ERROR_ACCESS_DENIED && MoveError != ERROR_SHARING_VIOLATION) - break; - } - + return WindowsSupport::rename(from, to); #else - boost::nowide::remove(to.c_str()); - ec = boost::nowide::rename(from.c_str(), to.c_str()); - + return std::make_error_code(static_cast(boost::nowide::rename(from.c_str(), to.c_str()))); #endif - - return ec; } int copy_file(const std::string &from, const std::string &to) diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index 7ca8c3e5eb..96213447cf 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -98,9 +98,10 @@ void AppConfig::load() pt::read_ini(ifs, tree); } catch (pt::ptree_error& ex) { // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - throw std::runtime_error(wxString::Format(_(L("Error parsing config file, it is probably corrupted. " - "Try to manualy delete the file. Your user profiles will not be affected.\n\n%s\n\n%s")), - AppConfig::config_path(), ex.what()).ToStdString()); + throw std::runtime_error( + _utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. " + "Try to manualy delete the file to recover from the error. Your user profiles will not be affected.")) + + "\n\n" + AppConfig::config_path() + "\n\n" + ex.what()); } // 2) Parse the property_tree, extract the sections and key / value pairs. From 88dcb7f366324225e715a67fa231fff0bda584a4 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 20 Aug 2019 16:38:03 +0200 Subject: [PATCH 591/627] Checking for OpenGL driver version in the GUI slicer and giving some reasonable advice to the user in case OpenGL < 2.0 was detected. --- src/PrusaSlicer.cpp | 3 +-- src/slic3r/GUI/GUI_App.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 92ff65a843..81422f4a88 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -60,7 +60,6 @@ PrinterTechnology get_printer_technology(const DynamicConfig &config) int CLI::run(int argc, char **argv) { -#ifdef _WIN32 // Switch boost::filesystem to utf8. try { boost::nowide::nowide_filesystem(); @@ -74,12 +73,12 @@ int CLI::run(int argc, char **argv) SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what(); #ifdef SLIC3R_GUI if (m_actions.empty()) + // Empty actions means Slicer is executed in the GUI mode. Show a GUI message. MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR); #endif boost::nowide::cerr << text.c_str() << std::endl; return 1; } -#endif if (! this->setup(argc, argv)) return 1; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9b4522356a..dff9fc1a97 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -293,6 +293,20 @@ bool GUI_App::on_init_inner() config_wizard_startup(app_conf_exists); preset_updater->slic3r_update_notify(); preset_updater->sync(preset_bundle); + const GLCanvas3DManager::GLInfo &glinfo = GLCanvas3DManager::get_gl_info(); + if (! glinfo.is_version_greater_or_equal_to(2, 0)) { + // Complain about the OpenGL version. + wxString message = wxString::Format( + _(L("PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n" + "while OpenGL version %s, render %s, vendor %s was detected.")), wxString(glinfo.get_version()), wxString(glinfo.get_renderer()), wxString(glinfo.get_vendor())); + message += "\n"; + message += _(L("You may need to update your graphics card driver.")); +#ifdef _WIN32 + message += "\n"; + message += _(L("As a workaround, you may run PrusaSlicer with a software rendered 3D graphics by running prusa-slicer.exe with the --sw_renderer parameter.")); +#endif + wxMessageBox(message, wxString("PrusaSlicer - ") + _(L("Unsupported OpenGL version")), wxOK | wxICON_ERROR); + } }); } }); From b58713c06ff154b8d421343496a64d29ffb9b1db Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 20 Aug 2019 17:24:48 +0200 Subject: [PATCH 592/627] SLA exposure bounds to printer params. --- src/libslic3r/PrintConfig.cpp | 8 +-- src/libslic3r/PrintConfig.hpp | 16 +++--- src/libslic3r/SLAPrint.cpp | 20 ++++---- src/slic3r/GUI/Preset.cpp | 6 ++- src/slic3r/GUI/Tab.cpp | 94 +++++++++-------------------------- 5 files changed, 50 insertions(+), 94 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 037b248007..8f56c1b83d 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2412,7 +2412,7 @@ void PrintConfigDef::init_sla_params() def->mode = comExpert; def->set_default_value(new ConfigOptionInt(10)); - def = this->add("exposure_time_min", coFloat); + def = this->add("min_exposure_time", coFloat); def->label = L("Minimum exposure time"); def->tooltip = L("Minimum exposure time"); def->sidetext = L("s"); @@ -2420,7 +2420,7 @@ void PrintConfigDef::init_sla_params() def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0)); - def = this->add("exposure_time_max", coFloat); + def = this->add("max_exposure_time", coFloat); def->label = L("Maximum exposure time"); def->tooltip = L("Maximum exposure time"); def->sidetext = L("s"); @@ -2435,7 +2435,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->set_default_value(new ConfigOptionFloat(10)); - def = this->add("initial_exposure_time_min", coFloat); + def = this->add("min_initial_exposure_time", coFloat); def->label = L("Minimum initial exposure time"); def->tooltip = L("Minimum initial exposure time"); def->sidetext = L("s"); @@ -2443,7 +2443,7 @@ void PrintConfigDef::init_sla_params() def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0)); - def = this->add("initial_exposure_time_max", coFloat); + def = this->add("max_initial_exposure_time", coFloat); def->label = L("Maximum initial exposure time"); def->tooltip = L("Maximum initial exposure time"); def->sidetext = L("s"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index ca2a210f26..35025fcd10 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1098,22 +1098,14 @@ class SLAMaterialConfig : public StaticPrintConfig STATIC_PRINT_CONFIG_CACHE(SLAMaterialConfig) public: ConfigOptionFloat initial_layer_height; - ConfigOptionFloat exposure_time_min; - ConfigOptionFloat exposure_time_max; ConfigOptionFloat exposure_time; - ConfigOptionFloat initial_exposure_time_min; - ConfigOptionFloat initial_exposure_time_max; ConfigOptionFloat initial_exposure_time; ConfigOptionFloats material_correction; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { OPT_PTR(initial_layer_height); - OPT_PTR(exposure_time_min); - OPT_PTR(exposure_time_max); OPT_PTR(exposure_time); - OPT_PTR(initial_exposure_time_min); - OPT_PTR(initial_exposure_time_max); OPT_PTR(initial_exposure_time); OPT_PTR(material_correction); } @@ -1139,6 +1131,10 @@ public: ConfigOptionFloat fast_tilt_time; ConfigOptionFloat slow_tilt_time; ConfigOptionFloat area_fill; + ConfigOptionFloat min_exposure_time; + ConfigOptionFloat max_exposure_time; + ConfigOptionFloat min_initial_exposure_time; + ConfigOptionFloat max_initial_exposure_time; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { @@ -1158,6 +1154,10 @@ protected: OPT_PTR(fast_tilt_time); OPT_PTR(slow_tilt_time); OPT_PTR(area_fill); + OPT_PTR(min_exposure_time); + OPT_PTR(max_exposure_time); + OPT_PTR(min_initial_exposure_time); + OPT_PTR(max_initial_exposure_time); } }; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 8e99d6ddc5..21aec83842 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -692,19 +692,19 @@ std::string SLAPrint::validate() const } } - double expt_max = m_material_config.exposure_time_max.getFloat(); - double expt_min = m_material_config.exposure_time_min.getFloat(); + double expt_max = m_printer_config.max_exposure_time.getFloat(); + double expt_min = m_printer_config.min_exposure_time.getFloat(); double expt_cur = m_material_config.exposure_time.getFloat(); if (expt_cur < expt_min || expt_cur > expt_max) - return L("Exposition time is out of predefined bounds."); + return L("Exposition time is out of printer profile bounds."); - double iexpt_max = m_material_config.initial_exposure_time_max.getFloat(); - double iexpt_min = m_material_config.initial_exposure_time_min.getFloat(); + double iexpt_max = m_printer_config.max_initial_exposure_time.getFloat(); + double iexpt_min = m_printer_config.min_initial_exposure_time.getFloat(); double iexpt_cur = m_material_config.initial_exposure_time.getFloat(); if (iexpt_cur < iexpt_min || iexpt_cur > iexpt_max) - return L("Initial exposition time is out of predefined bounds."); + return L("Initial exposition time is out of printer profile bounds."); return ""; } @@ -1600,11 +1600,11 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector steps_rasterize = { - "exposure_time_min", - "exposure_time_max", + "min_exposure_time", + "max_exposure_time", "exposure_time", - "initial_exposure_time_min", - "initial_exposure_time_max", + "min_initial_exposure_time", + "max_initial_exposure_time", "initial_exposure_time", "display_width", "display_height", diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 82124761a5..64793630ca 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -500,8 +500,8 @@ const std::vector& Preset::sla_material_options() if (s_opts.empty()) { s_opts = { "initial_layer_height", - "exposure_time_min", "exposure_time_max", "exposure_time", - "initial_exposure_time_min", "initial_exposure_time_max", "initial_exposure_time", + "exposure_time", + "initial_exposure_time", "material_correction", "material_notes", "default_sla_material_profile", @@ -527,6 +527,8 @@ const std::vector& Preset::sla_printer_options() "relative_correction", "absolute_correction", "gamma_correction", + "min_exposure_time", "max_exposure_time", + "min_initial_exposure_time", "max_initial_exposure_time", "print_host", "printhost_apikey", "printhost_cafile", "printer_notes", "inherits" diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 0301653371..3688542224 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -838,7 +838,7 @@ static wxString support_combo_value_for_config(const DynamicPrintConfig &config, static wxString pad_combo_value_for_config(const DynamicPrintConfig &config) { - return config.opt_bool("pad_enable") ? (config.opt_bool("pad_zero_elevation") ? _("Around object") : _("Below object")) : _("None"); + return config.opt_bool("pad_enable") ? (config.opt_bool("pad_zero_elevation") ? _("Around object") : _("Below object")) : _("None"); } void Tab::on_value_change(const std::string& opt_key, const boost::any& value) @@ -860,8 +860,8 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) (opt_key == "supports_enable" || opt_key == "support_buildplate_only")) og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); - if (! is_fff && (opt_key == "pad_enable" || opt_key == "pad_zero_elevation")) - og_freq_chng_params->set_value("pad", pad_combo_value_for_config(*m_config)); + if (! is_fff && (opt_key == "pad_enable" || opt_key == "pad_zero_elevation")) + og_freq_chng_params->set_value("pad", pad_combo_value_for_config(*m_config)); if (opt_key == "brim_width") { @@ -998,7 +998,7 @@ void Tab::update_frequently_changed_parameters() og_freq_chng_params->set_value("support", support_combo_value_for_config(*m_config, is_fff)); if (! is_fff) - og_freq_chng_params->set_value("pad", pad_combo_value_for_config(*m_config)); + og_freq_chng_params->set_value("pad", pad_combo_value_for_config(*m_config)); const std::string updated_value_key = is_fff ? "fill_density" : "pad_enable"; @@ -1772,13 +1772,13 @@ void TabFilament::reload_config() void TabFilament::update_volumetric_flow_preset_hints() { - wxString text; - try { - text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); - } catch (std::exception &ex) { - text = _(L("Volumetric flow hints not available\n\n")) + from_u8(ex.what()); - } - m_volumetric_speed_description_line->SetText(text); + wxString text; + try { + text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); + } catch (std::exception &ex) { + text = _(L("Volumetric flow hints not available\n\n")) + from_u8(ex.what()); + } + m_volumetric_speed_description_line->SetText(text); } void TabFilament::update() @@ -1788,9 +1788,9 @@ void TabFilament::update() m_update_cnt++; - wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); - m_cooling_description_line->SetText(text); - this->update_volumetric_flow_preset_hints(); + wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); + m_cooling_description_line->SetText(text); + this->update_volumetric_flow_preset_hints(); Layout(); bool cooling = m_config->opt_bool("cooling", 0); @@ -1812,8 +1812,8 @@ void TabFilament::update() void TabFilament::OnActivate() { - this->update_volumetric_flow_preset_hints(); - Tab::OnActivate(); + this->update_volumetric_flow_preset_hints(); + Tab::OnActivate(); } wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticText) @@ -2290,6 +2290,12 @@ void TabPrinter::build_sla() optgroup->append_single_option_line("absolute_correction"); optgroup->append_single_option_line("gamma_correction"); + optgroup = page->new_optgroup(_(L("Exposure"))); + optgroup->append_single_option_line("min_exposure_time"); + optgroup->append_single_option_line("max_exposure_time"); + optgroup->append_single_option_line("min_initial_exposure_time"); + optgroup->append_single_option_line("max_initial_exposure_time"); + optgroup = page->new_optgroup(_(L("Print Host upload"))); build_printhost(optgroup.get()); @@ -2560,7 +2566,7 @@ void TabPrinter::build_unregular_pages() optgroup = page->new_optgroup(_(L("Preview"))); auto reset_to_filament_color = [this, extruder_idx](wxWindow* parent) { - add_scaled_button(parent, &m_reset_to_filament_color, "undo", + add_scaled_button(parent, &m_reset_to_filament_color, "undo", _(L("Reset to Filament Color")), wxBU_LEFT | wxBU_EXACTFIT); ScalableButton* btn = m_reset_to_filament_color; btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -2571,7 +2577,7 @@ void TabPrinter::build_unregular_pages() { std::vector colors = static_cast(m_config->option("extruder_colour"))->values; colors[extruder_idx] = ""; - + DynamicPrintConfig new_conf = *m_config; new_conf.set_key_value("extruder_colour", new ConfigOptionStrings(colors)); load_config(new_conf); @@ -3614,11 +3620,7 @@ void TabSLAMaterial::build() optgroup->append_single_option_line("initial_layer_height"); optgroup = page->new_optgroup(_(L("Exposure"))); - optgroup->append_single_option_line("exposure_time_min"); - optgroup->append_single_option_line("exposure_time_max"); optgroup->append_single_option_line("exposure_time"); - optgroup->append_single_option_line("initial_exposure_time_min"); - optgroup->append_single_option_line("initial_exposure_time_max"); optgroup->append_single_option_line("initial_exposure_time"); optgroup = page->new_optgroup(_(L("Corrections"))); @@ -3683,59 +3685,11 @@ void TabSLAMaterial::reload_config() Tab::reload_config(); } - -namespace { - -enum e_cmp {EQUAL = 1, SMALLER = 2, GREATER = 4, SMALLER_EQ = 3, GREATER_EQ = 5}; - -void bound_check(Tab &tb, e_cmp cmp, const char *id, const char *boundid) -{ - double bound = tb.m_config->opt_float(boundid); - double value = tb.m_config->opt_float(id); - - auto boundlabel = tb.m_config->def()->get(boundid)->label; - auto valuelabel = tb.m_config->def()->get(id)->label; - - double ddiff = value - bound; - int diff = ddiff < 0 ? SMALLER : (std::abs(ddiff) < EPSILON ? EQUAL : GREATER); - - if ((cmp | diff) != cmp) { - wxString fmt; - - switch (cmp) { - case EQUAL: fmt = _(L("%s should be equal to %s")); break; - case SMALLER: fmt = _(L("%s should be smaller than %s")); break; - case GREATER: fmt = _(L("%s should be greater than %s")); break; - case SMALLER_EQ: fmt = _(L("%s should be smaller or equal to %s")); break; - case GREATER_EQ: fmt = _(L("%s should be greater or equal to %s")); break; - } - - wxString msg_text = wxString::Format(fmt, valuelabel, boundlabel); - - wxMessageDialog dialog(tb.parent(), msg_text, - _(L("Value outside bounds")), - wxICON_WARNING | wxOK); - - DynamicPrintConfig new_conf = *tb.m_config; - if (dialog.ShowModal() == wxID_OK) - new_conf.set_key_value(id, new ConfigOptionFloat(bound)); - - tb.load_config(new_conf); - } -}; - -} - void TabSLAMaterial::update() { if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) return; - bound_check(*this, e_cmp::GREATER_EQ, "exposure_time", "exposure_time_min"); - bound_check(*this, e_cmp::SMALLER_EQ, "exposure_time", "exposure_time_max"); - bound_check(*this, e_cmp::GREATER_EQ, "initial_exposure_time", "initial_exposure_time_min"); - bound_check(*this, e_cmp::SMALLER_EQ, "initial_exposure_time", "initial_exposure_time_max"); - // #ys_FIXME. Just a template for this function // m_update_cnt++; // ! something to update From 775a54846fc01ec9e977bad7cfa810ac1789d5b0 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 20 Aug 2019 17:46:19 +0200 Subject: [PATCH 593/627] Fixed compilation of Win32 message boxes on unix systems. --- src/PrusaSlicer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 81422f4a88..a75635ae60 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -71,7 +71,7 @@ int CLI::run(int argc, char **argv) "You may need to reconfigure the missing locales, likely by running the \"locale-gen\"" and \"dpkg-reconfigure locales\" commands.\n" #endif SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what(); - #ifdef SLIC3R_GUI + #if defined(_WIN32) && defined(SLIC3R_GUI) if (m_actions.empty()) // Empty actions means Slicer is executed in the GUI mode. Show a GUI message. MessageBoxA(NULL, text.c_str(), caption.c_str(), MB_OK | MB_ICONERROR); From 18d3792d3759f7a72a60530bafcb22154b25857b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 20 Aug 2019 18:45:12 +0200 Subject: [PATCH 594/627] Fixed a slack bug with wrong filament preset selection after importing of config --- src/slic3r/GUI/PresetBundle.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 7c9f7af4e9..3aee71c4c7 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -763,8 +763,11 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool } } // Load the configs into this->filaments and make them active. - this->filament_presets.clear(); - for (size_t i = 0; i < configs.size(); ++ i) { + this->filament_presets = std::vector(configs.size()); + // To avoid incorrect selection of the first filament preset (means a value of Preset->m_idx_selected) + // in a case when next added preset take a place of previosly selected preset, + // we should add presets from last to first + for (int i = (int)configs.size()-1; i >= 0; i--) { DynamicPrintConfig &cfg = configs[i]; // Split the "compatible_printers_condition" and "inherits" from the cummulative vectors to separate filament presets. cfg.opt_string("compatible_printers_condition", true) = compatible_printers_condition_values[i + 1]; @@ -789,7 +792,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool new_name, std::move(cfg), i == 0); loaded->save(); } - this->filament_presets.emplace_back(loaded->name); + this->filament_presets[i] = loaded->name; } } // 4) Load the project config values (the per extruder wipe matrix etc). From 6a2265150196a791e493ee16456ecc5ecdd85f6c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 20 Aug 2019 20:24:37 +0200 Subject: [PATCH 595/627] Fixed a typo preventing compilation on Linux --- src/PrusaSlicer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index a75635ae60..3c0f27f4d8 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -68,7 +68,7 @@ int CLI::run(int argc, char **argv) std::string text = std::string("An error occured while setting up locale.\n") + ( #if !defined(_WIN32) && !defined(__APPLE__) // likely some linux system - "You may need to reconfigure the missing locales, likely by running the \"locale-gen\"" and \"dpkg-reconfigure locales\" commands.\n" + "You may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n" #endif SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what(); #if defined(_WIN32) && defined(SLIC3R_GUI) From e403118d7d518c52b0bf8129a1c650d1f7c04e23 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 21 Aug 2019 08:50:38 +0200 Subject: [PATCH 596/627] Fixed a typo in an error message. --- src/PrusaSlicer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index a75635ae60..ccbe0ab542 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -68,7 +68,7 @@ int CLI::run(int argc, char **argv) std::string text = std::string("An error occured while setting up locale.\n") + ( #if !defined(_WIN32) && !defined(__APPLE__) // likely some linux system - "You may need to reconfigure the missing locales, likely by running the \"locale-gen\"" and \"dpkg-reconfigure locales\" commands.\n" + "You may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n" #endif SLIC3R_APP_NAME " will now terminate.\n\n") + ex.what(); #if defined(_WIN32) && defined(SLIC3R_GUI) From 7c0c5705df1b6d7739c0d20b713e99741d91cc88 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 21 Aug 2019 09:28:32 +0200 Subject: [PATCH 597/627] Fix of Excessive external_perimeter_extrusion_width error #2784 Increased the perimeter_extrusion_width check limit to 3x nozzle diameter. --- src/libslic3r/Print.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e875db1dca..c5b619891d 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1258,7 +1258,7 @@ std::string Print::validate() const } else if (extrusion_width_min <= layer_height) { err_msg = (boost::format(L("%1%=%2% mm is too low to be printable at a layer height %3% mm")) % opt_key % extrusion_width_min % layer_height).str(); return false; - } else if (extrusion_width_max >= max_nozzle_diameter * 2.) { + } else if (extrusion_width_max >= max_nozzle_diameter * 3.) { err_msg = (boost::format(L("Excessive %1%=%2% mm to be printable with a nozzle diameter %3% mm")) % opt_key % extrusion_width_max % max_nozzle_diameter).str(); return false; } From ded2019765643d6a0acfe1c296eb1f2d2e860eb9 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 21 Aug 2019 13:08:26 +0200 Subject: [PATCH 598/627] Fix of "spiral vase printable for a single region object only" check. --- src/libslic3r/Print.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index c5b619891d..fdac4d8981 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1144,7 +1144,12 @@ std::string Print::validate() const // #4043 if (total_copies_count > 1 && ! m_config.complete_objects.value) return L("The Spiral Vase option can only be used when printing a single object."); - if (m_regions.size() > 1) + assert(m_objects.size() == 1); + size_t num_regions = 0; + for (const std::vector> &volumes_per_region : m_objects.front()->region_volumes) + if (! volumes_per_region.empty()) + ++ num_regions; + if (num_regions > 1) return L("The Spiral Vase option can only be used when printing single material objects."); } From 40d313961e474fce91e0e94858254f149e877d91 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 21 Aug 2019 13:49:37 +0200 Subject: [PATCH 599/627] Fixing issues in Print / PrintObject / PrintRegion reporting a list of printing extruders. --- src/libslic3r/Print.cpp | 25 +++++++++++++++++++------ src/libslic3r/PrintRegion.cpp | 19 +++++++++++++++---- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index fdac4d8981..6437302c30 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -262,8 +262,14 @@ std::vector Print::object_extruders() const { std::vector extruders; extruders.reserve(m_regions.size() * 3); - for (const PrintRegion *region : m_regions) - region->collect_object_printing_extruders(extruders); + std::vector region_used(m_regions.size(), false); + for (const PrintObject *object : m_objects) + for (const std::vector> &volumes_per_region : object->region_volumes) + if (! volumes_per_region.empty()) + region_used[&volumes_per_region - &object->region_volumes.front()] = true; + for (size_t idx_region = 0; idx_region < m_regions.size(); ++ idx_region) + if (region_used[idx_region]) + m_regions[idx_region]->collect_object_printing_extruders(extruders); sort_remove_duplicates(extruders); return extruders; } @@ -273,17 +279,24 @@ std::vector Print::support_material_extruders() const { std::vector extruders; bool support_uses_current_extruder = false; + auto num_extruders = (unsigned int)m_config.nozzle_diameter.size(); for (PrintObject *object : m_objects) { if (object->has_support_material()) { + assert(object->config().support_material_extruder >= 0); if (object->config().support_material_extruder == 0) support_uses_current_extruder = true; - else - extruders.push_back(object->config().support_material_extruder - 1); + else { + unsigned int i = (unsigned int)object->config().support_material_extruder - 1; + extruders.emplace_back((i >= num_extruders) ? 0 : i); + } + assert(object->config().support_material_interface_extruder >= 0); if (object->config().support_material_interface_extruder == 0) support_uses_current_extruder = true; - else - extruders.push_back(object->config().support_material_interface_extruder - 1); + else { + unsigned int i = (unsigned int)object->config().support_material_interface_extruder - 1; + extruders.emplace_back((i >= num_extruders) ? 0 : i); + } } } diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index 73b40487bc..fc2bdfa7d1 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -46,7 +46,7 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir } double nozzle_diameter = m_print->config().nozzle_diameter.get_at(extruder-1); - return Flow::new_from_config_width(role, config_width, nozzle_diameter, layer_height, bridge ? (float)m_config.bridge_flow_ratio : 0.0); + return Flow::new_from_config_width(role, config_width, (float)nozzle_diameter, (float)layer_height, bridge ? (float)m_config.bridge_flow_ratio : 0.0f); } coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const @@ -64,16 +64,27 @@ coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const void PrintRegion::collect_object_printing_extruders(const PrintConfig &print_config, const PrintRegionConfig ®ion_config, std::vector &object_extruders) { // These checks reflect the same logic used in the GUI for enabling/disabling extruder selection fields. + auto num_extruders = (int)print_config.nozzle_diameter.size(); + auto emplace_extruder = [num_extruders, &object_extruders](int extruder_id) { + int i = std::max(0, extruder_id - 1); + object_extruders.emplace_back((i >= num_extruders) ? 0 : i); + }; if (region_config.perimeters.value > 0 || print_config.brim_width.value > 0) - object_extruders.emplace_back(region_config.perimeter_extruder - 1); + emplace_extruder(region_config.perimeter_extruder); if (region_config.fill_density.value > 0) - object_extruders.emplace_back(region_config.infill_extruder - 1); + emplace_extruder(region_config.infill_extruder); if (region_config.top_solid_layers.value > 0 || region_config.bottom_solid_layers.value > 0) - object_extruders.emplace_back(region_config.solid_infill_extruder - 1); + emplace_extruder(region_config.solid_infill_extruder); } void PrintRegion::collect_object_printing_extruders(std::vector &object_extruders) const { + auto num_extruders = (int)print()->config().nozzle_diameter.size(); + // PrintRegion, if used by some PrintObject, shall have all the extruders set to an existing printer extruder. + // If not, then there must be something wrong with the Print::apply() function. + assert(this->config().perimeter_extruder <= num_extruders); + assert(this->config().infill_extruder <= num_extruders); + assert(this->config().solid_infill_extruder <= num_extruders); collect_object_printing_extruders(print()->config(), this->config(), object_extruders); } From 668a8cd2eaaf4d3c06cc7053f071afddb70d3583 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 21 Aug 2019 14:05:32 +0200 Subject: [PATCH 600/627] Fix of an update of support extruders when changing number of printer extruders. --- src/libslic3r/Print.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 6437302c30..71529cff15 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -590,6 +590,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Apply variables to placeholder parser. The placeholder parser is used by G-code export, // which should be stopped if print_diff is not empty. + size_t num_extruders = m_config.nozzle_diameter.size(); + bool num_extruders_changed = false; if (! full_config_diff.empty() || ! placeholder_parser_overrides.empty()) { update_apply_status(this->invalidate_step(psGCodeExport)); m_placeholder_parser.apply_config(std::move(placeholder_parser_overrides)); @@ -605,6 +607,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Handle changes to regions config defaults m_default_region_config.apply_only(new_full_config, region_diff, true); m_full_print_config = std::move(new_full_config); + if (num_extruders != m_config.nozzle_diameter.size()) { + num_extruders = m_config.nozzle_diameter.size(); + num_extruders_changed = true; + } } class LayerRanges @@ -781,7 +787,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ print_object_status.emplace(PrintObjectStatus(print_object)); // 3) Synchronize ModelObjects & PrintObjects. - size_t num_extruders = m_config.nozzle_diameter.size(); for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { ModelObject &model_object = *m_model.objects[idx_model_object]; auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); @@ -828,7 +833,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ bool object_config_changed = model_object.config != model_object_new.config; if (object_config_changed) static_cast(model_object.config) = static_cast(model_object_new.config); - if (! object_diff.empty() || object_config_changed) { + if (! object_diff.empty() || object_config_changed || num_extruders_changed) { PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders); auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); for (auto it = range.first; it != range.second; ++ it) { From 67a677577339d88663fb35f8f5720d0799914761 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 21 Aug 2019 14:07:56 +0200 Subject: [PATCH 601/627] Making arrange a little bit smarter: fix for issue #2787 --- src/libslic3r/Arrange.cpp | 42 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 99645f29d8..a70c208bc8 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -148,8 +148,9 @@ protected: double m_norm; // A coefficient to scale distances MultiPolygon m_merged_pile; // The already merged pile (vector of items) Box m_pilebb; // The bounding box of the merged pile. - ItemGroup m_remaining; // Remaining items (m_items at the beginning) - ItemGroup m_items; // The items to be packed + ItemGroup m_remaining; // Remaining items + ItemGroup m_items; // allready packed items + size_t m_item_count = 0; // Number of all items to be packed template ArithmeticOnly norm(T val) { @@ -167,7 +168,6 @@ protected: const double bin_area = m_bin_area; const SpatIndex& spatindex = m_rtree; const SpatIndex& smalls_spatindex = m_smallsrtree; - const ItemGroup& remaining = m_remaining; // We will treat big items (compared to the print bed) differently auto isBig = [bin_area](double a) { @@ -209,8 +209,8 @@ protected: } compute_case; bool bigitems = isBig(item.area()) || spatindex.empty(); - if(bigitems && !remaining.empty()) compute_case = BIG_ITEM; - else if (bigitems && remaining.empty()) compute_case = LAST_BIG_ITEM; + if(bigitems && !m_remaining.empty()) compute_case = BIG_ITEM; + else if (bigitems && m_remaining.empty()) compute_case = LAST_BIG_ITEM; else compute_case = SMALL_ITEM; switch (compute_case) { @@ -235,7 +235,7 @@ protected: // The smalles distance from the arranged pile center: double dist = norm(*(std::min_element(dists.begin(), dists.end()))); double bindist = norm(pl::distance(ibb.center(), bincenter)); - dist = 0.8 * dist + 0.2*bindist; + dist = 0.8 * dist + 0.2 * bindist; // Prepare a variable for the alignment score. // This will indicate: how well is the candidate item @@ -267,29 +267,24 @@ protected: if(ascore < alignment_score) alignment_score = ascore; } } - + density = std::sqrt(norm(fullbb.width()) * norm(fullbb.height())); - + double R = double(m_remaining.size()) / m_item_count; + // The final mix of the score is the balance between the // distance from the full pile center, the pack density and // the alignment with the neighbors if (result.empty()) - score = 0.5 * dist + 0.5 * density; + score = 0.50 * dist + 0.50 * density; else - score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; + score = R * 0.60 * dist + + (1.0 - R) * 0.20 * density + + 0.20 * alignment_score; break; } case LAST_BIG_ITEM: { - auto mp = m_merged_pile; - mp.emplace_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - - placers::EdgeCache ec(chull); - - double circ = norm(ec.circumference()); - double bcirc = 2.0 * norm(fullbb.width() + fullbb.height()); - score = 0.5 * circ + 0.5 * bcirc; + score = norm(pl::distance(ibb.center(), m_pilebb.center())); break; } case SMALL_ITEM: { @@ -355,9 +350,11 @@ public: m_pck.configure(m_pconf); } - template inline void operator()(Args&&...args) { - m_rtree.clear(); /*m_preload_idx.clear();*/ - m_pck.execute(std::forward(args)...); + template inline void operator()(It from, It to) { + m_rtree.clear(); + m_item_count += size_t(to - from); + m_pck.execute(from, to); + m_item_count = 0; } inline void preload(std::vector& fixeditems) { @@ -376,6 +373,7 @@ public: } m_pck.configure(m_pconf); + m_item_count += fixeditems.size(); } }; From 85ed363951543a5acdc09ff8e92098fcea31978f Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 21 Aug 2019 14:09:20 +0200 Subject: [PATCH 602/627] GCodeAnalyzer uses annotated gcode in place of processing m600 lines to detect color print changes --- src/libslic3r/GCode.cpp | 4 ++++ src/libslic3r/GCode/Analyzer.cpp | 36 ++++++++++++++++++-------------- src/libslic3r/GCode/Analyzer.hpp | 7 ++++--- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index cc7262cd81..907bd9a0e8 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1522,7 +1522,11 @@ void GCode::process_layer( // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count if (colorprint_change && print./*extruders()*/config().nozzle_diameter.size()==1) + { + // add tag for analyzer + gcode += "; " + GCodeAnalyzer::Color_Change_Tag + "\n"; gcode += "M600\n"; + } // Extrude skirt at the print_z of the raft layers and normal object layers diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index a08f83cf54..b28aa558e5 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -25,6 +25,7 @@ const std::string GCodeAnalyzer::Extrusion_Role_Tag = "_ANALYZER_EXTR_ROLE:"; const std::string GCodeAnalyzer::Mm3_Per_Mm_Tag = "_ANALYZER_MM3_PER_MM:"; const std::string GCodeAnalyzer::Width_Tag = "_ANALYZER_WIDTH:"; const std::string GCodeAnalyzer::Height_Tag = "_ANALYZER_HEIGHT:"; +const std::string GCodeAnalyzer::Color_Change_Tag = "_ANALYZER_COLOR_CHANGE"; const double GCodeAnalyzer::Default_mm3_per_mm = 0.0; const float GCodeAnalyzer::Default_Width = 0.0f; @@ -240,11 +241,6 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi { switch (::atoi(&cmd[1])) { - case 600: // Set color change - { - _processM600(line); - break; - } case 82: // Set extruder to absolute mode { _processM82(line); @@ -517,12 +513,6 @@ void GCodeAnalyzer::_reset_cached_position() } } -void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line) -{ - m_state.cur_cp_color_id++; - _set_cp_color_id(m_state.cur_cp_color_id); -} - void GCodeAnalyzer::_processT(const std::string& cmd) { if (cmd.length() > 1) @@ -579,6 +569,14 @@ bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line) return true; } + // color change tag + pos = comment.find(Color_Change_Tag); + if (pos != comment.npos) + { + _process_color_change_tag(); + return true; + } + return false; } @@ -608,6 +606,12 @@ void GCodeAnalyzer::_process_height_tag(const std::string& comment, size_t pos) _set_height((float)::strtod(comment.substr(pos + Height_Tag.length()).c_str(), nullptr)); } +void GCodeAnalyzer::_process_color_change_tag() +{ + m_state.cur_cp_color_id++; + _set_cp_color_id(m_state.cur_cp_color_id); +} + void GCodeAnalyzer::_set_units(GCodeAnalyzer::EUnits units) { m_state.units = units; @@ -945,12 +949,12 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data, s polyline = Polyline3(); // add both vertices of the move - polyline.append(Vec3crd(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z()))); - polyline.append(Vec3crd(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z()))); + polyline.append(Vec3crd((int)scale_(move.start_position.x()), (int)scale_(move.start_position.y()), (int)scale_(move.start_position.z()))); + polyline.append(Vec3crd((int)scale_(move.end_position.x()), (int)scale_(move.end_position.y()), (int)scale_(move.end_position.z()))); } else // append end vertex of the move to current polyline - polyline.append(Vec3crd(scale_(move.end_position.x()), scale_(move.end_position.y()), scale_(move.end_position.z()))); + polyline.append(Vec3crd((int)scale_(move.end_position.x()), (int)scale_(move.end_position.y()), (int)scale_(move.end_position.z()))); // update current values position = move.end_position; @@ -994,7 +998,7 @@ void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_da cancel_callback(); // store position - Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); + Vec3crd position((int)scale_(move.start_position.x()), (int)scale_(move.start_position.y()), (int)scale_(move.start_position.z())); preview_data.retraction.positions.emplace_back(position, move.data.width, move.data.height); } @@ -1021,7 +1025,7 @@ void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_ cancel_callback(); // store position - Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); + Vec3crd position((int)scale_(move.start_position.x()), (int)scale_(move.start_position.y()), (int)scale_(move.start_position.z())); preview_data.unretraction.positions.emplace_back(position, move.data.width, move.data.height); } diff --git a/src/libslic3r/GCode/Analyzer.hpp b/src/libslic3r/GCode/Analyzer.hpp index c9c81d429f..47f4e92aa5 100644 --- a/src/libslic3r/GCode/Analyzer.hpp +++ b/src/libslic3r/GCode/Analyzer.hpp @@ -19,6 +19,7 @@ public: static const std::string Mm3_Per_Mm_Tag; static const std::string Width_Tag; static const std::string Height_Tag; + static const std::string Color_Change_Tag; static const double Default_mm3_per_mm; static const float Default_Width; @@ -177,9 +178,6 @@ private: // Repetier: Go to stored position void _processM402(const GCodeReader::GCodeLine& line); - // Set color change - void _processM600(const GCodeReader::GCodeLine& line); - // Processes T line (Select Tool) void _processT(const std::string& command); void _processT(const GCodeReader::GCodeLine& line); @@ -200,6 +198,9 @@ private: // Processes height tag void _process_height_tag(const std::string& comment, size_t pos); + // Processes color change tag + void _process_color_change_tag(); + void _set_units(EUnits units); EUnits _get_units() const; From 9cabb03f725d1873a772cf6c59bcb387b71cc4b3 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 21 Aug 2019 14:43:14 +0200 Subject: [PATCH 603/627] GCodeTimeEstimator uses annotated gcode in place of processing m600 lines to detect color print changes. WARNING -> After this commit the exported gcode will contain the extra lines used by the time estimator. They will be removed by a future commit when a new post-process method will be implemented. --- src/libslic3r/GCode.cpp | 2 + src/libslic3r/GCodeTimeEstimator.cpp | 59 ++++++++++++++++++---------- src/libslic3r/GCodeTimeEstimator.hpp | 12 ++++-- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 907bd9a0e8..008cad940b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1525,6 +1525,8 @@ void GCode::process_layer( { // add tag for analyzer gcode += "; " + GCodeAnalyzer::Color_Change_Tag + "\n"; + // add tag for time estimator + gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n"; gcode += "M600\n"; } diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index a03d3c7c85..5aa0a9ceb0 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -168,10 +168,12 @@ namespace Slic3r { } #endif // ENABLE_MOVE_STATS - const std::string GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag = "; NORMAL_FIRST_M73_OUTPUT_PLACEHOLDER"; - const std::string GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag = "; SILENT_FIRST_M73_OUTPUT_PLACEHOLDER"; - const std::string GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag = "; NORMAL_LAST_M73_OUTPUT_PLACEHOLDER"; - const std::string GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag = "; SILENT_LAST_M73_OUTPUT_PLACEHOLDER"; + const std::string GCodeTimeEstimator::Normal_First_M73_Output_Placeholder_Tag = "; _TE_NORMAL_FIRST_M73_OUTPUT_PLACEHOLDER"; + const std::string GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag = "; _TE_SILENT_FIRST_M73_OUTPUT_PLACEHOLDER"; + const std::string GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag = "; _TE_NORMAL_LAST_M73_OUTPUT_PLACEHOLDER"; + const std::string GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag = "; _TE_SILENT_LAST_M73_OUTPUT_PLACEHOLDER"; + + const std::string GCodeTimeEstimator::Color_Change_Tag = "_TE_COLOR_CHANGE"; GCodeTimeEstimator::GCodeTimeEstimator(EMode mode) : m_mode(mode) @@ -814,6 +816,11 @@ namespace Slic3r { void GCodeTimeEstimator::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line) { PROFILE_FUNC(); + + // processes 'special' comments contained in line + if (_process_tags(line)) + return; + std::string cmd = line.cmd(); if (cmd.length() > 1) { @@ -921,11 +928,6 @@ namespace Slic3r { _processM566(line); break; } - case 600: // Set color change - { - _processM600(line); - break; - } case 702: // MK3 MMU2: Process the final filament unload. { _processM702(line); @@ -1396,18 +1398,6 @@ namespace Slic3r { set_axis_max_jerk(E, line.e() * MMMIN_TO_MMSEC); } - void GCodeTimeEstimator::_processM600(const GCodeReader::GCodeLine& line) - { - PROFILE_FUNC(); - m_needs_color_times = true; - _calculate_time(); - if (m_color_time_cache != 0.0f) - { - m_color_times.push_back(m_color_time_cache); - m_color_time_cache = 0.0f; - } - } - void GCodeTimeEstimator::_processM702(const GCodeReader::GCodeLine& line) { PROFILE_FUNC(); @@ -1439,6 +1429,33 @@ namespace Slic3r { } } + bool GCodeTimeEstimator::_process_tags(const GCodeReader::GCodeLine& line) + { + std::string comment = line.comment(); + + // color change tag + size_t pos = comment.find(Color_Change_Tag); + if (pos != comment.npos) + { + _process_color_change_tag(); + return true; + } + + return false; + } + + void GCodeTimeEstimator::_process_color_change_tag() + { + PROFILE_FUNC(); + m_needs_color_times = true; + _calculate_time(); + if (m_color_time_cache != 0.0f) + { + m_color_times.push_back(m_color_time_cache); + m_color_time_cache = 0.0f; + } + } + void GCodeTimeEstimator::_simulate_st_synchronize() { PROFILE_FUNC(); diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index 8d794af1e4..dd5d14b57a 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -22,6 +22,8 @@ namespace Slic3r { static const std::string Normal_Last_M73_Output_Placeholder_Tag; static const std::string Silent_Last_M73_Output_Placeholder_Tag; + static const std::string Color_Change_Tag; + enum EMode : unsigned char { Normal, @@ -425,15 +427,19 @@ namespace Slic3r { // Set allowable instantaneous speed change void _processM566(const GCodeReader::GCodeLine& line); - // Set color change - void _processM600(const GCodeReader::GCodeLine& line); - // Unload the current filament into the MK3 MMU2 unit at the end of print. void _processM702(const GCodeReader::GCodeLine& line); // Processes T line (Select Tool) void _processT(const GCodeReader::GCodeLine& line); + // Processes the tags + // Returns true if any tag has been processed + bool _process_tags(const GCodeReader::GCodeLine& line); + + // Processes color change tag + void _process_color_change_tag(); + // Simulates firmware st_synchronize() call void _simulate_st_synchronize(); From dc3a0a0ab34af1ac0e0139cbde2a75f820e17f1f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 21 Aug 2019 14:52:22 +0200 Subject: [PATCH 604/627] Refactoring of EdgeGrid to accept an segment to segment visitor. WIP: PolygonTrimmer to trim skirt & brim with polygons stored in EdgeGrid. --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/EdgeGrid.cpp | 173 ++++--------------------------- src/libslic3r/EdgeGrid.hpp | 139 +++++++++++++++++++++++++ src/libslic3r/Geometry.hpp | 23 ++++ src/libslic3r/PolygonTrimmer.cpp | 56 ++++++++++ src/libslic3r/PolygonTrimmer.hpp | 31 ++++++ 6 files changed, 272 insertions(+), 152 deletions(-) create mode 100644 src/libslic3r/PolygonTrimmer.cpp create mode 100644 src/libslic3r/PolygonTrimmer.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 1a9a153b94..85e11eded3 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -127,6 +127,8 @@ add_library(libslic3r STATIC Point.hpp Polygon.cpp Polygon.hpp + PolygonTrimmer.cpp + PolygonTrimmer.hpp Polyline.cpp Polyline.hpp PolylineCollection.cpp diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index 9d02ef09b7..f40d499de1 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -11,6 +11,7 @@ #include "libslic3r.h" #include "ClipperUtils.hpp" #include "EdgeGrid.hpp" +#include "Geometry.hpp" #include "SVG.hpp" #if 0 @@ -275,134 +276,24 @@ void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) // 6) Finally fill in m_cell_data by rasterizing the lines once again. for (size_t i = 0; i < m_cells.size(); ++i) m_cells[i].end = m_cells[i].begin; - for (size_t i = 0; i < m_contours.size(); ++i) { - const Slic3r::Points &pts = *m_contours[i]; - for (size_t j = 0; j < pts.size(); ++j) { - // End points of the line segment. - Slic3r::Point p1(pts[j]); - Slic3r::Point p2 = pts[(j + 1 == pts.size()) ? 0 : j + 1]; - p1(0) -= m_bbox.min(0); - p1(1) -= m_bbox.min(1); - p2(0) -= m_bbox.min(0); - p2(1) -= m_bbox.min(1); - // Get the cells of the end points. - coord_t ix = p1(0) / m_resolution; - coord_t iy = p1(1) / m_resolution; - coord_t ixb = p2(0) / m_resolution; - coord_t iyb = p2(1) / m_resolution; - assert(ix >= 0 && size_t(ix) < m_cols); - assert(iy >= 0 && size_t(iy) < m_rows); - assert(ixb >= 0 && size_t(ixb) < m_cols); - assert(iyb >= 0 && size_t(iyb) < m_rows); - // Account for the end points. - m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); - if (ix == ixb && iy == iyb) - // Both ends fall into the same cell. - continue; - // Raster the centeral part of the line. - coord_t dx = std::abs(p2(0) - p1(0)); - coord_t dy = std::abs(p2(1) - p1(1)); - if (p1(0) < p2(0)) { - int64_t ex = int64_t((ix + 1)*m_resolution - p1(0)) * int64_t(dy); - if (p1(1) < p2(1)) { - // x positive, y positive - int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); - do { - assert(ix <= ixb && iy <= iyb); - if (ex < ey) { - ey -= ex; - ex = int64_t(dy) * m_resolution; - ix += 1; - } - else if (ex == ey) { - ex = int64_t(dy) * m_resolution; - ey = int64_t(dx) * m_resolution; - ix += 1; - iy += 1; - } - else { - assert(ex > ey); - ex -= ey; - ey = int64_t(dx) * m_resolution; - iy += 1; - } - m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); - } while (ix != ixb || iy != iyb); - } - else { - // x positive, y non positive - int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); - do { - assert(ix <= ixb && iy >= iyb); - if (ex <= ey) { - ey -= ex; - ex = int64_t(dy) * m_resolution; - ix += 1; - } - else { - ex -= ey; - ey = int64_t(dx) * m_resolution; - iy -= 1; - } - m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); - } while (ix != ixb || iy != iyb); - } - } - else { - int64_t ex = int64_t(p1(0) - ix*m_resolution) * int64_t(dy); - if (p1(1) < p2(1)) { - // x non positive, y positive - int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); - do { - assert(ix >= ixb && iy <= iyb); - if (ex < ey) { - ey -= ex; - ex = int64_t(dy) * m_resolution; - ix -= 1; - } - else { - assert(ex >= ey); - ex -= ey; - ey = int64_t(dx) * m_resolution; - iy += 1; - } - m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); - } while (ix != ixb || iy != iyb); - } - else { - // x non positive, y non positive - int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); - do { - assert(ix >= ixb && iy >= iyb); - if (ex < ey) { - ey -= ex; - ex = int64_t(dy) * m_resolution; - ix -= 1; - } - else if (ex == ey) { - // The lower edge of a grid cell belongs to the cell. - // Handle the case where the ray may cross the lower left corner of a cell in a general case, - // or a left or lower edge in a degenerate case (horizontal or vertical line). - if (dx > 0) { - ex = int64_t(dy) * m_resolution; - ix -= 1; - } - if (dy > 0) { - ey = int64_t(dx) * m_resolution; - iy -= 1; - } - } - else { - assert(ex > ey); - ex -= ey; - ey = int64_t(dx) * m_resolution; - iy -= 1; - } - m_cell_data[m_cells[iy*m_cols + ix].end++] = std::pair(i, j); - } while (ix != ixb || iy != iyb); - } - } - } + + struct Visitor { + Visitor(std::vector> &cell_data, std::vector &cells, size_t cols) : + cell_data(cell_data), cells(cells), cols(cols), i(0), j(0) {} + + void operator()(coord_t iy, coord_t ix) { cell_data[cells[iy*cols + ix].end++] = std::pair(i, j); } + + std::vector> &cell_data; + std::vector &cells; + size_t cols; + size_t i; + size_t j; + } visitor(m_cell_data, m_cells, m_cols); + + for (; visitor.i < m_contours.size(); ++ visitor.i) { + const Slic3r::Points &pts = *m_contours[visitor.i]; + for (; visitor.j < pts.size(); ++ visitor.j) + this->visit_cells_intersecting_line(pts[visitor.j], pts[(visitor.j + 1 == pts.size()) ? 0 : visitor.j + 1], visitor); } } @@ -1360,28 +1251,6 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) co return out; } -inline int segments_could_intersect( - const Slic3r::Point &ip1, const Slic3r::Point &ip2, - const Slic3r::Point &jp1, const Slic3r::Point &jp2) -{ - Vec2i64 iv = (ip2 - ip1).cast(); - Vec2i64 vij1 = (jp1 - ip1).cast(); - Vec2i64 vij2 = (jp2 - ip1).cast(); - int64_t tij1 = cross2(iv, vij1); - int64_t tij2 = cross2(iv, vij2); - int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum - int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0); - return sij1 * sij2; -} - -inline bool segments_intersect( - const Slic3r::Point &ip1, const Slic3r::Point &ip2, - const Slic3r::Point &jp1, const Slic3r::Point &jp2) -{ - return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 && - segments_could_intersect(jp1, jp2, ip1, ip2) <= 0; -} - std::vector> EdgeGrid::Grid::intersecting_edges() const { std::vector> out; @@ -1405,7 +1274,7 @@ std::vector> if (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) // Segments of the same contour share a common vertex. continue; - if (segments_intersect(ip1, ip2, jp1, jp2)) { + if (Geometry::segments_intersect(ip1, ip2, jp1, jp2)) { // The two segments intersect. Add them to the output. int jfirst = (&jpts < &ipts) || (&jpts == &ipts && jpt < ipt); out.emplace_back(jfirst ? @@ -1440,7 +1309,7 @@ bool EdgeGrid::Grid::has_intersecting_edges() const const Slic3r::Point &jp1 = jpts[jpt]; const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1]; if (! (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) && - segments_intersect(ip1, ip2, jp1, jp2)) + Geometry::segments_intersect(ip1, ip2, jp1, jp2)) return true; } } diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index 7faafdb3e1..cad20e07bb 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -65,6 +65,145 @@ public: std::vector> intersecting_edges() const; bool has_intersecting_edges() const; + template void visit_cells_intersecting_line(Slic3r::Point p1, Slic3r::Point p2, FUNCTION func) const + { + // End points of the line segment. + p1(0) -= m_bbox.min(0); + p1(1) -= m_bbox.min(1); + p2(0) -= m_bbox.min(0); + p2(1) -= m_bbox.min(1); + // Get the cells of the end points. + coord_t ix = p1(0) / m_resolution; + coord_t iy = p1(1) / m_resolution; + coord_t ixb = p2(0) / m_resolution; + coord_t iyb = p2(1) / m_resolution; + assert(ix >= 0 && size_t(ix) < m_cols); + assert(iy >= 0 && size_t(iy) < m_rows); + assert(ixb >= 0 && size_t(ixb) < m_cols); + assert(iyb >= 0 && size_t(iyb) < m_rows); + // Account for the end points. + func(iy, ix); + if (ix == ixb && iy == iyb) + // Both ends fall into the same cell. + return; + // Raster the centeral part of the line. + coord_t dx = std::abs(p2(0) - p1(0)); + coord_t dy = std::abs(p2(1) - p1(1)); + if (p1(0) < p2(0)) { + int64_t ex = int64_t((ix + 1)*m_resolution - p1(0)) * int64_t(dy); + if (p1(1) < p2(1)) { + // x positive, y positive + int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); + do { + assert(ix <= ixb && iy <= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix += 1; + } + else if (ex == ey) { + ex = int64_t(dy) * m_resolution; + ey = int64_t(dx) * m_resolution; + ix += 1; + iy += 1; + } + else { + assert(ex > ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy += 1; + } + func(iy, ix); + } while (ix != ixb || iy != iyb); + } + else { + // x positive, y non positive + int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); + do { + assert(ix <= ixb && iy >= iyb); + if (ex <= ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix += 1; + } + else { + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy -= 1; + } + func(iy, ix); + } while (ix != ixb || iy != iyb); + } + } + else { + int64_t ex = int64_t(p1(0) - ix*m_resolution) * int64_t(dy); + if (p1(1) < p2(1)) { + // x non positive, y positive + int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx); + do { + assert(ix >= ixb && iy <= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix -= 1; + } + else { + assert(ex >= ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy += 1; + } + func(iy, ix); + } while (ix != ixb || iy != iyb); + } + else { + // x non positive, y non positive + int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx); + do { + assert(ix >= ixb && iy >= iyb); + if (ex < ey) { + ey -= ex; + ex = int64_t(dy) * m_resolution; + ix -= 1; + } + else if (ex == ey) { + // The lower edge of a grid cell belongs to the cell. + // Handle the case where the ray may cross the lower left corner of a cell in a general case, + // or a left or lower edge in a degenerate case (horizontal or vertical line). + if (dx > 0) { + ex = int64_t(dy) * m_resolution; + ix -= 1; + } + if (dy > 0) { + ey = int64_t(dx) * m_resolution; + iy -= 1; + } + } + else { + assert(ex > ey); + ex -= ey; + ey = int64_t(dx) * m_resolution; + iy -= 1; + } + func(iy, ix); + } while (ix != ixb || iy != iyb); + } + } + } + + std::pair>::const_iterator, std::vector>::const_iterator> cell_data_range(coord_t row, coord_t col) const + { + const EdgeGrid::Grid::Cell &cell = m_cells[row * m_cols + col]; + return std::make_pair(m_cell_data.begin() + cell.begin, m_cell_data.begin() + cell.end); + } + + std::pair segment(const std::pair &contour_and_segment_idx) const + { + const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; + size_t ipt = contour_and_segment_idx.second; + return std::pair(ipts[ipt], ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]); + } + protected: struct Cell { Cell() : begin(0), end(0) {} diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index dca1872d82..eec2673227 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -111,6 +111,29 @@ inline bool segment_segment_intersection(const Vec2d &p1, const Vec2d &v1, const return true; } + +inline int segments_could_intersect( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + Vec2i64 iv = (ip2 - ip1).cast(); + Vec2i64 vij1 = (jp1 - ip1).cast(); + Vec2i64 vij2 = (jp2 - ip1).cast(); + int64_t tij1 = cross2(iv, vij1); + int64_t tij2 = cross2(iv, vij2); + int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum + int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0); + return sij1 * sij2; +} + +inline bool segments_intersect( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 && + segments_could_intersect(jp1, jp2, ip1, ip2) <= 0; +} + Pointf3s convex_hull(Pointf3s points); Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); diff --git a/src/libslic3r/PolygonTrimmer.cpp b/src/libslic3r/PolygonTrimmer.cpp new file mode 100644 index 0000000000..3e3c9b4982 --- /dev/null +++ b/src/libslic3r/PolygonTrimmer.cpp @@ -0,0 +1,56 @@ +#include "PolygonTrimmer.hpp" +#include "EdgeGrid.hpp" +#include "Geometry.hpp" + +namespace Slic3r { + +TrimmedLoop trim_loop(const Polygon &loop, const EdgeGrid::Grid &grid) +{ + assert(! loop.empty()); + assert(loop.size() >= 2); + + TrimmedLoop out; + + if (loop.size() >= 2) { + size_t cnt = loop.points.size(); + + struct Visitor { + Visitor(const EdgeGrid::Grid &grid, const Slic3r::Point *pt_prev, const Slic3r::Point *pt_this) : grid(grid), pt_prev(pt_prev), pt_this(pt_this) {} + + void operator()(coord_t iy, coord_t ix) { + // Called with a row and colum of the grid cell, which is intersected by a line. + auto cell_data_range = grid.cell_data_range(iy, ix); + for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) { + // End points of the line segment and their vector. + auto segment = grid.segment(*it_contour_and_segment); + if (Geometry::segments_intersect(segment.first, segment.second, *pt_prev, *pt_this)) { + // The two segments intersect. Add them to the output. + } + } + } + + const EdgeGrid::Grid &grid; + const Slic3r::Point *pt_this; + const Slic3r::Point *pt_prev; + } visitor(grid, &loop.points.back(), nullptr); + + for (const Point &pt_this : loop.points) { + visitor.pt_this = &pt_this; + grid.visit_cells_intersecting_line(*visitor.pt_prev, pt_this, visitor); + visitor.pt_prev = &pt_this; + } + } + + return out; +} + +std::vector trim_loops(const Polygons &loops, const EdgeGrid::Grid &grid) +{ + std::vector out; + out.reserve(loops.size()); + for (const Polygon &loop : loops) + out.emplace_back(trim_loop(loop, grid)); + return out; +} + +} diff --git a/src/libslic3r/PolygonTrimmer.hpp b/src/libslic3r/PolygonTrimmer.hpp new file mode 100644 index 0000000000..b2a780be91 --- /dev/null +++ b/src/libslic3r/PolygonTrimmer.hpp @@ -0,0 +1,31 @@ +#ifndef slic3r_PolygonTrimmer_hpp_ +#define slic3r_PolygonTrimmer_hpp_ + +#include "libslic3r.h" +#include +#include +#include "Line.hpp" +#include "MultiPoint.hpp" +#include "Polyline.hpp" + +namespace Slic3r { + +namespace EdgeGrid { + class Grid; +} + +struct TrimmedLoop +{ + std::vector points; + // Number of points per segment. Empty if the loop is + std::vector segments; + + bool is_trimmed() const { return ! segments.empty(); } +}; + +TrimmedLoop trim_loop(const Polygon &loop, const EdgeGrid::Grid &grid); +std::vector trim_loops(const Polygons &loops, const EdgeGrid::Grid &grid); + +} // namespace Slic3r + +#endif /* slic3r_PolygonTrimmer_hpp_ */ From 1b00932a01a03dd9894f63abdff1c4cbb9697bf9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 21 Aug 2019 15:39:20 +0200 Subject: [PATCH 605/627] Small refactor for BedShapeHint --- src/libslic3r/Arrange.cpp | 54 ++++++++++++++++++++++++++++++++++----- src/libslic3r/Arrange.hpp | 44 ++++--------------------------- 2 files changed, 53 insertions(+), 45 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index a70c208bc8..34e07302a0 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -437,6 +437,18 @@ inline Circle to_lnCircle(const CircleBed& circ) { } // Get the type of bed geometry from a simple vector of points. +void BedShapeHint::reset(BedShapes type) +{ + if (m_type != type) { + if (m_type == bsIrregular) + m_bed.polygon.Slic3r::Polyline::~Polyline(); + else if (type == bsIrregular) + ::new (&m_bed.polygon) Polyline(); + } + + m_type = type; +} + BedShapeHint::BedShapeHint(const Polyline &bed) { auto x = [](const Point& p) { return p(X); }; auto y = [](const Point& p) { return p(Y); }; @@ -511,14 +523,44 @@ BedShapeHint::BedShapeHint(const Polyline &bed) { } } +BedShapeHint &BedShapeHint::operator=(BedShapeHint &&cpy) +{ + reset(cpy.m_type); + + switch(m_type) { + case bsBox: m_bed.box = std::move(cpy.m_bed.box); break; + case bsCircle: m_bed.circ = std::move(cpy.m_bed.circ); break; + case bsIrregular: m_bed.polygon = std::move(cpy.m_bed.polygon); break; + case bsInfinite: m_bed.infbed = std::move(cpy.m_bed.infbed); break; + case bsUnknown: break; + } + + return *this; +} + +BedShapeHint &BedShapeHint::operator=(const BedShapeHint &cpy) +{ + reset(cpy.m_type); + + switch(m_type) { + case bsBox: m_bed.box = cpy.m_bed.box; break; + case bsCircle: m_bed.circ = cpy.m_bed.circ; break; + case bsIrregular: m_bed.polygon = cpy.m_bed.polygon; break; + case bsInfinite: m_bed.infbed = cpy.m_bed.infbed; break; + case bsUnknown: break; + } + + return *this; +} + template // Arrange for arbitrary bin type void _arrange( - std::vector & shapes, - std::vector & excludes, - const BinT & bin, - coord_t minobjd, - std::function prind, - std::function stopfn) + std::vector & shapes, + std::vector & excludes, + const BinT & bin, + coord_t minobjd, + std::function prind, + std::function stopfn) { // Integer ceiling the min distance from the bed perimeters coord_t md = minobjd - 2 * scaled(0.1 + EPSILON); diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index a0e4c043f0..1cfe1c907d 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -51,6 +51,9 @@ class BedShapeHint { BedShape_u() {} } m_bed; + // Reset the type, allocate m_bed properly + void reset(BedShapes type); + public: BedShapeHint(){} @@ -81,45 +84,8 @@ public: BedShapeHint(const BedShapeHint &cpy) { *this = cpy; } BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); } - BedShapeHint &operator=(const BedShapeHint &cpy) - { - if (m_type != cpy.m_type) { - if (m_type == bsIrregular) - m_bed.polygon.Slic3r::Polyline::~Polyline(); - else if (cpy.m_type == bsIrregular) - ::new (&m_bed.polygon) Polyline(); - } - m_type = cpy.m_type; - switch(m_type) { - case bsBox: m_bed.box = cpy.m_bed.box; break; - case bsCircle: m_bed.circ = cpy.m_bed.circ; break; - case bsIrregular: m_bed.polygon = cpy.m_bed.polygon; break; - case bsInfinite: m_bed.infbed = cpy.m_bed.infbed; break; - case bsUnknown: break; - } - - return *this; - } - - BedShapeHint& operator=(BedShapeHint &&cpy) - { - if (m_type != cpy.m_type) { - if (m_type == bsIrregular) - m_bed.polygon.Slic3r::Polyline::~Polyline(); - else if (cpy.m_type == bsIrregular) - ::new (&m_bed.polygon) Polyline(); - } - m_type = cpy.m_type; - switch(m_type) { - case bsBox: m_bed.box = std::move(cpy.m_bed.box); break; - case bsCircle: m_bed.circ = std::move(cpy.m_bed.circ); break; - case bsIrregular: m_bed.polygon = std::move(cpy.m_bed.polygon); break; - case bsInfinite: m_bed.infbed = std::move(cpy.m_bed.infbed); break; - case bsUnknown: break; - } - - return *this; - } + BedShapeHint &operator=(const BedShapeHint &cpy); + BedShapeHint& operator=(BedShapeHint &&cpy); BedShapes get_type() const { return m_type; } From 42b8176e9b545bd0aa274e8c4b7cd4ebce274b26 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 21 Aug 2019 16:06:56 +0200 Subject: [PATCH 606/627] Temporary human readable form for print color change tag for time estimate --- src/libslic3r/GCodeTimeEstimator.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index 5aa0a9ceb0..00b409344e 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -173,7 +173,9 @@ namespace Slic3r { const std::string GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag = "; _TE_NORMAL_LAST_M73_OUTPUT_PLACEHOLDER"; const std::string GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag = "; _TE_SILENT_LAST_M73_OUTPUT_PLACEHOLDER"; - const std::string GCodeTimeEstimator::Color_Change_Tag = "_TE_COLOR_CHANGE"; + // temporary human readable form to use until not removed from gcode by the new post-process method + const std::string GCodeTimeEstimator::Color_Change_Tag = "PRINT_COLOR_CHANGE"; +// const std::string GCodeTimeEstimator::Color_Change_Tag = "_TE_COLOR_CHANGE"; GCodeTimeEstimator::GCodeTimeEstimator(EMode mode) : m_mode(mode) From e928c0ac0bc6888b70b7ba9729ba850628da91e1 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 22 Aug 2019 11:35:25 +0200 Subject: [PATCH 607/627] Added a missing include --- src/libslic3r/PolygonTrimmer.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/PolygonTrimmer.hpp b/src/libslic3r/PolygonTrimmer.hpp index b2a780be91..eddffbc7fa 100644 --- a/src/libslic3r/PolygonTrimmer.hpp +++ b/src/libslic3r/PolygonTrimmer.hpp @@ -7,6 +7,7 @@ #include "Line.hpp" #include "MultiPoint.hpp" #include "Polyline.hpp" +#include "Polygon.hpp" namespace Slic3r { From 0333553389b55c50e40e2df13f5830b8348c1564 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 22 Aug 2019 11:47:58 +0200 Subject: [PATCH 608/627] Windows specific fix of extruder selection and object editing at the object list: When clicking on a column of an already selected line, simulate ENTER key to enter the editing mode. This works most of the time, but not 100% reliably. solves the arthritis issue #1606 --- src/slic3r/GUI/GUI_ObjectList.cpp | 49 +++++++++++++++++++++++++++++-- src/slic3r/GUI/GUI_ObjectList.hpp | 8 +++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index e0bdd26df9..d4f5d79a92 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -17,6 +17,10 @@ #include #include "slic3r/Utils/FixModelByWin10.hpp" +#ifdef __WXMSW__ +#include "wx/uiaction.h" +#endif /* __WXMSW__ */ + namespace Slic3r { namespace GUI @@ -119,6 +123,10 @@ ObjectList::ObjectList(wxWindow* parent) : * instead of real last clicked item. * So, let check last selected item in such strange way */ +#ifdef __WXMSW__ + // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. + int new_selected_column = -1; +#endif __WXMSW__ if (wxGetKeyState(WXK_SHIFT)) { wxDataViewItemArray sels; @@ -128,8 +136,25 @@ ObjectList::ObjectList(wxWindow* parent) : else m_last_selected_item = event.GetItem(); } - else - m_last_selected_item = event.GetItem(); + else { + wxDataViewItem new_selected_item = event.GetItem(); +#ifdef __WXMSW__ + // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. + wxDataViewItem item; + wxDataViewColumn *col; + this->HitTest(get_mouse_position_in_control(), item, col); + new_selected_column = (col == nullptr) ? -1 : (int)col->GetModelColumn(); + if (new_selected_item == m_last_selected_item && m_last_selected_column != -1 && m_last_selected_column != new_selected_column) { + // Mouse clicked on another column of the active row. Simulate keyboard enter to enter the editing mode of the current column. + wxUIActionSimulator sim; + sim.Char(WXK_RETURN); + } +#endif __WXMSW__ + m_last_selected_item = new_selected_item; + } +#ifdef __WXMSW__ + m_last_selected_column = new_selected_column; +#endif __WXMSW__ selection_changed(); #ifndef __WXMSW__ @@ -184,7 +209,10 @@ ObjectList::ObjectList(wxWindow* parent) : Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, &ObjectList::OnDropPossible, this); Bind(wxEVT_DATAVIEW_ITEM_DROP, &ObjectList::OnDrop, this); - Bind(wxEVT_DATAVIEW_ITEM_EDITING_DONE, &ObjectList::OnEditingDone, this); +#ifdef __WXMSW__ + Bind(wxEVT_DATAVIEW_ITEM_EDITING_STARTED, &ObjectList::OnEditingStarted, this); +#endif /* __WXMSW__ */ + Bind(wxEVT_DATAVIEW_ITEM_EDITING_DONE, &ObjectList::OnEditingDone, this); Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &ObjectList::ItemValueChanged, this); @@ -3541,6 +3569,15 @@ void ObjectList::ItemValueChanged(wxDataViewEvent &event) update_extruder_in_config(event.GetItem()); } +#ifdef __WXMSW__ +// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. +// Here the last active column is forgotten, so when leaving the editing mode, the next mouse click will not enter the editing mode of the newly selected column. +void ObjectList::OnEditingStarted(wxDataViewEvent &event) +{ + m_last_selected_column = -1; +} +#endif __WXMSW__ + void ObjectList::OnEditingDone(wxDataViewEvent &event) { if (event.GetColumn() != colName) @@ -3553,6 +3590,12 @@ void ObjectList::OnEditingDone(wxDataViewEvent &event) show_error(this, _(L("The supplied name is not valid;")) + "\n" + _(L("the following characters are not allowed:")) + " <>:/\\|?*\""); }); + +#ifdef __WXMSW__ + // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. + // Here the last active column is forgotten, so when leaving the editing mode, the next mouse click will not enter the editing mode of the newly selected column. + m_last_selected_column = -1; +#endif __WXMSW__ } void ObjectList::show_multi_selection_menu() diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 62003e5571..0164d20c12 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -157,6 +157,10 @@ private: int m_selected_row = 0; wxDataViewItem m_last_selected_item {nullptr}; +#ifdef __WXMSW__ + // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. + int m_last_selected_column = -1; +#endif /* __MSW__ */ #if 0 SettingsBundle m_freq_settings_fff; @@ -366,6 +370,10 @@ private: bool can_drop(const wxDataViewItem& item) const ; void ItemValueChanged(wxDataViewEvent &event); +#ifdef __WXMSW__ + // Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected. + void OnEditingStarted(wxDataViewEvent &event); +#endif /* __WXMSW__ */ void OnEditingDone(wxDataViewEvent &event); void show_multi_selection_menu(); From 2c9521c6d246beab98dac248bbfa85046fe02c4d Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 22 Aug 2019 13:29:21 +0200 Subject: [PATCH 609/627] Model::convert_multipart_object() modified to keep original object position on the bed --- src/libslic3r/Model.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 606af246f9..4331c43798 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -528,8 +528,9 @@ void Model::convert_multipart_object(unsigned int max_extruders) copy_volume(object->add_volume(*v))->set_transformation(i->get_transformation() * trafo_volume); } } - // If there are more than one object, create a single instance + object->add_instance(); + object->instances[0]->set_offset(object->raw_mesh_bounding_box().center()); this->clear_objects(); this->objects.push_back(object); From 628d2a2614234e97c8676a99186e8efcdff3fd50 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 22 Aug 2019 15:42:23 +0200 Subject: [PATCH 610/627] Fixed "split to part" for Undo/Redo stack --- src/slic3r/GUI/GUI_ObjectList.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index d4f5d79a92..eba5ea8857 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1972,13 +1972,15 @@ void ObjectList::split() DynamicPrintConfig& config = printer_config(); const ConfigOption *nozzle_dmtrs_opt = config.option("nozzle_diameter", false); const auto nozzle_dmrs_cnt = (nozzle_dmtrs_opt == nullptr) ? size_t(1) : dynamic_cast(nozzle_dmtrs_opt)->values.size(); - if (volume->split(nozzle_dmrs_cnt) == 1) { + if (!volume->is_splittable()) { wxMessageBox(_(L("The selected object couldn't be split because it contains only one part."))); return; } take_snapshot(_(L("Split to Parts"))); + volume->split(nozzle_dmrs_cnt); + wxBusyCursor wait; auto model_object = (*m_objects)[obj_idx]; From 0de566e2a2741f70dfe12da8f33e21f04da57826 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 22 Aug 2019 20:02:25 +0200 Subject: [PATCH 611/627] Hopefully a workaround for crashes and hang ups at closing on Windows. Release the OpenGL contexts not by a destructor of a static class, but explicitely (predictively). --- src/PrusaSlicer.cpp | 6 +++++- src/slic3r/GUI/3DScene.cpp | 5 +++++ src/slic3r/GUI/3DScene.hpp | 1 + src/slic3r/GUI/GLCanvas3DManager.cpp | 15 ++++++++++----- src/slic3r/GUI/GLCanvas3DManager.hpp | 1 + 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index ccbe0ab542..6c6f9584f0 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -48,6 +48,7 @@ #ifdef SLIC3R_GUI #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" + #include "slic3r/GUI/3DScene.hpp" #endif /* SLIC3R_GUI */ using namespace Slic3r; @@ -505,7 +506,10 @@ int CLI::run(int argc, char **argv) if (! m_extra_config.empty()) gui->mainframe->load_config(m_extra_config); }); - return wxEntry(argc, argv); + int result = wxEntry(argc, argv); + //FIXME this is a workaround for the PrusaSlicer 2.1 release. + _3DScene::destroy(); + return result; #else /* SLIC3R_GUI */ // No GUI support. Just print out a help. this->print_help(false); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 8d1a154b4d..d764d6cef8 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -2007,6 +2007,11 @@ bool _3DScene::init(wxGLCanvas* canvas) return s_canvas_mgr.init(canvas); } +void _3DScene::destroy() +{ + s_canvas_mgr.destroy(); +} + GUI::GLCanvas3D* _3DScene::get_canvas(wxGLCanvas* canvas) { return s_canvas_mgr.get_canvas(canvas); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index e0603ebc0a..5acc85217f 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -647,6 +647,7 @@ public: static void remove_all_canvases(); static bool init(wxGLCanvas* canvas); + static void destroy(); static GUI::GLCanvas3D* get_canvas(wxGLCanvas* canvas); diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index c096c0e9fc..a764fec664 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -197,11 +197,7 @@ GLCanvas3DManager::GLCanvas3DManager() GLCanvas3DManager::~GLCanvas3DManager() { - if (m_context != nullptr) - { - delete m_context; - m_context = nullptr; - } + this->destroy(); } bool GLCanvas3DManager::add(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) @@ -282,6 +278,15 @@ bool GLCanvas3DManager::init(wxGLCanvas* canvas) return false; } +void GLCanvas3DManager::destroy() +{ + if (m_context != nullptr) + { + delete m_context; + m_context = nullptr; + } +} + GLCanvas3D* GLCanvas3DManager::get_canvas(wxGLCanvas* canvas) { CanvasesMap::const_iterator it = do_get_canvas(canvas); diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index c0e0df6224..760266a274 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -91,6 +91,7 @@ public: void init_gl(); bool init(wxGLCanvas* canvas); + void destroy(); GLCanvas3D* get_canvas(wxGLCanvas* canvas); From 24b9c926c676f9caecbf11dad3d174cfebd1918b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 22 Aug 2019 16:58:51 +0200 Subject: [PATCH 612/627] GLGizmoManager.cpp: Initial work on the possibility of a gizmo refusing to close --- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 61 ++++++++++++++++------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 38af634646..99a2f229f6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -1065,34 +1065,57 @@ void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos) float scaled_stride_y = scaled_icons_size + scaled_gap_y; float top_y = 0.5f * (cnv_h - height) + scaled_border; - for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + auto inside = [scaled_border, scaled_icons_size, &mouse_pos](float top_y) { + return (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); + }; + + bool could_activate = true; + for (std::pair &type_and_gizmo : m_gizmos) { - if ((it->second == nullptr) || !it->second->is_selectable()) + GLGizmoBase *gizmo = type_and_gizmo.second; + if ((gizmo == nullptr) || !gizmo->is_selectable()) continue; - bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); - if (it->second->is_activable() && inside) - { - if ((it->second->get_state() == GLGizmoBase::On)) - { - it->second->set_state(GLGizmoBase::Hover); - m_current = Undefined; - } - else if ((it->second->get_state() == GLGizmoBase::Hover)) - { - it->second->set_state(GLGizmoBase::On); - m_current = it->first; + if (! (gizmo->is_activable() && inside(top_y))) { + gizmo->set_state(GLGizmoBase::Off); + if (gizmo->get_state() != GLGizmoBase::Off) { + // Gizmo refused to leave it's active state. Don't try to select + could_activate = false; } } - else - it->second->set_state(GLGizmoBase::Off); top_y += scaled_stride_y; } - GizmosMap::iterator it = m_gizmos.find(m_current); - if ((it != m_gizmos.end()) && (it->second != nullptr) && (it->second->get_state() != GLGizmoBase::On)) - it->second->set_state(GLGizmoBase::On); + top_y = 0.5f * (cnv_h - height) + scaled_border; + for (std::pair &type_and_gizmo : m_gizmos) + { + GLGizmoBase *gizmo = type_and_gizmo.second; + if ((gizmo == nullptr) || !gizmo->is_selectable()) + continue; + + if (gizmo->is_activable() && inside(top_y)) + { + if ((gizmo->get_state() == GLGizmoBase::On)) + { + gizmo->set_state(GLGizmoBase::Hover); + m_current = Undefined; + } + else if ((gizmo->get_state() == GLGizmoBase::Hover) && could_activate) + { + gizmo->set_state(GLGizmoBase::On); + m_current = type_and_gizmo.first; + } + } + + top_y += scaled_stride_y; + } + + if (could_activate) { + GizmosMap::iterator it = m_gizmos.find(m_current); + if ((it != m_gizmos.end()) && (it->second != nullptr) && (it->second->get_state() != GLGizmoBase::On)) + it->second->set_state(GLGizmoBase::On); + } } std::string GLGizmosManager::update_hover_state(const Vec2d& mouse_pos) From c736b657d402ccbad9a602e54f6a4a71b544758c Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 23 Aug 2019 01:08:14 +0200 Subject: [PATCH 613/627] SLA gizmo now refuses to close with unsaved changes in editing mode --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 24 +++++++------ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 38 +++++++++++++------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 4976520fa5..32a43e78c8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -1127,19 +1127,23 @@ void GLGizmoSlaSupports::on_set_state() m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - wxGetApp().CallAfter([this]() { - // Following is called through CallAfter, because otherwise there was a problem - // on OSX with the wxMessageDialog being shown several times when clicked into. - if (m_model_object) { - if (m_editing_mode && unsaved_changes()) { - wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points?")) + "\n", - _(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); + bool will_ask = m_model_object && m_editing_mode && unsaved_changes(); + if (will_ask) { + wxGetApp().CallAfter([this]() { + // Following is called through CallAfter, because otherwise there was a problem + // on OSX with the wxMessageDialog being shown several times when clicked into. + wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually " + "edited support points?")) + "\n",_(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); if (dlg.ShowModal() == wxID_YES) editing_mode_apply_changes(); else editing_mode_discard_changes(); - } - } + }); + // refuse to be turned off so the gizmo is active when the CallAfter is executed + m_state = m_old_state; + } + else { + // we are actually shutting down m_parent.toggle_model_objects_visibility(true); disable_editing_mode(); // so it is not active next time the gizmo opens m_normal_cache.clear(); @@ -1149,7 +1153,7 @@ void GLGizmoSlaSupports::on_set_state() m_its = nullptr; m_tms.reset(); m_supports_tms.reset(); - }); + } } m_old_state = m_state; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 99a2f229f6..bd176b231b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -280,8 +280,8 @@ bool GLGizmosManager::handle_shortcut(int key) if (m_parent.get_selection().is_empty()) return false; - EType old_current = m_current; bool handled = false; + for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) @@ -294,25 +294,34 @@ bool GLGizmosManager::handle_shortcut(int key) if ((it->second->get_state() == GLGizmoBase::On)) { it->second->set_state(GLGizmoBase::Off); - m_current = Undefined; + if (it->second->get_state() == GLGizmoBase::Off) { + m_current = Undefined; + } handled = true; } else if ((it->second->get_state() == GLGizmoBase::Off)) { - it->second->set_state(GLGizmoBase::On); - m_current = it->first; + // Before turning anything on, turn everything else off to see if + // nobody refuses. Only then activate the gizmo. + bool can_open = true; + for (GizmosMap::iterator i = m_gizmos.begin(); i != m_gizmos.end(); ++i) { + if (i->first != it->first) { + if (m_current == i->first && i->second->get_state() != GLGizmoBase::Off ) { + i->second->set_state(GLGizmoBase::Off); + if (i->second->get_state() != GLGizmoBase::Off) + can_open = false; + } + } + } + if (can_open) { + it->second->set_state(GLGizmoBase::On); + m_current = it->first; + } handled = true; } } } - if (handled && (old_current != Undefined) && (old_current != m_current)) - { - GizmosMap::const_iterator it = m_gizmos.find(old_current); - if (it != m_gizmos.end()) - it->second->set_state(GLGizmoBase::Off); - } - return handled; } @@ -1098,8 +1107,11 @@ void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos) { if ((gizmo->get_state() == GLGizmoBase::On)) { - gizmo->set_state(GLGizmoBase::Hover); - m_current = Undefined; + gizmo->set_state(GLGizmoBase::Off); + if (gizmo->get_state() == GLGizmoBase::Off) { + gizmo->set_state(GLGizmoBase::Hover); + m_current = Undefined; + } } else if ((gizmo->get_state() == GLGizmoBase::Hover) && could_activate) { From b55a140ad41ab0b317c99d7b0996724c687dffbd Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 23 Aug 2019 01:36:56 +0200 Subject: [PATCH 614/627] SLA gizmo takes snapshots on opening and closing --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 7 ++++++- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 2 ++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 9 +++++++-- src/slic3r/GUI/Plater.cpp | 3 +++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 32a43e78c8..8236410bd9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -1100,6 +1100,9 @@ std::string GLGizmoSlaSupports::on_get_name() const void GLGizmoSlaSupports::on_set_state() { + if (m_state == Hover) + return; + // m_model_object pointer can be invalid (for instance because of undo/redo action), // we should recover it from the object id m_model_object = nullptr; @@ -1111,6 +1114,7 @@ void GLGizmoSlaSupports::on_set_state() } if (m_state == On && m_old_state != On) { // the gizmo was just turned on + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on"))); if (is_mesh_update_necessary()) update_mesh(); @@ -1144,8 +1148,9 @@ void GLGizmoSlaSupports::on_set_state() } else { // we are actually shutting down - m_parent.toggle_model_objects_visibility(true); disable_editing_mode(); // so it is not active next time the gizmo opens + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off"))); + m_parent.toggle_model_objects_visibility(true); m_normal_cache.clear(); m_clipping_plane_distance = 0.f; // Release triangle mesh slicer and the AABB spatial search structure. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index fa3facf4b5..2f6e9ccf32 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -101,6 +101,8 @@ private: void update_cache_entry_normal(unsigned int i) const; bool unsaved_changes() const; + EState m_no_hover_state = Off; + EState m_no_hover_old_state = Off; bool m_lock_unique_islands = false; bool m_editing_mode = false; // Is editing mode active? bool m_old_editing_state = false; // To keep track of whether the user toggled between the modes (needed for imgui refreshes). diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index bd176b231b..236a0a27cf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -1096,6 +1096,10 @@ void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos) top_y += scaled_stride_y; } + // We may change m_current soon. If we did it during following loop, gizmos that take undo/redo snapshots + // in their on_set_state function could snapshot a state with the new gizmo already active. + // Therefore, just remember what needs to be done and actually change m_current afterwards. + EType new_current = m_current; top_y = 0.5f * (cnv_h - height) + scaled_border; for (std::pair &type_and_gizmo : m_gizmos) { @@ -1110,18 +1114,19 @@ void GLGizmosManager::update_on_off_state(const Vec2d& mouse_pos) gizmo->set_state(GLGizmoBase::Off); if (gizmo->get_state() == GLGizmoBase::Off) { gizmo->set_state(GLGizmoBase::Hover); - m_current = Undefined; + new_current = Undefined; } } else if ((gizmo->get_state() == GLGizmoBase::Hover) && could_activate) { gizmo->set_state(GLGizmoBase::On); - m_current = type_and_gizmo.first; + new_current = type_and_gizmo.first; } } top_y += scaled_stride_y; } + m_current = new_current; if (could_activate) { GizmosMap::iterator it = m_gizmos.find(m_current); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bb6c1e2f64..3e733aef5b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3863,6 +3863,9 @@ void Plater::priv::undo_redo_to(size_t time_to_load) void Plater::priv::undo_redo_to(std::vector::const_iterator it_snapshot) { + // Make sure that no updating function calls take_snapshot until we are done. + SuppressSnapshots snapshot_supressor(q); + bool temp_snapshot_was_taken = this->undo_redo_stack().temp_snapshot_active(); PrinterTechnology new_printer_technology = it_snapshot->snapshot_data.printer_technology; bool printer_technology_changed = this->printer_technology != new_printer_technology; From 52933d1babc1b1f6f8c5a771bfa5daebf63c9b3d Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 22 Aug 2019 16:44:49 +0200 Subject: [PATCH 615/627] Recalculate SLA supports after undo/redo in case SLA gizmo was active and had supports when the snapshot was taken --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 47 +++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 4 ++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 11 ++++- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 8 +++- src/slic3r/GUI/Plater.cpp | 19 ++++++-- src/slic3r/Utils/UndoRedo.hpp | 1 + 6 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 8236410bd9..9aa46f1e72 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -528,7 +528,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_selection_empty) { std::pair pos_and_normal; if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection - wxGetApp().plater()->take_snapshot(_(L("Add support point"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add support point"))); m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); m_parent.set_as_dirty(); m_wait_for_up_event = true; @@ -716,14 +716,12 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) std::abort(); } - wxGetApp().plater()->take_snapshot(_(L("Delete support point"))); + Plater::TakeSnapshot(wxGetApp().plater(), _(L("Delete support point"))); for (unsigned int idx=0; idxreslice_SLA_supports(*m_model_object); } select_point(NoPoints); @@ -919,7 +917,7 @@ RENDER_AGAIN: cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f; float backup = m_new_point_head_diameter; m_new_point_head_diameter = m_old_point_head_diameter; - wxGetApp().plater()->take_snapshot(_(L("Change point head diameter"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Change point head diameter"))); m_new_point_head_diameter = backup; for (auto& cache_entry : m_editing_cache) if (cache_entry.selected) @@ -985,7 +983,7 @@ RENDER_AGAIN: if (slider_released) { m_model_object->config.opt("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash; m_model_object->config.opt("support_points_density_relative", true)->value = (int)m_density_stash; - wxGetApp().plater()->take_snapshot(_(L("Support parameter change"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change"))); m_model_object->config.opt("support_points_minimal_distance", true)->value = minimal_point_distance; m_model_object->config.opt("support_points_density_relative", true)->value = (int)density; wxGetApp().obj_list()->update_and_show_object_settings_item(); @@ -1186,7 +1184,7 @@ void GLGizmoSlaSupports::on_stop_dragging() && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected { m_editing_cache[m_hover_id] = m_point_before_drag; - wxGetApp().plater()->take_snapshot(_(L("Move support point"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move support point"))); m_editing_cache[m_hover_id] = backup; } } @@ -1285,7 +1283,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken if (unsaved_changes()) { - wxGetApp().plater()->take_snapshot(_(L("Support points edit"))); + take_snapshot_internal(_(L("Support points edit"))); m_normal_cache.clear(); for (const CacheEntry& ce : m_editing_cache) @@ -1295,7 +1293,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() m_model_object->sla_support_points.clear(); m_model_object->sla_support_points = m_normal_cache; - wxGetApp().CallAfter([this]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object); }); + reslice_SLA_supports(); } } @@ -1312,11 +1310,29 @@ void GLGizmoSlaSupports::reload_cache() } +bool GLGizmoSlaSupports::has_backend_supports() const +{ + // find SlaPrintObject with this ID + for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { + if (po->model_object()->id() == m_model_object->id() && po->is_step_done(slaposSupportPoints)) + return true; + } + return false; +} + +void GLGizmoSlaSupports::reslice_SLA_supports() const +{ + wxGetApp().CallAfter([this]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object); }); +} void GLGizmoSlaSupports::get_data_from_backend() { + if (! has_backend_supports()) + return; + + // find the respective SLAPrintObject, we need a pointer to it for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == m_model_object->id() && po->is_step_done(slaposSupportPoints)) { + if (po->model_object()->id() == m_model_object->id()) { m_normal_cache.clear(); const std::vector& points = po->get_support_points(); auto mat = po->trafo().inverse().cast(); @@ -1331,6 +1347,13 @@ void GLGizmoSlaSupports::get_data_from_backend() // We don't copy the data into ModelObject, as this would stop the background processing. } +void GLGizmoSlaSupports::take_snapshot_internal(const wxString& desc) +{ + m_internal_snapshot = true; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), desc); + m_internal_snapshot = false; +} + void GLGizmoSlaSupports::auto_generate() @@ -1341,8 +1364,8 @@ void GLGizmoSlaSupports::auto_generate() )), _(L("Warning")), wxICON_WARNING | wxYES | wxNO); if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { - wxGetApp().plater()->take_snapshot(_(L("Autogenerate support points"))); - wxGetApp().CallAfter([this]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object); }); + take_snapshot_internal(_(L("Autogenerate support points"))); + wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); m_model_object->sla_points_status = sla::PointsStatus::Generating; } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 2f6e9ccf32..a184582c4f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -86,6 +86,8 @@ public: bool is_in_editing_mode() const { return m_editing_mode; } bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); } + bool has_backend_supports() const; + void reslice_SLA_supports() const; private: bool on_init(); @@ -100,6 +102,7 @@ private: void update_mesh(); void update_cache_entry_normal(unsigned int i) const; bool unsaved_changes() const; + void take_snapshot_internal(const wxString& desc); EState m_no_hover_state = Off; EState m_no_hover_old_state = Off; @@ -128,6 +131,7 @@ private: bool m_wait_for_up_event = false; bool m_selection_empty = true; EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) + bool m_internal_snapshot = false; mutable std::unique_ptr m_tms; mutable std::unique_ptr m_supports_tms; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 236a0a27cf..c21af45f0d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -5,6 +5,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" #include "slic3r/GUI/PresetBundle.hpp" +#include "slic3r/Utils/UndoRedo.hpp" #include #include @@ -464,6 +465,11 @@ ClippingPlane GLGizmosManager::get_sla_clipping_plane() const return ClippingPlane::ClipsNothing(); } +bool GLGizmosManager::wants_reslice_supports_on_undo() const +{ + return (m_current == SlaSupports + && dynamic_cast(m_gizmos.at(SlaSupports))->has_backend_supports()); +} void GLGizmosManager::render_current_gizmo() const { @@ -870,10 +876,13 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) return processed; } -void GLGizmosManager::update_after_undo_redo() +void GLGizmosManager::update_after_undo_redo(const UndoRedo::Snapshot& snapshot) { update_data(); m_serializing = false; + if (m_current == SlaSupports + && snapshot.snapshot_data.flags & UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS) + dynamic_cast(m_gizmos[SlaSupports])->reslice_SLA_supports(); } void GLGizmosManager::reset() diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index b1c000ceb7..8b744c74fb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -9,6 +9,11 @@ #include namespace Slic3r { + +namespace UndoRedo { +class Snapshot; +} + namespace GUI { class GLCanvas3D; @@ -173,6 +178,7 @@ public: void set_sla_support_data(ModelObject* model_object); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false); ClippingPlane get_sla_clipping_plane() const; + bool wants_reslice_supports_on_undo() const; void render_current_gizmo() const; void render_current_gizmo_for_picking_pass() const; @@ -186,7 +192,7 @@ public: bool on_char(wxKeyEvent& evt); bool on_key(wxKeyEvent& evt); - void update_after_undo_redo(); + void update_after_undo_redo(const UndoRedo::Snapshot& snapshot); private: void reset(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3e733aef5b..6f7adc01b1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1908,7 +1908,7 @@ private: void update_fff_scene(); void update_sla_scene(); void undo_redo_to(std::vector::const_iterator it_snapshot); - void update_after_undo_redo(bool temp_snapshot_was_taken = false); + void update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool temp_snapshot_was_taken = false); // path to project file stored with no extension wxString m_project_filename; @@ -3823,6 +3823,12 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) } else if (this->sidebar->obj_list()->is_selected(itLayerRoot)) snapshot_data.flags |= UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR; + + // If SLA gizmo is active, ask it if it wants to trigger support generation + // on loading this snapshot. + if (view3D->get_canvas3d()->get_gizmos_manager().wants_reslice_supports_on_undo()) + snapshot_data.flags |= UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS; + //FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config. // This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config. if (this->printer_technology == ptFFF) { @@ -3908,9 +3914,14 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator bool new_selected_layer_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYER_ON_SIDEBAR) != 0; bool new_selected_layerroot_on_sidebar = (new_flags & UndoRedo::SnapshotData::SELECTED_LAYERROOT_ON_SIDEBAR) != 0; + if (this->view3D->get_canvas3d()->get_gizmos_manager().wants_reslice_supports_on_undo()) + top_snapshot_data.flags |= UndoRedo::SnapshotData::RECALCULATE_SLA_SUPPORTS; + // Disable layer editing before the Undo / Redo jump. if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled()) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); + // Make a copy of the snapshot, undo/redo could invalidate the iterator + const UndoRedo::Snapshot snapshot_copy = *it_snapshot; // Do the jump in time. if (it_snapshot->timestamp < this->undo_redo_stack().active_snapshot_time() ? this->undo_redo_stack().undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), top_snapshot_data, it_snapshot->timestamp) : @@ -3948,14 +3959,14 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator if (new_selected_settings_on_sidebar || new_selected_layer_on_sidebar) this->sidebar->obj_list()->set_selected_layers_range_idx(layer_range_idx); - this->update_after_undo_redo(temp_snapshot_was_taken); + this->update_after_undo_redo(snapshot_copy, temp_snapshot_was_taken); // Enable layer editing after the Undo / Redo jump. if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); } } -void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */) +void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */) { this->view3D->get_canvas3d()->get_selection().clear(); // Update volumes from the deserializd model, always stop / update the background processing (for both the SLA and FFF technologies). @@ -3967,7 +3978,7 @@ void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */) this->undo_redo_stack().release_least_recently_used(); //YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time) this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack().selection_deserialized().mode), this->undo_redo_stack().selection_deserialized().volumes_and_instances); - this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo(); + this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo(snapshot); wxGetApp().obj_list()->update_after_undo_redo(); diff --git a/src/slic3r/Utils/UndoRedo.hpp b/src/slic3r/Utils/UndoRedo.hpp index 558449003e..2901eacebf 100644 --- a/src/slic3r/Utils/UndoRedo.hpp +++ b/src/slic3r/Utils/UndoRedo.hpp @@ -45,6 +45,7 @@ struct SnapshotData SELECTED_SETTINGS_ON_SIDEBAR = 2, SELECTED_LAYERROOT_ON_SIDEBAR = 4, SELECTED_LAYER_ON_SIDEBAR = 8, + RECALCULATE_SLA_SUPPORTS = 16 }; }; From 189312624a6083b6d0497743aeaf50737110a668 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 23 Aug 2019 09:27:15 +0200 Subject: [PATCH 616/627] Disable layers editing if enabled while switching to SLA printer --- src/slic3r/GUI/GLCanvas3D.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 67f30c6e45..3e668c4d84 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3607,7 +3607,15 @@ bool GLCanvas3D::_init_main_toolbar() item.sprite_id = 10; item.left.toggable = true; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; - item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; }; + item.visibility_callback = [this]()->bool + { + bool res = m_process->current_printer_technology() == ptFFF; + // turns off if changing printer technology + if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) + force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); + + return res; + }; item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; if (!m_main_toolbar.add_item(item)) return false; From 77d793c5404034be71a49a6026079929503deb5b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 23 Aug 2019 09:43:38 +0200 Subject: [PATCH 617/627] KBShortcutsDialog is changed to ScrollWindow --- src/slic3r/GUI/KBShortcutsDialog.cpp | 15 +++++++++++++-- src/slic3r/GUI/KBShortcutsDialog.hpp | 6 +++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 08d46444c4..25facdd340 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -9,6 +9,11 @@ namespace Slic3r { namespace GUI { +enum { + DLG_WIDTH = 90, + DLG_HEIGHT = 60 +}; + KBShortcutsDialog::KBShortcutsDialog() : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Keyboard Shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) @@ -34,7 +39,11 @@ KBShortcutsDialog::KBShortcutsDialog() fill_shortcuts(); - auto panel = new wxPanel(this); + const int em = em_unit(); + const wxSize& size = wxSize(DLG_WIDTH * em, DLG_HEIGHT * em); + panel = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, size); + panel->SetScrollbars(1, 20, 1, 2); + auto main_grid_sizer = new wxFlexGridSizer(2, 10, 10); panel->SetSizer(main_grid_sizer); main_sizer->Add(panel, 1, wxEXPAND | wxALL, 0); @@ -207,7 +216,9 @@ void KBShortcutsDialog::on_dpi_changed(const wxRect &suggested_rect) msw_buttons_rescale(this, em, { wxID_OK }); - const wxSize& size = wxSize(85 * em, 75 * em); + const wxSize& size = wxSize(DLG_WIDTH * em, DLG_HEIGHT * em); + + panel->SetMinSize(size); SetMinSize(size); Fit(); diff --git a/src/slic3r/GUI/KBShortcutsDialog.hpp b/src/slic3r/GUI/KBShortcutsDialog.hpp index 66fe7c399b..2ba2664e8a 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.hpp +++ b/src/slic3r/GUI/KBShortcutsDialog.hpp @@ -23,10 +23,10 @@ class KBShortcutsDialog : public DPIDialog typedef std::vector< Shortcut > Shortcuts; typedef std::vector< std::pair> > ShortcutsVec; - wxString text_info {wxEmptyString}; + wxScrolledWindow* panel; - ShortcutsVec m_full_shortcuts; - ScalableBitmap m_logo_bmp; + ShortcutsVec m_full_shortcuts; + ScalableBitmap m_logo_bmp; std::vector m_head_bitmaps; public: From ac62734033f53b68ce10edac48fe2e53ea3e7133 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 23 Aug 2019 10:02:45 +0200 Subject: [PATCH 618/627] Fixed time stamps for the configuration snapshots (local / UTC time functions were mixed up) --- src/slic3r/Utils/Time.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/slic3r/Utils/Time.cpp b/src/slic3r/Utils/Time.cpp index f38c4b4074..db1aa31f6e 100644 --- a/src/slic3r/Utils/Time.cpp +++ b/src/slic3r/Utils/Time.cpp @@ -21,7 +21,11 @@ time_t parse_time_ISO8601Z(const std::string &sdate) tms.tm_hour = h; // 0-23 tms.tm_min = m; // 0-59 tms.tm_sec = s; // 0-61 (0-60 in C++11) - return mktime(&tms); +#ifdef WIN32 + return _mkgmtime(&tms); +#else /* WIN32 */ + return timegm(&tms); +#endif /* WIN32 */ } std::string format_time_ISO8601Z(time_t time) @@ -47,6 +51,7 @@ std::string format_local_date_time(time_t time) { struct tm tms; #ifdef WIN32 + // Converts a time_t time value to a tm structure, and corrects for the local time zone. localtime_s(&tms, &time); #else localtime_r(&time, &tms); @@ -60,6 +65,7 @@ time_t get_current_time_utc() { #ifdef WIN32 SYSTEMTIME st; + // Retrieves the current system date and time. The system time is expressed in Coordinated Universal Time (UTC). ::GetSystemTime(&st); std::tm tm; tm.tm_sec = st.wSecond; @@ -69,10 +75,10 @@ time_t get_current_time_utc() tm.tm_mon = st.wMonth - 1; tm.tm_year = st.wYear - 1900; tm.tm_isdst = -1; - return mktime(&tm); + return _mkgmtime(&tm); #else - const time_t current_local = time(nullptr); - return mktime(gmtime(¤t_local)); + // time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). + return time(nullptr); #endif } From bf232e1ade20ebf0a9bb7b0eb9f476933871c564 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 23 Aug 2019 10:34:16 +0200 Subject: [PATCH 619/627] Fixed forward declaration of struct Slic3r::UndoRedo::Snapshot so MSVC does not complain --- src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 8b744c74fb..c0adeb957d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -11,7 +11,7 @@ namespace Slic3r { namespace UndoRedo { -class Snapshot; +struct Snapshot; } namespace GUI { From 1d8e060b75d3370b513c4cb18dfc5dddb161bd81 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 23 Aug 2019 12:11:45 +0200 Subject: [PATCH 620/627] Followup of previous commits - removing some unnecessary code --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 13 +++---------- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp | 2 -- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 9aa46f1e72..69d57857bf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -716,7 +716,7 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) std::abort(); } - Plater::TakeSnapshot(wxGetApp().plater(), _(L("Delete support point"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete support point"))); for (unsigned int idx=0; idxsla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { - take_snapshot_internal(_(L("Autogenerate support points"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points"))); wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); m_model_object->sla_points_status = sla::PointsStatus::Generating; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index a184582c4f..e331cef53e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -102,7 +102,6 @@ private: void update_mesh(); void update_cache_entry_normal(unsigned int i) const; bool unsaved_changes() const; - void take_snapshot_internal(const wxString& desc); EState m_no_hover_state = Off; EState m_no_hover_old_state = Off; @@ -131,7 +130,6 @@ private: bool m_wait_for_up_event = false; bool m_selection_empty = true; EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) - bool m_internal_snapshot = false; mutable std::unique_ptr m_tms; mutable std::unique_ptr m_supports_tms; From 32dc4709a48bea7089d7d84d22e89e4db98baa49 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 23 Aug 2019 12:27:46 +0200 Subject: [PATCH 621/627] Workaround to fix gizmos' imgui dialog not showing up the first time the user activates them by using keyboard shortcuts. --- src/slic3r/GUI/GLCanvas3D.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3e668c4d84..8c15d0fb62 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2264,7 +2264,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) return; if (m_gizmos.on_char(evt)) + { + // FIXME: Without the following call to render(), the gimgui dialogs are not shown the first time the user tries to open them using the keyboard shortcuts + // (it looks like as if 2 render calls are needed before they show up) + render(); return; + } //#ifdef __APPLE__ // ctrlMask |= wxMOD_RAW_CONTROL; From 058a1d9a986f69f127476547e0e27caf0f219f99 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 23 Aug 2019 12:29:47 +0200 Subject: [PATCH 622/627] Some refactoring for ObjectList::remove() for the purpose of getting correct Undo/Redo snapshots --- src/slic3r/GUI/GUI_ObjectList.cpp | 50 ++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index eba5ea8857..94f3252ae7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2495,35 +2495,47 @@ void ObjectList::remove() if (GetSelectedItemsCount() == 0) return; - wxDataViewItemArray sels; - GetSelections(sels); - - wxDataViewItem parent = wxDataViewItem(0); - - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete Selected"))); - - for (auto& item : sels) + auto delete_item = [this](wxDataViewItem item) { - if (m_objects_model->InvalidItem(item)) // item can be deleted for this moment (like last 2 Instances or Volumes) - continue; - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) + wxDataViewItem parent = m_objects_model->GetParent(item); + if (m_objects_model->GetItemType(item) & itObject) delete_from_model_and_list(itObject, m_objects_model->GetIdByItem(item), -1); else { if (m_objects_model->GetItemType(item) & itLayer) { - parent = m_objects_model->GetParent(item); wxDataViewItemArray children; if (m_objects_model->GetChildren(parent, children) == 1) parent = m_objects_model->GetTopParent(item); } - else if (sels.size() == 1) - select_item(m_objects_model->GetParent(item)); del_subobject_item(item); } + + return parent; + }; + + wxDataViewItemArray sels; + GetSelections(sels); + + wxDataViewItem parent = wxDataViewItem(0); + + if (sels.Count() == 1) + parent = delete_item(GetSelection()); + else + { + Plater::TakeSnapshot snapshot = Plater::TakeSnapshot(wxGetApp().plater(), _(L("Delete Selected"))); + + for (auto& item : sels) + { + if (m_objects_model->InvalidItem(item)) // item can be deleted for this moment (like last 2 Instances or Volumes) + continue; + parent = delete_item(item); + } } - if (parent) + if (parent && !m_objects_model->InvalidItem(parent)) { select_item(parent); + update_selections_on_canvas(); + } } void ObjectList::del_layer_range(const t_layer_height_range& range) @@ -2780,8 +2792,12 @@ void ObjectList::update_selections() } else if (m_selection_mode & smLayerRoot) sels.Add(m_objects_model->GetLayerRootItem(obj_item)); - else if (m_selection_mode & smLayer) - sels.Add(m_objects_model->GetItemByLayerId(obj_idx, m_selected_layers_range_idx)); + else if (m_selection_mode & smLayer) { + if (m_selected_layers_range_idx >= 0) + sels.Add(m_objects_model->GetItemByLayerId(obj_idx, m_selected_layers_range_idx)); + else + sels.Add(obj_item); + } } else { for (const auto& object : objects_content) { From c7cdb2fd3e19ae9560f540b65e2d47759cdb59d0 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Fri, 23 Aug 2019 13:12:31 +0200 Subject: [PATCH 623/627] Added version check for .3mf and .amf project files. PrusaSlicer will refuse to import files with newer version numbers. --- src/libslic3r/Format/3mf.cpp | 36 ++++++++++++++++++++++++++++-------- src/libslic3r/Format/3mf.hpp | 2 +- src/libslic3r/Format/AMF.cpp | 33 ++++++++++++++++++++++----------- src/libslic3r/Format/AMF.hpp | 2 +- src/libslic3r/Model.cpp | 12 ++++++------ src/libslic3r/Model.hpp | 4 ++-- src/slic3r/GUI/Plater.cpp | 4 ++-- 7 files changed, 62 insertions(+), 31 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index fd1d11c2fb..9da0d4fd99 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -350,6 +350,7 @@ namespace Slic3r { // Version of the 3mf file unsigned int m_version; + bool m_check_version; XML_Parser m_xml_parser; Model* m_model; @@ -372,7 +373,7 @@ namespace Slic3r { _3MF_Importer(); ~_3MF_Importer(); - bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); + bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version); private: void _destroy_xml_parser(); @@ -465,6 +466,7 @@ namespace Slic3r { _3MF_Importer::_3MF_Importer() : m_version(0) + , m_check_version(false) , m_xml_parser(nullptr) , m_model(nullptr) , m_unit_factor(1.0f) @@ -479,9 +481,10 @@ namespace Slic3r { _destroy_xml_parser(); } - bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config) + bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version) { m_version = 0; + m_check_version = check_version; m_model = &model; m_unit_factor = 1.0f; m_curr_object.reset(); @@ -543,12 +546,21 @@ namespace Slic3r { if (boost::algorithm::istarts_with(name, MODEL_FOLDER) && boost::algorithm::iends_with(name, MODEL_EXTENSION)) { - // valid model name -> extract model - if (!_extract_model_from_archive(archive, stat)) + try { + // valid model name -> extract model + if (!_extract_model_from_archive(archive, stat)) + { + close_zip_reader(&archive); + add_error("Archive does not contain a valid model"); + return false; + } + } + catch (const std::exception& e) + { + // ensure the zip archive is closed and rethrow the exception close_zip_reader(&archive); - add_error("Archive does not contain a valid model"); - return false; + throw e; } } } @@ -1399,8 +1411,16 @@ namespace Slic3r { bool _3MF_Importer::_handle_end_metadata() { if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION) + { m_version = (unsigned int)atoi(m_curr_characters.c_str()); + if (m_check_version && (m_version > VERSION_3MF)) + { + std::string msg = "The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatibile."; + throw std::runtime_error(msg.c_str()); + } + } + return true; } @@ -2316,13 +2336,13 @@ namespace Slic3r { return true; } - bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model) + bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) { if ((path == nullptr) || (config == nullptr) || (model == nullptr)) return false; _3MF_Importer importer; - bool res = importer.load_model_from_file(path, *model, *config); + bool res = importer.load_model_from_file(path, *model, *config, check_version); importer.log_errors(); return res; } diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index b5927651ec..f387192ab5 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -24,7 +24,7 @@ namespace Slic3r { class DynamicPrintConfig; // Load the content of a 3mf file into the given model and preset bundle. - extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model); + extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version); // Save the given model and the config data contained in the given Print into a 3mf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 08820f2ea1..be15f833b5 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -44,7 +44,7 @@ namespace Slic3r struct AMFParserContext { - AMFParserContext(XML_Parser parser, DynamicPrintConfig *config, Model *model) : + AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, Model* model) : m_version(0), m_parser(parser), m_model(*model), @@ -755,7 +755,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) return result; } -bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, Model* model, unsigned int& version) +bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, Model* model, bool check_version) { if (stat.m_uncomp_size == 0) { @@ -801,19 +801,21 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi ctx.endDocument(); - version = ctx.m_version; + if (check_version && (ctx.m_version > VERSION_AMF)) + { + std::string msg = "The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatibile."; + throw std::runtime_error(msg.c_str()); + } return true; } // Load an AMF archive into a provided model. -bool load_amf_archive(const char *path, DynamicPrintConfig *config, Model *model) +bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) { if ((path == nullptr) || (model == nullptr)) return false; - unsigned int version = 0; - mz_zip_archive archive; mz_zip_zero_struct(&archive); @@ -833,11 +835,20 @@ bool load_amf_archive(const char *path, DynamicPrintConfig *config, Model *model { if (boost::iends_with(stat.m_filename, ".amf")) { - if (!extract_model_from_archive(archive, stat, config, model, version)) + try { + if (!extract_model_from_archive(archive, stat, config, model, check_version)) + { + close_zip_reader(&archive); + printf("Archive does not contain a valid model"); + return false; + } + } + catch (const std::exception& e) + { + // ensure the zip archive is closed and rethrow the exception close_zip_reader(&archive); - printf("Archive does not contain a valid model"); - return false; + throw e; } break; @@ -862,7 +873,7 @@ bool load_amf_archive(const char *path, DynamicPrintConfig *config, Model *model // Load an AMF file into a provided model. // If config is not a null pointer, updates it if the amf file/archive contains config data -bool load_amf(const char *path, DynamicPrintConfig *config, Model *model) +bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) { if (boost::iends_with(path, ".amf.xml")) // backward compatibility with older slic3r output @@ -877,7 +888,7 @@ bool load_amf(const char *path, DynamicPrintConfig *config, Model *model) file.read(const_cast(zip_mask.data()), 2); file.close(); - return (zip_mask == "PK") ? load_amf_archive(path, config, model) : load_amf_file(path, config, model); + return (zip_mask == "PK") ? load_amf_archive(path, config, model, check_version) : load_amf_file(path, config, model); } else return false; diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp index e085ad22ea..1206d60d07 100644 --- a/src/libslic3r/Format/AMF.hpp +++ b/src/libslic3r/Format/AMF.hpp @@ -7,7 +7,7 @@ class Model; class DynamicPrintConfig; // Load the content of an amf file into the given model and configuration. -extern bool load_amf(const char *path, DynamicPrintConfig *config, Model *model); +extern bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version); // Save the given model and the config data into an amf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 4331c43798..5121a1fe21 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -84,7 +84,7 @@ void Model::update_links_bottom_up_recursive() } } -Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances) +Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version) { Model model; @@ -98,9 +98,9 @@ Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *c else if (boost::algorithm::iends_with(input_file, ".obj")) result = load_obj(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) - result = load_amf(input_file.c_str(), config, &model); + result = load_amf(input_file.c_str(), config, &model, check_version); else if (boost::algorithm::iends_with(input_file, ".3mf")) - result = load_3mf(input_file.c_str(), config, &model); + result = load_3mf(input_file.c_str(), config, &model, false); else if (boost::algorithm::iends_with(input_file, ".prusa")) result = load_prus(input_file.c_str(), &model); else @@ -121,15 +121,15 @@ Model Model::read_from_file(const std::string &input_file, DynamicPrintConfig *c return model; } -Model Model::read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances) +Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version) { Model model; bool result = false; if (boost::algorithm::iends_with(input_file, ".3mf")) - result = load_3mf(input_file.c_str(), config, &model); + result = load_3mf(input_file.c_str(), config, &model, check_version); else if (boost::algorithm::iends_with(input_file, ".zip.amf")) - result = load_amf(input_file.c_str(), config, &model); + result = load_amf(input_file.c_str(), config, &model, check_version); else throw std::runtime_error("Unknown file format. Input file must have .3mf or .zip.amf extension."); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 2678ddfcc6..422467e088 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -747,8 +747,8 @@ public: OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) - static Model read_from_file(const std::string &input_file, DynamicPrintConfig *config = nullptr, bool add_default_instances = true); - static Model read_from_archive(const std::string &input_file, DynamicPrintConfig *config, bool add_default_instances = true); + static Model read_from_file(const std::string& input_file, DynamicPrintConfig* config = nullptr, bool add_default_instances = true, bool check_version = false); + static Model read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances = true, bool check_version = false); // Add a new ModelObject to this Model, generate a new ID for this ModelObject. ModelObject* add_object(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bb6c1e2f64..c8ffa2ef04 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2215,7 +2215,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ DynamicPrintConfig config; { DynamicPrintConfig config_loaded; - model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false); + model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false, load_config); if (load_config && !config_loaded.empty()) { // Based on the printer technology field found in the loaded config, select the base for the config, PrinterTechnology printer_technology = Preset::printer_technology(config_loaded); @@ -2255,7 +2255,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } else { - model = Slic3r::Model::read_from_file(path.string(), nullptr, false); + model = Slic3r::Model::read_from_file(path.string(), nullptr, false, load_config); for (auto obj : model.objects) if (obj->name.empty()) obj->name = fs::path(obj->input_file).filename().string(); From 7929bda24a7cf4c87bb3207063862057555ae8b0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 23 Aug 2019 13:21:56 +0200 Subject: [PATCH 624/627] Resize of the KBShortcutsDialog according to the display size --- src/slic3r/GUI/KBShortcutsDialog.cpp | 36 +++++++++++++++++++++------- src/slic3r/GUI/KBShortcutsDialog.hpp | 1 + 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 25facdd340..936a25e22a 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -9,11 +9,6 @@ namespace Slic3r { namespace GUI { -enum { - DLG_WIDTH = 90, - DLG_HEIGHT = 60 -}; - KBShortcutsDialog::KBShortcutsDialog() : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Keyboard Shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) @@ -39,9 +34,7 @@ KBShortcutsDialog::KBShortcutsDialog() fill_shortcuts(); - const int em = em_unit(); - const wxSize& size = wxSize(DLG_WIDTH * em, DLG_HEIGHT * em); - panel = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, size); + panel = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, get_size()); panel->SetScrollbars(1, 20, 1, 2); auto main_grid_sizer = new wxFlexGridSizer(2, 10, 10); @@ -216,7 +209,7 @@ void KBShortcutsDialog::on_dpi_changed(const wxRect &suggested_rect) msw_buttons_rescale(this, em, { wxID_OK }); - const wxSize& size = wxSize(DLG_WIDTH * em, DLG_HEIGHT * em); + wxSize size = get_size(); panel->SetMinSize(size); @@ -231,5 +224,30 @@ void KBShortcutsDialog::onCloseDialog(wxEvent &) this->EndModal(wxID_CLOSE); } +wxSize KBShortcutsDialog::get_size() +{ + wxTopLevelWindow* window = Slic3r::GUI::find_toplevel_parent(this); + const int display_idx = wxDisplay::GetFromWindow(window); + wxRect display; + if (display_idx == wxNOT_FOUND) { + display = wxDisplay(0u).GetClientArea(); + window->Move(display.GetTopLeft()); + } + else { + display = wxDisplay(display_idx).GetClientArea(); + } + + const int em = em_unit(); + wxSize dialog_size = wxSize(90 * em, 85 * em); + + const int margin = 10 * em; + if (dialog_size.x > display.GetWidth()) + dialog_size.x = display.GetWidth() - margin; + if (dialog_size.y > display.GetHeight()) + dialog_size.y = display.GetHeight() - margin; + + return dialog_size; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/KBShortcutsDialog.hpp b/src/slic3r/GUI/KBShortcutsDialog.hpp index 2ba2664e8a..7ac28778b5 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.hpp +++ b/src/slic3r/GUI/KBShortcutsDialog.hpp @@ -39,6 +39,7 @@ protected: private: void onCloseDialog(wxEvent &); + wxSize get_size(); }; } // namespace GUI From 12cb930634f81791d60494df5f41c863934b3b96 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 23 Aug 2019 13:27:19 +0200 Subject: [PATCH 625/627] A little optimizaiton --- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 69d57857bf..693c3c074d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -1314,8 +1314,8 @@ bool GLGizmoSlaSupports::has_backend_supports() const { // find SlaPrintObject with this ID for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == m_model_object->id() && po->is_step_done(slaposSupportPoints)) - return true; + if (po->model_object()->id() == m_model_object->id()) + return po->is_step_done(slaposSupportPoints); } return false; } From bb6cde789df747f10c8d51eb9099b196b4d05041 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 23 Aug 2019 13:44:07 +0200 Subject: [PATCH 626/627] Fix of a missing parameter. --- src/slic3r/Utils/FixModelByWin10.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 710f19090b..d955d6a7e4 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -377,7 +377,7 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) // PresetBundle bundle; on_progress(L("Loading repaired model"), 80); DynamicPrintConfig config; - bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model); + bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model, false); boost::filesystem::remove(path_dst); if (! loaded) throw std::runtime_error(L("Import of the repaired 3mf file failed")); From 6dbba8dbb07992e68306886c55bbe2ac5aba4d35 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 23 Aug 2019 13:57:49 +0200 Subject: [PATCH 627/627] Added missed include for OSX build --- src/slic3r/GUI/KBShortcutsDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 936a25e22a..2d5454ce99 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -3,6 +3,7 @@ #include "libslic3r/Utils.hpp" #include "GUI.hpp" #include +#include #include "GUI_App.hpp" #include "wxExtensions.hpp"