From 0afe2aec1e34d66974f54bbeceedfb38b65f59ec Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 7 Dec 2018 14:10:16 +0100 Subject: [PATCH] First version of SLA support points generation --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/PrintConfig.cpp | 27 +++++ src/libslic3r/PrintConfig.hpp | 8 ++ src/libslic3r/SLA/SLAAutoSupports.cpp | 155 ++++++++++++++++++++++++++ src/libslic3r/SLA/SLAAutoSupports.hpp | 41 +++++++ src/libslic3r/TriangleMesh.hpp | 2 +- src/slic3r/GUI/GLGizmo.cpp | 51 +++++++-- src/slic3r/GUI/GLGizmo.hpp | 3 + src/slic3r/GUI/Preset.cpp | 3 + src/slic3r/GUI/Tab.cpp | 5 + 10 files changed, 288 insertions(+), 9 deletions(-) create mode 100644 src/libslic3r/SLA/SLAAutoSupports.cpp create mode 100644 src/libslic3r/SLA/SLAAutoSupports.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 669efd620..df3ddb6de 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -139,6 +139,8 @@ add_library(libslic3r STATIC Rasterizer/Rasterizer.cpp SLAPrint.cpp SLAPrint.hpp + SLA/SLAAutoSupports.hpp + SLA/SLAAutoSupports.cpp Slicing.cpp Slicing.hpp SlicingAdaptive.cpp diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d86ddc25b..4b3b1e80a 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2561,6 +2561,33 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->default_value = new ConfigOptionFloat(5.0); + def = this->add("support_density_at_horizontal", coInt); + def->label = L("Density on horizontal surfaces"); + def->category = L("Supports"); + def->tooltip = L("How many support points (approximately) should be placed on horizontal surface."); + def->sidetext = L("points per square dm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionInt(500); + + def = this->add("support_density_at_45", coInt); + def->label = L("Density on surfaces at 45 degrees"); + def->category = L("Supports"); + def->tooltip = L("How many support points (approximately) should be placed on surface sloping at 45 degrees."); + def->sidetext = L("points per square dm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionInt(250); + + def = this->add("support_minimal_z", coFloat); + def->label = L("Minimal support point height"); + def->category = L("Supports"); + def->tooltip = L("No support points will be placed lower than this value from the bottom."); + def->sidetext = L("mm"); + def->cli = ""; + def->min = 0; + def->default_value = new ConfigOptionFloat(0.f); + def = this->add("pad_enable", coBool); def->label = L("Use pad"); def->category = L("Pad"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index dac2de7cc..9804d72ee 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -955,6 +955,11 @@ public: // 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_density_at_horizontal; + ConfigOptionInt support_density_at_45; + ConfigOptionFloat support_minimal_z; + // Now for the base pool (pad) ///////////////////////////////////////////// // Enabling or disabling support creation @@ -987,6 +992,9 @@ protected: OPT_PTR(support_base_height); OPT_PTR(support_critical_angle); OPT_PTR(support_max_bridge_length); + OPT_PTR(support_density_at_horizontal); + OPT_PTR(support_density_at_45); + OPT_PTR(support_minimal_z); OPT_PTR(support_object_elevation); OPT_PTR(pad_enable); OPT_PTR(pad_wall_thickness); diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp new file mode 100644 index 000000000..73c418911 --- /dev/null +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -0,0 +1,155 @@ +#include "igl/random_points_on_mesh.h" +#include "igl/AABB.h" + +#include "SLAAutoSupports.hpp" +#include "Model.hpp" + +#include + + +namespace Slic3r { + +SLAAutoSupports::SLAAutoSupports(ModelObject& mo, const SLAAutoSupports::Config& c) +: m_model_object(mo), mesh(), m_config(c) +{} + + +float SLAAutoSupports::approximate_geodesic_distance(const Vec3f& p1, const Vec3f& p2, Vec3f& n1, Vec3f& n2) +{ + n1.normalize(); + n2.normalize(); + + Vec3f v = (p2-p1); + v.normalize(); + + float c1 = n1.dot(v); + float c2 = n2.dot(v); + float result = pow(p1(0)-p2(0), 2) + pow(p1(1)-p2(1), 2) + pow(p1(2)-p2(2), 2); + // Check for division by zero: + if(fabs(c1 - c2) > 0.0001) + result *= (asin(c1) - asin(c2)) / (c1 - c2); + return result; +} + + +void SLAAutoSupports::generate() +{ + // Loads the ModelObject raw_mesh and transforms it by first instance's transformation matrix (disregarding translation). + // Instances only differ in z-rotation, so it does not matter which of them will be used for the calculation. + // The supports point will be calculated on this mesh (so scaling ang vertical direction is correctly accounted for). + // Results will be inverse-transformed to raw_mesh coordinates. + TriangleMesh mesh = m_model_object.raw_mesh(); + Transform3d transformation_matrix = m_model_object.instances[0]->get_matrix(true/*dont_translate*/); + mesh.transform(transformation_matrix); + + // Check that the object is thick enough to produce any support points + BoundingBoxf3 bb = mesh.bounding_box(); + if (bb.size()(2) < m_config.minimal_z) + return; + + // All points that we curretly have must be transformed too, so distance to them is correcly calculated. + for (Vec3f& point : m_model_object.sla_support_points) + point = transformation_matrix.cast() * point; + + const stl_file& stl = mesh.stl; + Eigen::MatrixXf V; + Eigen::MatrixXi F; + 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); + F(i, 0) = 3*i+0; + F(i, 1) = 3*i+1; + F(i, 2) = 3*i+2; + } + + // In order to calculate distance to already placed points, we must keep know which facet the point lies on. + std::vector facets_normals; + + // The AABB hierarchy will be used to find normals of already placed points. + // The points added automatically will just push_back the new normal on the fly. + igl::AABB aabb; + aabb.init(V, F); + for (unsigned int i=0; i dump; + Eigen::MatrixXf query_point = m_model_object.sla_support_points[i]; + aabb.squared_distance(V, F, query_point, facet_idx, dump); + Vec3f a1 = V.row(F(facet_idx,1)) - V.row(F(facet_idx,0)); + Vec3f a2 = V.row(F(facet_idx,2)) - V.row(F(facet_idx,0)); + Vec3f normal = a1.cross(a2); + normal.normalize(); + facets_normals.push_back(normal); + } + + // New potential support point is randomly generated on the mesh and distance to all already placed points is calculated. + // In case it is never smaller than certain limit (depends on the new point's facet normal), the point is accepted. + // The process stops after certain number of points is refused in a row. + Vec3f point; + Vec3f normal; + int added_points = 0; + int refused_points = 0; + const int refused_limit = std::min(100, (int)(1. / m_config.density_at_horizontal)); + // Angle at which the density reaches zero: + const float threshold_angle = std::min(M_PI_2, M_PI_4 * acos(0.f/m_config.density_at_horizontal) / acos(m_config.density_at_45/m_config.density_at_horizontal)); + + srand(time(NULL)); // rand() is used by igl::random_point_on_mesh + while (refused_points < refused_limit) { + // Place a random point on the mesh and calculate corresponding facet's normal: + Eigen::VectorXi FI; + Eigen::MatrixXf B; + igl::random_points_on_mesh(1, V, F, B, FI); + point = B(0,0)*V.row(F(FI(0),0)) + + B(0,1)*V.row(F(FI(0),1)) + + B(0,2)*V.row(F(FI(0),2)); + if (point(2) - bb.min(2) < m_config.minimal_z) + continue; + + Vec3f a1 = V.row(F(FI(0),1)) - V.row(F(FI(0),0)); + Vec3f a2 = V.row(F(FI(0),2)) - V.row(F(FI(0),0)); + normal = a1.cross(a2); + normal.normalize(); + + // calculate angle between the normal and vertical: + float angle = angle_from_normal(normal); + if (angle > threshold_angle) + continue; + + const float distance_limit = 1./(2.4*get_required_density(angle)); + bool add_it = true; + for (unsigned int i=0; i() * point; +} + + + +float SLAAutoSupports::get_required_density(float angle) const +{ + // calculation would be density_0 * cos(angle). To provide one more degree of freedom, we will scale the angle + // to get the user-set density for 45 deg. So it ends up as density_0 * cos(K * angle). + float K = 4*acos(m_config.density_at_45/m_config.density_at_horizontal) / M_PI; + return std::max(0., m_config.density_at_horizontal * cos(K*angle)); +} + + + +} // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/SLA/SLAAutoSupports.hpp b/src/libslic3r/SLA/SLAAutoSupports.hpp new file mode 100644 index 000000000..637f0909c --- /dev/null +++ b/src/libslic3r/SLA/SLAAutoSupports.hpp @@ -0,0 +1,41 @@ +#ifndef SLAAUTOSUPPORTS_HPP_ +#define SLAAUTOSUPPORTS_HPP_ + +#include + + +namespace Slic3r { + +class ModelObject; + + + + +class SLAAutoSupports { +public: + struct Config { + float density_at_horizontal; + float density_at_45; + float minimal_z; + }; + + SLAAutoSupports(ModelObject& mo, const SLAAutoSupports::Config& c); + void generate(); + +private: + TriangleMesh mesh; + static float angle_from_normal(const stl_normal& normal) { return acos((-normal.normalized())(2)); } + float get_required_density(float angle) const; + static float approximate_geodesic_distance(const Vec3f& p1, const Vec3f& p2, Vec3f& n1, Vec3f& n2); + + ModelObject& m_model_object; + SLAAutoSupports::Config m_config; +}; + + + + +} // namespace Slic3r + + +#endif // SLAAUTOSUPPORTS_HPP_ \ No newline at end of file diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index e2bf47014..81f64ac85 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -35,7 +35,7 @@ public: void repair(); float volume(); void check_topology(); - bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == this->stl.stats.number_of_facets; } + bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; } void WriteOBJFile(char* output_file); void scale(float factor); void scale(const Vec3d &versor); diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index 239b45702..34de24983 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -20,6 +20,7 @@ #include #include +#include "Tab.hpp" #include "GUI.hpp" #include "GUI_Utils.hpp" #include "GUI_App.hpp" @@ -1079,7 +1080,7 @@ void GLGizmoScale3D::on_render_input_window(float x, float y, const GLCanvas3D:: m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); m_imgui->set_next_window_bg_alpha(0.5f); m_imgui->begin(label, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - m_imgui->input_vec3("", m_scale, 100.0f, "%.2f"); + m_imgui->input_vec3("", m_scale * 100.f, 100.0f, "%.2f"); m_imgui->end(); } #endif // ENABLE_IMGUI @@ -1782,8 +1783,7 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const G #else void GLGizmoSlaSupports::set_model_object_ptr(ModelObject* model_object) { - if (model_object != nullptr) - { + if (model_object != nullptr) { m_starting_center = Vec3d::Zero(); m_model_object = model_object; @@ -1812,6 +1812,7 @@ void GLGizmoSlaSupports::on_render(const GLCanvas3D::Selection& selection) const Vec3d dragged_offset = selection.get_bounding_box().center() - m_starting_center; #endif // !ENABLE_SLA_SUPPORT_GIZMO_MOD + for (auto& g : m_grabbers) { g.color[0] = 1.f; g.color[1] = 0.f; @@ -1871,6 +1872,16 @@ void GLGizmoSlaSupports::render_grabbers(const GLCanvas3D::Selection& selection, float render_color[3]; for (int i = 0; i < (int)m_grabbers.size(); ++i) { + // first precalculate the grabber position in world coordinates, so that the grabber + // is not scaled with the object (as it would be if rendered with current gl matrix). + Eigen::Matrix glmatrix; + glGetFloatv (GL_MODELVIEW_MATRIX, glmatrix.data()); + Eigen::Matrix grabber_pos; + for (int j=0; j<3; ++j) + grabber_pos(j) = m_grabbers[i].center(j); + grabber_pos[3] = 1.f; + Eigen::Matrix grabber_world_position = glmatrix * grabber_pos; + if (!picking && (m_hover_id == i)) { render_color[0] = 1.0f - m_grabbers[i].color[0]; @@ -1882,9 +1893,10 @@ void GLGizmoSlaSupports::render_grabbers(const GLCanvas3D::Selection& selection, ::glColor3fv(render_color); ::glPushMatrix(); - ::glTranslated(m_grabbers[i].center(0), m_grabbers[i].center(1), m_grabbers[i].center(2)); + ::glLoadIdentity(); + ::glTranslated(grabber_world_position(0), grabber_world_position(1), grabber_world_position(2) + z_shift); ::gluQuadricDrawStyle(m_quadric, GLU_SILHOUETTE); - ::gluSphere(m_quadric, 0.75, 64, 32); + ::gluSphere(m_quadric, 0.75, 64, 36); ::glPopMatrix(); } @@ -1933,7 +1945,7 @@ void GLGizmoSlaSupports::render_grabbers(bool picking) const GLUquadricObj *quadric; quadric = ::gluNewQuadric(); ::gluQuadricDrawStyle(quadric, GLU_FILL ); - ::gluSphere( quadric , 0.75f, 36 , 18 ); + ::gluSphere( quadric , 0.75f, 64 , 32 ); ::gluDeleteQuadric(quadric); ::glPopMatrix(); if (!picking) @@ -1992,6 +2004,7 @@ void GLGizmoSlaSupports::update_mesh() // we'll now reload Grabbers (selection might have changed): m_grabbers.clear(); + for (const Vec3f& point : m_model_object->sla_support_points) { m_grabbers.push_back(Grabber()); m_grabbers.back().center = point.cast(); @@ -2083,7 +2096,6 @@ void GLGizmoSlaSupports::delete_current_grabber(bool delete_all) if (delete_all) { m_grabbers.clear(); m_model_object->sla_support_points.clear(); - m_parent.reload_scene(true); // in case this was called from ImGui overlay dialog, the refresh would be delayed // This should trigger the support generation // wxGetApp().plater()->reslice(); @@ -2159,12 +2171,34 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, const GLCanvas m_imgui->text(_(L("Right mouse click - remove point"))); m_imgui->text(" "); - bool remove_all_clicked = m_imgui->button(_(L("Remove all points"))); + bool generate = m_imgui->button(_(L("Generate points automatically"))); + bool remove_all_clicked = m_imgui->button(_(L("Remove all points")) + (m_model_object == nullptr ? "" : " (" + std::to_string(m_model_object->sla_support_points.size())+")")); m_imgui->end(); if (remove_all_clicked) delete_current_grabber(true); + + if (generate) { + const DynamicPrintConfig& cfg = *wxGetApp().get_tab(Preset::TYPE_SLA_PRINT)->get_config(); + SLAAutoSupports::Config config; + config.density_at_horizontal = cfg.opt_int("support_density_at_horizontal") / 10000.f; + config.density_at_45 = cfg.opt_int("support_density_at_45") / 10000.f; + config.minimal_z = cfg.opt_float("support_minimal_z"); + + SLAAutoSupports sas(*m_model_object, config); + sas.generate(); + m_grabbers.clear(); + for (const Vec3f& point : m_model_object->sla_support_points) { + m_grabbers.push_back(Grabber()); + m_grabbers.back().center = point.cast(); + } + } + + if (remove_all_clicked || generate) { + m_parent.reload_scene(true); + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } } #endif // ENABLE_IMGUI @@ -2180,6 +2214,7 @@ bool GLGizmoSlaSupports::on_is_selectable() const } std::string GLGizmoSlaSupports::on_get_name() const + { return L("SLA Support Points"); } diff --git a/src/slic3r/GUI/GLGizmo.hpp b/src/slic3r/GUI/GLGizmo.hpp index 4229d0362..bfec0965e 100644 --- a/src/slic3r/GUI/GLGizmo.hpp +++ b/src/slic3r/GUI/GLGizmo.hpp @@ -5,8 +5,10 @@ #include "../../slic3r/GUI/GLTexture.hpp" #include "../../slic3r/GUI/GLCanvas3D.hpp" + #include "libslic3r/Point.hpp" #include "libslic3r/BoundingBox.hpp" +#include "libslic3r/SLA/SLAAutoSupports.hpp" #include #include @@ -434,6 +436,7 @@ protected: class GLGizmoSlaSupports : public GLGizmoBase { private: + SLAAutoSupports* m_sas = nullptr; ModelObject* m_model_object = nullptr; #if ENABLE_SLA_SUPPORT_GIZMO_MOD ModelObject* m_old_model_object = nullptr; diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index bab3293a4..3100f25a1 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -403,6 +403,9 @@ const std::vector& Preset::sla_print_options() "support_critical_angle", "support_max_bridge_length", "support_object_elevation", + "support_density_at_horizontal", + "support_density_at_45", + "support_minimal_z", "pad_enable", "pad_wall_thickness", "pad_wall_height", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6d6b3b614..9a5c960bb 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3112,6 +3112,11 @@ void TabSLAPrint::build() optgroup->append_single_option_line("support_critical_angle"); optgroup->append_single_option_line("support_max_bridge_length"); + optgroup = page->new_optgroup(_(L("Automatic generation"))); + optgroup->append_single_option_line("support_density_at_horizontal"); + optgroup->append_single_option_line("support_density_at_45"); + optgroup->append_single_option_line("support_minimal_z"); + page = add_options_page(_(L("Pad")), "brick.png"); optgroup = page->new_optgroup(_(L("Pad"))); optgroup->append_single_option_line("pad_enable");