First version of SLA support points generation

This commit is contained in:
Lukas Matena 2018-12-07 14:10:16 +01:00
parent c23c09c453
commit 0afe2aec1e
10 changed files with 288 additions and 9 deletions

View file

@ -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

View file

@ -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");

View file

@ -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);

View file

@ -0,0 +1,155 @@
#include "igl/random_points_on_mesh.h"
#include "igl/AABB.h"
#include "SLAAutoSupports.hpp"
#include "Model.hpp"
#include <iostream>
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<float>() * 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; i<stl.stats.number_of_facets; ++i) {
const stl_facet* facet = stl.facet_start+i;
V(3*i+0, 0) = facet->vertex[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<Vec3f> 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<Eigen::MatrixXf,3> aabb;
aabb.init(V, F);
for (unsigned int i=0; i<m_model_object.sla_support_points.size(); ++i) {
int facet_idx = 0;
Eigen::Matrix<float, 1, 3> 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<m_model_object.sla_support_points.size(); ++i) {
if (approximate_geodesic_distance(m_model_object.sla_support_points[i], point, facets_normals[i], normal) < distance_limit) {
add_it = false;
++refused_points;
break;
}
}
if (add_it) {
m_model_object.sla_support_points.push_back(point);
facets_normals.push_back(normal);
++added_points;
refused_points = 0;
}
}
// Now transform all support points to mesh coordinates:
for (Vec3f& point : m_model_object.sla_support_points)
point = transformation_matrix.inverse().cast<float>() * 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

View file

@ -0,0 +1,41 @@
#ifndef SLAAUTOSUPPORTS_HPP_
#define SLAAUTOSUPPORTS_HPP_
#include <libslic3r/Point.hpp>
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_

View file

@ -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);

View file

@ -20,6 +20,7 @@
#include <wx/stattext.h>
#include <wx/debug.h>
#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<GLfloat, 4, 4> glmatrix;
glGetFloatv (GL_MODELVIEW_MATRIX, glmatrix.data());
Eigen::Matrix<float, 4, 1> grabber_pos;
for (int j=0; j<3; ++j)
grabber_pos(j) = m_grabbers[i].center(j);
grabber_pos[3] = 1.f;
Eigen::Matrix<float, 4, 1> 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<double>();
@ -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<double>();
}
}
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");
}

View file

@ -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 <array>
#include <vector>
@ -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;

View file

@ -403,6 +403,9 @@ const std::vector<std::string>& 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",

View file

@ -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");