From 7617b10d6e089014cf15f10d01f6171c9dd1f74e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 21 Dec 2018 12:35:20 +0100 Subject: [PATCH] SLA autosupports including islands --- src/libslic3r/SLA/SLAAutoSupports.cpp | 205 ++++++++++++++++++-------- src/libslic3r/SLA/SLAAutoSupports.hpp | 28 ++-- src/libslic3r/SLAPrint.cpp | 53 ++++--- src/libslic3r/SLAPrint.hpp | 3 + src/slic3r/GUI/GLCanvas3D.hpp | 1 + src/slic3r/GUI/GLGizmo.cpp | 35 +++-- 6 files changed, 209 insertions(+), 116 deletions(-) diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index dd737c9886..21d54e518d 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -8,21 +8,41 @@ #include "Point.hpp" #include - +#include namespace Slic3r { -namespace SLAAutoSupports { -SLAAutoSupports::SLAAutoSupports(ModelObject& mo, const SLAAutoSupports::Config& c) -: m_model_object(mo), mesh(), m_config(c) -{} + +SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, const std::vector& heights, const Config& config) +: m_config(config), m_V(emesh.V), m_F(emesh.F) +{ + // FIXME: It might be safer to get rid of the rand() calls altogether, because it is probably + // not always thread-safe and can be slow if it is. + srand(time(NULL)); // rand() is used by igl::random_point_on_mesh + + // Find all separate islands that will need support. The coord_t number denotes height + // of a point just below the mesh (so that we can later project the point precisely + // on the mesh by raycasting (done by igl) and not risking we will place the point inside). + std::vector> islands = find_islands(slices, heights); + + // Uniformly cover each of the islands with support points. + for (const auto& island : islands) { + std::vector points = uniformly_cover(island); + project_upward_onto_mesh(points); + m_output.insert(m_output.end(), points.begin(), points.end()); + } + + // We are done with the islands. Let's sprinkle the rest of the mesh. + // The function appends to m_output. + sprinkle_mesh(mesh); +} -float SLAAutoSupports::approximate_geodesic_distance(const Vec3f& p1, const Vec3f& p2, Vec3f& n1, Vec3f& n2) +float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2) { n1.normalize(); n2.normalize(); - Vec3f v = (p2-p1); + Vec3d v = (p2-p1); v.normalize(); float c1 = n1.dot(v); @@ -35,15 +55,16 @@ float SLAAutoSupports::approximate_geodesic_distance(const Vec3f& p1, const Vec3 } -void SLAAutoSupports::generate() +void SLAAutoSupports::sprinkle_mesh(const TriangleMesh& mesh) { + std::vector points; // 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); + //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(); @@ -51,30 +72,20 @@ void SLAAutoSupports::generate() 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; + //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; + std::vector facets_normals; + + // Only points belonging to islands were added so far - they all lie on horizontal surfaces: + for (unsigned int i=0; i aabb; + /*igl::AABB aabb; aabb.init(V, F); for (unsigned int i=0; i()); if (angle > threshold_angle) continue; - const float distance_limit = 1./(2.4*get_required_density(angle)); + const float limit = distance_limit(angle); bool add_it = true; - for (unsigned int i=0; i()); facets_normals.push_back(normal); ++added_points; refused_points = 0; } } + m_output.insert(m_output.end(), points.begin(), points.end()); + // Now transform all support points to mesh coordinates: - for (Vec3f& point : m_model_object.sla_support_points) - point = transformation_matrix.inverse().cast() * point; + //for (Vec3f& point : m_model_object.sla_support_points) + // point = transformation_matrix.inverse().cast() * point; } @@ -153,13 +165,13 @@ float SLAAutoSupports::get_required_density(float angle) const return std::max(0.f, float(m_config.density_at_horizontal * cos(K*angle))); } +float SLAAutoSupports::distance_limit(float angle) const +{ + return 1./(2.4*get_required_density(angle)); +} - - - - -void output_expolygons(const ExPolygons& expolys, std::string filename) +void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, std::string filename) const { BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000)); Slic3r::SVG svg_cummulative(filename, bb); @@ -176,9 +188,9 @@ void output_expolygons(const ExPolygons& expolys, std::string filename) } } -std::vector find_islands(const std::vector& slices, const std::vector& heights) +std::vector> SLAAutoSupports::find_islands(const std::vector& slices, const std::vector& heights) const { - std::vector support_points_out; + std::vector> islands; struct PointAccessor { const Point* operator()(const Point &pt) const { return &pt; } @@ -192,7 +204,6 @@ std::vector find_islands(const std::vector& slices, const std std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i); output_expolygons(expolys_top, "top" + layer_num_str + ".svg"); ExPolygons diff = diff_ex(expolys_top, expolys_bottom); - ExPolygons islands; output_expolygons(diff, "diff" + layer_num_str + ".svg"); @@ -223,22 +234,86 @@ std::vector find_islands(const std::vector& slices, const std } if (island) { // all points of the diff polygon are from the top slice - islands.push_back(polygon); + islands.push_back(std::make_pair(polygon, scale_(i!=0 ? heights[i-1] : heights[0]-(heights[1]-heights[0])))); } NO_ISLAND: ;// continue with next ExPolygon } - if (!islands.empty()) - output_expolygons(islands, "islands" + layer_num_str + ".svg"); - for (const ExPolygon& island : islands) { - Point centroid = island.contour.centroid(); - Vec3d centroid_d(centroid(0), centroid(1), scale_(i!=0 ? heights[i-1] : heights[0]-(heights[1]-heights[0]))) ; - support_points_out.push_back(unscale(centroid_d)); - } + //if (!islands.empty()) + // output_expolygons(islands, "islands" + layer_num_str + ".svg"); } - return support_points_out; + return islands; } -} // namespace SLAAutoSupports +std::vector SLAAutoSupports::uniformly_cover(const std::pair& island) +{ + int num_of_points = std::max(1, (int)(island.first.area()*pow(SCALING_FACTOR, 2) * get_required_density(0))); + + // In case there is just one point to place, we'll place it into the polygon's centroid (unless it lies in a hole). + if (num_of_points == 1) { + Point out(island.first.contour.centroid()); + out(2) = island.second; + + for (const auto& hole : island.first.holes) + if (hole.contains(out)) + goto HOLE_HIT; + return std::vector{unscale(out(0), out(1), out(2))}; + } + +HOLE_HIT: + // In this case either the centroid lies in a hole, or there are multiple points + // to place. We will cover the island another way. + // For now we'll just place the points randomly not too close to the others. + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<> dis(0., 1.); + + std::vector island_new_points; + const BoundingBox& bb = get_extents(island.first); + const int refused_limit = 30; + int refused_points = 0; + while (refused_points < refused_limit) { + Point out(bb.min(0) + bb.size()(0) * dis(gen), + bb.min(1) + bb.size()(1) * dis(gen)) ; + Vec3d unscaled_out = unscale(out(0), out(1), island.second); + bool add_it = true; + + if (!island.first.contour.contains(out)) + add_it = false; + else + for (const Polygon& hole : island.first.holes) + if (hole.contains(out)) + add_it = false; + + if (add_it) { + for (const Vec3d& p : island_new_points) { + if ((p - unscaled_out).squaredNorm() < distance_limit(0)) { + add_it = false; + ++refused_points; + break; + } + } + } + if (add_it) { + out(2) = island.second; + island_new_points.emplace_back(unscaled_out); + } + } + return island_new_points; +} + +void SLAAutoSupports::project_upward_onto_mesh(std::vector& points) const +{ + Vec3f dir(0., 0., 1.); + igl::Hit hit{0, 0, 0.f, 0.f, 0.f}; + for (Vec3d& p : points) { + igl::ray_mesh_intersect(p.cast(), dir, m_V, m_F, hit); + int fid = hit.id; + Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); + p = (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))).cast(); + } +} + + } // namespace Slic3r \ No newline at end of file diff --git a/src/libslic3r/SLA/SLAAutoSupports.hpp b/src/libslic3r/SLA/SLAAutoSupports.hpp index 25d924af13..568c200d0a 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.hpp +++ b/src/libslic3r/SLA/SLAAutoSupports.hpp @@ -3,16 +3,12 @@ #include #include +#include namespace Slic3r { -class ModelObject; - -namespace SLAAutoSupports { - - class SLAAutoSupports { public: struct Config { @@ -21,24 +17,30 @@ public: float minimal_z; }; - SLAAutoSupports(ModelObject& mo, const SLAAutoSupports::Config& c); - void generate(); + SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, const std::vector& heights, const Config& config); + const std::vector& output() { return m_output; } private: + std::vector m_output; + std::vector m_normals; 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); + float distance_limit(float angle) const; + static float approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2); + std::vector> find_islands(const std::vector& slices, const std::vector& heights) const; + void sprinkle_mesh(const TriangleMesh& mesh); + std::vector uniformly_cover(const std::pair& island); + void project_upward_onto_mesh(std::vector& points) const; + + void output_expolygons(const ExPolygons& expolys, std::string filename) const; - ModelObject& m_model_object; SLAAutoSupports::Config m_config; + const Eigen::MatrixXd& m_V; + const Eigen::MatrixXi& m_F; }; -std::vector find_islands(const std::vector& slices, const std::vector& heights); - -} // namespace SLAAutoSupports - } // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index eb7c5f0d2a..a2fa3419f5 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -511,31 +511,33 @@ void SLAPrint::process() // 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, ilh](SLAPrintObject& po) { - // find islands to support - double lh = po.m_config.layer_height.getFloat(); - std::vector heights = calculate_heights(po.transformed_mesh().bounding_box(), po.get_elevation(), ilh, lh); - //SLAAutoSupports auto_supports(po.get_model_slices(), heights, *po.m_model_object); - std::vector points = SLAAutoSupports::find_islands(po.get_model_slices(), heights); - - // TODO: - - // create mesh in igl format - - // cover the islands with points, use igl to get precise z coordinate - - // sprinkle the mesh with points (SLAAutoSupports::generate()) - - - /*for (const auto& p: points) - std::cout << p(0) << " " << p(1) << " " << p(2) << std::endl; - std::cout << std::endl; - */ - //for (auto& p: points) - // p = po.trafo().inverse() * p; - + const ModelObject& mo = *po.m_model_object; po.m_supportdata.reset(new SLAPrintObject::SupportData()); po.m_supportdata->emesh = sla::to_eigenmesh(po.transformed_mesh()); - po.m_supportdata->support_points = sla::to_point_set(points); + + // If there are no points on the front-end, we will do the autoplacement. + // Otherwise we will just blindly copy the frontend data into the backend cache. + if(mo.sla_support_points.empty()) { + // calculate heights of slices (slices are calculated already) + double lh = po.m_config.layer_height.getFloat(); + std::vector heights = calculate_heights(po.transformed_mesh().bounding_box(), po.get_elevation(), ilh, lh); + + SLAAutoSupports::Config config; + const SLAPrintObjectConfig& cfg = po.config(); + config.density_at_horizontal = cfg.support_density_at_horizontal / 10000.f; + config.density_at_45 = cfg.support_density_at_45 / 10000.f; + config.minimal_z = cfg.support_minimal_z; + + // Construction of this object does the calculation. + SLAAutoSupports auto_supports(po.transformed_mesh(), po.m_supportdata->emesh, po.get_model_slices(), heights, config); + // Now let's extract the result. + const std::vector& points = auto_supports.output(); + po.m_supportdata->support_points = sla::to_point_set(points); + } + else { + // There are some points on the front-end, no calculation will be done. + po.m_supportdata->support_points = sla::to_point_set(po.transformed_support_points()); + } }; // In this step we create the supports @@ -1131,6 +1133,11 @@ const std::vector EMPTY_SLICES; const TriangleMesh EMPTY_MESH; } +const Eigen::MatrixXd& SLAPrintObject::get_support_points() const +{ + return m_supportdata->support_points; +} + const std::vector &SLAPrintObject::get_support_slices() const { // assert(is_step_done(slaposSliceSupports)); diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 3c05b49324..3a4dd846ec 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -90,6 +90,9 @@ public: const std::vector& get_model_slices() const; const std::vector& get_support_slices() const; + // This method returns the support points of this SLAPrintObject. + const Eigen::MatrixXd& get_support_points() const; + // An index record referencing the slices // (get_model_slices(), get_support_slices()) where the keys are the height // levels of the model in scaled-clipper coordinates. The levels correspond diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 0cd6cb9b9d..4666fcd059 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -1145,6 +1145,7 @@ private: static std::vector _parse_colors(const std::vector& colors); +public: const Print* fff_print() const; const SLAPrint* sla_print() const; }; diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index 2ed7fa1816..69dbdddcfa 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -8,6 +8,7 @@ #include "libslic3r/Geometry.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/SLA/SLASupportTree.hpp" +#include "libslic3r/SLAPrint.hpp" #include #include @@ -1786,6 +1787,18 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const G { if (is_mesh_update_necessary()) update_mesh(); + + // If there are no points, let's ask the backend if it calculated some. + if (model_object->sla_support_points.empty() && m_parent.sla_print()->is_step_done(slaposSupportPoints)) { + for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { + if (po->model_object()->id() == model_object->id()) { + const Eigen::MatrixXd& points = po->get_support_points(); + for (unsigned int i=0; isla_support_points.push_back(Vec3f(po->trafo().inverse().cast() * Vec3f(points(i,0), points(i,1), points(i,2)))); + break; + } + } + } } } #else @@ -2170,6 +2183,9 @@ void GLGizmoSlaSupports::render_tooltip_texture() const { #if ENABLE_IMGUI void GLGizmoSlaSupports::on_render_input_window(float x, float y, const GLCanvas3D::Selection& selection) { + bool first_run = true; // This is a hack to redraw the button when all points are removed, + // so it is not delayed until the background process finishes. +RENDER_AGAIN: m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); m_imgui->set_next_window_bg_alpha(0.5f); m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); @@ -2184,22 +2200,11 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, const GLCanvas m_imgui->end(); - if (remove_all_clicked) + if (remove_all_clicked) { delete_current_grabber(true); - - if (generate) { - const DynamicPrintConfig& cfg = *wxGetApp().get_tab(Preset::TYPE_SLA_PRINT)->get_config(); - SLAAutoSupports::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::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 (first_run) { + first_run = false; + goto RENDER_AGAIN; } }