diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 46d81c2367..d8c8d1e069 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -444,8 +444,6 @@ set(SLIC3R_SOURCES SLA/SupportPoint.hpp SLA/SupportPointGenerator.hpp SLA/SupportPointGenerator.cpp - SLA/SupportPointGeneratorNew.hpp - SLA/SupportPointGeneratorNew.cpp SLA/Clustering.hpp SLA/Clustering.cpp SLA/ReprojectPointsOnMesh.hpp diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 5c608067cb..6ed02590f9 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1420,20 +1420,21 @@ namespace Slic3r { if (version == 0) { for (unsigned int i=0; isla_support_points.push_back(sla::SupportPoint(point)); + m_object->sla_support_points.push_back(sla::SupportPoint{Vec3f(point[0], point[1], point[2]), point[3]}); coord_idx = 0; } if (end == nullptr) diff --git a/src/libslic3r/SLA/SupportPoint.hpp b/src/libslic3r/SLA/SupportPoint.hpp index 66ed674028..53e965c382 100644 --- a/src/libslic3r/SLA/SupportPoint.hpp +++ b/src/libslic3r/SLA/SupportPoint.hpp @@ -7,11 +7,7 @@ #include -namespace Slic3r { - -class ModelObject; - -namespace sla { +namespace Slic3r::sla { // An enum to keep track of where the current points on the ModelObject came from. enum class PointsStatus { @@ -21,58 +17,47 @@ enum class PointsStatus { UserModified // User has done some edits. }; -struct SupportPoint -{ - Vec3f pos; - float head_front_radius; - bool is_new_island; - - 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 = false) - : 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 = false) - : 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) - {} - - bool operator==(const SupportPoint &sp) const - { - float rdiff = std::abs(head_front_radius - sp.head_front_radius); - return (pos == sp.pos) && rdiff < float(EPSILON) && - 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); - } +// Reason of automatic support placement usage +enum class SupportPointType { + manual_add, + island, // no move, island should be grouped + slope, + thin, + stability, + edge }; +/// +/// Stereolithography(SLA) support point +/// +struct SupportPoint +{ + // Position on model surface + Vec3f pos = Vec3f::Zero(); // [in mm] + + // radius of the touching interface + // Also define force it must keep + float head_front_radius = 0.f; // [in mm] + + // type + SupportPointType type{SupportPointType::manual_add}; + + bool is_island() const { return type == SupportPointType::island; } + template void serialize(Archive &ar){ + ar(pos, head_front_radius, type); + } + + // unsaved changes + cache invalidation + bool operator==(const SupportPoint &sp) const { + float rdiff = std::abs(head_front_radius - sp.head_front_radius); + return (pos == sp.pos) && rdiff < float(EPSILON) && type == sp.type; + } + + bool operator!=(const SupportPoint &sp) const { return !(sp == (*this)); } + +}; using SupportPoints = std::vector; -SupportPoints transformed_support_points(const ModelObject &mo, - const Transform3d &trafo); - -}} // namespace Slic3r::sla +} // namespace Slic3r::sla #endif // SUPPORTPOINT_HPP diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 552c261a54..4f70ebedb1 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -1,686 +1,436 @@ -///|/ Copyright (c) Prusa Research 2019 - 2023 Tomáš Mészáros @tamasmeszaros, Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena +///|/ Copyright (c) Prusa Research 2024 Filip Sykala @Jony01 ///|/ ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher ///|/ -#include -#include -#include -#include -#include -#include #include "SupportPointGenerator.hpp" -#include "libslic3r/Execution/ExecutionTBB.hpp" -#include "libslic3r/Geometry/ConvexHull.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/Point.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/Tesselate.hpp" -#include "libslic3r/MinAreaBoundingBox.hpp" -#include "libslic3r/libslic3r.h" -#include "libslic3r/AABBMesh.hpp" + +#include // point grid + +#include "libslic3r/Execution/ExecutionTBB.hpp" // parallel preparation of data for sampling #include "libslic3r/Execution/Execution.hpp" -#include "libslic3r/SLA/SupportPoint.hpp" -#include "libslic3r/BoundingBox.hpp" -namespace Slic3r { -namespace sla { +using namespace Slic3r; +using namespace Slic3r::sla; -/*float SupportPointGenerator::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2) +namespace { +/// +/// Struct to store support points in 2d grid to faster search for nearest support points +/// +class Grid2D { - n1.normalize(); - n2.normalize(); + coord_t m_cell_size; // Squar: x and y are same + coord_t m_cell_size_half; - Vec3d v = (p2-p1); - v.normalize(); + using Key = Point; + struct GridHash{std::size_t operator()(const Key &cell_id) const { + return std::hash()(cell_id.x()) ^ std::hash()(cell_id.y() * 593); + }}; + using Grid = std::unordered_multimap; + Grid m_grid; - 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; +public: + /// + /// Set cell size for grid + /// + /// Granularity of stored points + /// Must be bigger than maximal used radius + explicit Grid2D(const coord_t &cell_size) + : m_cell_size(cell_size), m_cell_size_half(cell_size / 2) {} + + Key cell_id(const Point &point) const { + return Key(point.x() / m_cell_size, point.y() / m_cell_size); + } + + void add(LayerSupportPoint &&point) { + m_grid.emplace(cell_id(point.position_on_layer), std::move(point)); + } + + using CheckFnc = std::function; + bool exist_true_in_4cell_neighbor(const Point &pos, const CheckFnc& fnc) const { + Key key = cell_id(pos); + if (exist_true_for_cell(key, pos, fnc)) return true; + Point un_cell_pos( + key.x() * m_cell_size + m_cell_size_half, + key.y() * m_cell_size + m_cell_size_half ); + Key key2( + (un_cell_pos.x() > pos.x()) ? key.x() + 1 : key.x() - 1, + (un_cell_pos.y() > pos.y()) ? key.y() + 1 : key.y() - 1); + if (exist_true_for_cell(key2, pos, fnc)) return true; + if (exist_true_for_cell({key.x(), key2.y()}, pos, fnc)) return true; + if (exist_true_for_cell({key2.x(), key.y()}, pos, fnc)) return true; + return false; + } + + void merge(Grid2D &&grid) { + // support to merge only grid with same size + assert(m_cell_size == grid.m_cell_size); + m_grid.merge(std::move(grid.m_grid)); + } + + LayerSupportPoints get_points() const { + LayerSupportPoints result; + result.reserve(m_grid.size()); + for (const auto& [key, support] : m_grid) + result.push_back(support); + return result; + } +private: + bool exist_true_for_cell(const Key &key, const Point &pos, const CheckFnc& fnc) const{ + auto [begin_it, end_it] = m_grid.equal_range(key); + for (Grid::const_iterator it = begin_it; it != end_it; ++it) { + const LayerSupportPoint &support_point = it->second; + if (fnc(support_point, pos)) + return true; + } + return false; + } +}; + +/// +/// Intersection of line segment and circle +/// +/// Line segment point A, Point lay inside circle +/// Line segment point B, Point lay outside or on circle +/// Circle center point +/// squared value of Circle Radius (r2 = r*r) +/// Intersection point +Point intersection(const Point &p1, const Point &p2, const Point &cnt, double r2) { + // Vector from p1 to p2 + Vec2d dp_d((p2 - p1).cast()); + // Vector from circle center to p1 + Vec2d f_d((p1 - cnt).cast()); + + double a = dp_d.squaredNorm(); + double b = 2 * (f_d.x() * dp_d.x() + f_d.y() * dp_d.y()); + double c = f_d.squaredNorm() - r2; + + // Discriminant of the quadratic equation + double discriminant = b * b - 4 * a * c; + + // No intersection if discriminant is negative + assert(discriminant > 0); + if (discriminant < 0) + return {}; // No intersection + + // Calculate the two possible values of t (parametric parameter) + discriminant = sqrt(discriminant); + double t1 = (-b - discriminant) / (2 * a); + + // Check for valid intersection points within the line segment + if (t1 >= 0 && t1 <= 1) { + return {p1.x() + t1 * dp_d.x(), p1.y() + t1 * dp_d.y()}; + } + + // should not be in use + double t2 = (-b + discriminant) / (2 * a); + if (t2 >= 0 && t2 <= 1 && t1 != t2) { + return {p1.x() + t2 * dp_d.x(), p1.y() + t2 * dp_d.y()}; + } + return {}; } +/// +/// Uniformly sample Polygon, +/// Use first point and each next point is first crosing radius from last added +/// +/// Polygon to sample +/// Squared distance for sampling +/// Uniformly distributed points laying on input polygon +/// with exception of first and last point(they are closer than dist2) +Slic3r::Points sample(const Polygon &p, double dist2) { + if (p.empty()) + return {}; -float SupportPointGenerator::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.f * float(acos(m_config.density_at_45/m_config.density_at_horizontal) / M_PI); - return std::max(0.f, float(m_config.density_at_horizontal * cos(K*angle))); + Slic3r::Points r; + r.push_back(p.front()); + const Point *prev_pt = nullptr; + for (size_t prev_i = 0; prev_i < p.size(); prev_i++) { + size_t curr_i = (prev_i != p.size() - 1) ? prev_i + 1 : 0; + const Point &pt = p.points[curr_i]; + double p_dist2 = (r.back() - pt).cast().squaredNorm(); + while (p_dist2 > dist2) { // line segment goes out of radius + if (prev_pt == nullptr) + prev_pt = &p.points[prev_i]; + r.push_back(intersection(*prev_pt, pt, r.back(), dist2)); + p_dist2 = (r.back() - pt).cast().squaredNorm(); + prev_pt = &r.back(); + } + prev_pt = nullptr; + } + return r; } -float SupportPointGenerator::distance_limit(float angle) const -{ - return 1./(2.4*get_required_density(angle)); -}*/ - -SupportPointGenerator::SupportPointGenerator( - const AABBMesh &emesh, - const std::vector &slices, - const std::vector & heights, - const Config & config, - std::function throw_on_cancel, - std::function statusfn) - : SupportPointGenerator(emesh, config, throw_on_cancel, statusfn) -{ - std::random_device rd; - m_rng.seed(rd()); - execute(slices, heights); +coord_t get_supported_radius(const LayerSupportPoint &p, float z_distance, const SupportPointGeneratorConfig &config +) { + // TODO: calculate support radius + return scale_(5.); } -SupportPointGenerator::SupportPointGenerator( - const AABBMesh &emesh, - const SupportPointGenerator::Config &config, - std::function throw_on_cancel, - std::function statusfn) - : m_config(config) - , m_emesh(emesh) - , m_throw_on_cancel(throw_on_cancel) - , m_statusfn(statusfn) -{ +void sample_part( + const LayerPart &part, + size_t layer_id, + const SupportPointGeneratorData &data, + const SupportPointGeneratorConfig &config, + std::vector &grids, + std::vector &prev_grids +) { + // NOTE: first layer do not have prev part + assert(layer_id != 0); + + const Layers &layers = data.layers; + const LayerParts &prev_layer_parts = layers[layer_id - 1].parts; + const LayerParts::const_iterator &prev_part_it = part.prev_parts.front().part_it; + size_t index_of_prev_part = prev_part_it - prev_layer_parts.begin(); + if (prev_part_it->next_parts.size() == 1) { + grids.push_back(std::move(prev_grids[index_of_prev_part])); + } else { // Need a copy there are multiple parts above previus one + grids.push_back(prev_grids[index_of_prev_part]); // copy + } + // current part grid + Grid2D &part_grid = grids.back(); + + // merge other grid in case of multiple previous parts + for (size_t i = 1; i < part.prev_parts.size(); ++i) { + const LayerParts::const_iterator &prev_part_it = part.prev_parts[i].part_it; + size_t index_of_prev_part = prev_part_it - prev_layer_parts.begin(); + if (prev_part_it->next_parts.size() == 1) { + part_grid.merge(std::move(prev_grids[index_of_prev_part])); + } else { // Need a copy there are multiple parts above previus one + Grid2D grid_ = prev_grids[index_of_prev_part]; // copy + part_grid.merge(std::move(grid_)); + } + } + + float part_z = data.heights[layer_id]; + Grid2D::CheckFnc is_supported = [part_z, &config] + (const LayerSupportPoint &support_point, const Point &p) -> bool { + float diff_height = part_z - support_point.pos.z(); + coord_t r_ = get_supported_radius(support_point, diff_height, config); + Point dp = support_point.position_on_layer - p; + if (std::abs(dp.x()) > r_) return false; + if (std::abs(dp.y()) > r_) return false; + double r2 = static_cast(r_); + r2 *= r2; + return dp.cast().squaredNorm() < r2; + }; + + // check distance to nearest support points from grid + float maximal_radius = scale_(5.f); + for (const Point &p : part.samples) { + if (!part_grid.exist_true_in_4cell_neighbor(p, is_supported)) { + // not supported sample, soo create new support point + part_grid.add(LayerSupportPoint{ + SupportPoint{ + Vec3f{unscale(p.x()), unscale(p.y()), part_z}, + /* head_front_radius */ 0.4f, + SupportPointType::slope + }, + /* position_on_layer */ p, + /* direction_to_mass */ Point(1,0) + }); + } + } } -void SupportPointGenerator::execute(const std::vector &slices, - const std::vector & heights) -{ - process(slices, heights); - project_onto_mesh(m_output); +Points uniformly_sample(const ExPolygon &island, const SupportPointGeneratorConfig &cfg) { + // TODO: Implement it + return Points{island.contour.centroid()}; } -void SupportPointGenerator::project_onto_mesh(std::vector& points) const -{ - // The function makes sure that all the points are really exactly placed on the mesh. +Grid2D support_island(const LayerPart &part, float part_z, const SupportPointGeneratorConfig &cfg) { + // Maximal radius of supported area of one support point + double max_support_radius = 10.; // cfg.cell_size; - // Use a reasonable granularity to account for the worker thread synchronization cost. - static constexpr size_t gransize = 64; + // maximal radius of support + coord_t cell_size = scale_(max_support_radius); - execution::for_each(ex_tbb, size_t(0), points.size(), [this, &points](size_t idx) - { - if ((idx % 16) == 0) - // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. - m_throw_on_cancel(); - - Vec3f& p = points[idx].pos; - // Project the point upward and downward and choose the closer intersection with the mesh. - AABBMesh::hit_result hit_up = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., 1.)); - AABBMesh::hit_result hit_down = m_emesh.query_ray_hit(p.cast(), Vec3d(0., 0., -1.)); - - bool up = hit_up.is_hit(); - bool down = hit_down.is_hit(); - - if (!up && !down) - return; - - AABBMesh::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down; - p = p + (hit.distance() * hit.direction()).cast(); - }, gransize); + Grid2D part_grid(cell_size); + Points pts = uniformly_sample(*part.shape, cfg); + for (const Point &pt : pts) + part_grid.add(LayerSupportPoint{ + SupportPoint{ + Vec3f{unscale(pt.x()), unscale(pt.y()), part_z}, + /* head_front_radius */ 0.4f, + SupportPointType::island + }, + /* position_on_layer */ pt, + /* direction_to_mass */ Point(0,0) // direction from bottom + }); + return part_grid; } -static std::vector make_layers( - const std::vector& slices, const std::vector& heights, - std::function throw_on_cancel, const sla::SupportPointGenerator::Config& config) -{ +}; + +SupportPointGeneratorData Slic3r::sla::prepare_generator_data( + std::vector &&slices, + std::vector &&heights, + ThrowOnCancel throw_on_cancel, + StatusFunction statusfn +) { + // check input + assert(!slices.empty()); assert(slices.size() == heights.size()); + if (slices.empty() || slices.size() != heights.size()) + return SupportPointGeneratorData{}; + + // Move input into result + SupportPointGeneratorData result; + result.slices = std::move(slices); + result.heights = std::move(heights); // Allocate empty layers. - std::vector layers; - layers.reserve(slices.size()); - for (size_t i = 0; i < slices.size(); ++ i) - layers.emplace_back(i, heights[i]); + result.layers = Layers(result.slices.size(), {}); - execution::for_each(ex_tbb, size_t(0), layers.size(), - [&layers, &slices, min_area = config.minimal_island_area, throw_on_cancel](size_t layer_id) - { + // Generate Extents and SampleLayers + execution::for_each(ex_tbb, size_t(0), result.slices.size(), + [&result, throw_on_cancel](size_t layer_id) { 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(); - SupportPointGenerator::MyLayer &layer = layers[layer_id]; - const ExPolygons & islands = slices[layer_id]; - layer.islands.reserve(islands.size()); - for (const ExPolygon &island : islands) { - float area = float(island.area() * SCALING_FACTOR * SCALING_FACTOR); + const double sample_distance_in_mm = scale_(2); + const double sample_distance_in_mm2 = sample_distance_in_mm * sample_distance_in_mm; - // Skip too small islands (non-printable) - if (area < min_area) - continue; - - layer.islands.emplace_back(layer, island, get_extents(island.contour), area); - } + Layer &layer = result.layers[layer_id]; + const ExPolygons &islands = result.slices[layer_id]; + layer.parts.reserve(islands.size()); + for (const ExPolygon &island : islands) + layer.parts.push_back(LayerPart{ + &island, + get_extents(island.contour), + sample(island.contour, sample_distance_in_mm2) + }); + }, 32 /*gransize*/); - // Calculate overlap of successive layers. Link overlapping islands. - execution::for_each(ex_tbb, size_t(1), layers.size(), - [&layers, &heights, throw_on_cancel] (size_t 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(); - SupportPointGenerator::MyLayer &layer_above = layers[layer_id]; - SupportPointGenerator::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 = 35.f * (float(M_PI)/180.f); // smaller number - less supports - const float between_layers_offset = scaled(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 = scaled(layer_height * std::tan(slope_angle)); - //FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands. - for (SupportPointGenerator::Structure &top : layer_above.islands) { - for (SupportPointGenerator::Structure &bottom : layer_below.islands) { - float overlap_area = top.overlap_area(bottom); - if (overlap_area > 0) { - top.islands_below.emplace_back(&bottom, overlap_area); - bottom.islands_above.emplace_back(&top, overlap_area); - } - } - if (! top.islands_below.empty()) { - // Why only polygons?? (some sort of speed up?) - Polygons bottom_polygons = top.polygons_below(); - top.overhangs = diff_ex(*top.polygon, bottom_polygons); - if (! top.overhangs.empty()) { + // Link parts by intersections + execution::for_each(ex_tbb, size_t(1), result.slices.size(), + [&result, throw_on_cancel] (size_t 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(); - // Produce 2 bands around the island, a safe band for dangling overhangs - // and an unsafe band for sloped overhangs. - // These masks include the original island - auto dangl_mask = expand(bottom_polygons, between_layers_offset, ClipperLib::jtSquare); - auto overh_mask = expand(bottom_polygons, slope_offset, ClipperLib::jtSquare); + LayerParts &parts_above = result.layers[layer_id].parts; + LayerParts &parts_below = result.layers[layer_id-1].parts; + for (auto it_above = parts_above.begin(); it_above < parts_above.end(); ++it_above) { + for (auto it_below = parts_below.begin(); it_below < parts_below.end(); ++it_below) { + // Improve: do some sort of parts + skip some of them + if (!it_above->shape_extent.overlap(it_below->shape_extent)) + continue; // no bounding box overlap - // Absolutely hopeless overhangs are those outside the unsafe band - top.overhangs = diff_ex(*top.polygon, overh_mask); + // Improve: test could be done faster way + Polygons polys = intersection(*it_above->shape, *it_below->shape); + if (polys.empty()) + continue; // no intersection - // Now cut out the supported core from the safe band - // and cut the safe band from the unsafe band to get distinct - // zones. - overh_mask = diff(overh_mask, dangl_mask); - dangl_mask = diff(dangl_mask, bottom_polygons); + // TODO: check minimal intersection! - top.dangling_areas = intersection_ex(*top.polygon, dangl_mask); - top.overhangs_slopes = intersection_ex(*top.polygon, overh_mask); - - top.overhangs_area = 0.f; - - // Sort overhangs by area - std::vector> expolys_with_areas; - for (ExPolygon &ex : top.overhangs) { - float area = float(ex.area()); - expolys_with_areas.emplace_back(&ex, area); - top.overhangs_area += area; - } - 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_area *= float(SCALING_FACTOR * SCALING_FACTOR); - } - } - } + it_above->prev_parts.emplace_back(PartLink{it_below}); + it_below->next_parts.emplace_back(PartLink{it_above}); + } + } }, 8 /* gransize */); - - return layers; + return result; } -void SupportPointGenerator::process(const std::vector& slices, const std::vector& heights) -{ -#ifdef SLA_SUPPORTPOINTGEN_DEBUG - std::vector> islands; -#endif /* SLA_SUPPORTPOINTGEN_DEBUG */ +LayerSupportPoints Slic3r::sla::generate_support_points( + const SupportPointGeneratorData &data, + const SupportPointGeneratorConfig &config, + ThrowOnCancel throw_on_cancel, + StatusFunction statusfn +){ + const Layers &layers = data.layers; + double increment = 100.0 / static_cast(layers.size()); + double status = 0; // current progress + int status_int = 0; - std::vector layers = make_layers(slices, heights, m_throw_on_cancel, m_config); + LayerSupportPoints result; + std::vector prev_grids; // same count as previous layer item size + for (size_t layer_id = 0; layer_id < layers.size(); ++layer_id) { + const Layer &layer = layers[layer_id]; - PointGrid3D point_grid; - point_grid.cell_size = Vec3f(10.f, 10.f, 10.f); - - double increment = 100.0 / layers.size(); - double status = 0; - - std::vector support_force_bottom; - for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) { - bool is_first_layer = layer_id == 0; - SupportPointGenerator::MyLayer *layer_top = &layers[layer_id]; - SupportPointGenerator::MyLayer *layer_bottom = (!is_first_layer) ? &layers[layer_id - 1] : nullptr; - if (!is_first_layer) { - support_force_bottom.resize(layer_bottom->islands.size()); - for (size_t i = 0; i < layer_bottom->islands.size(); ++i) - support_force_bottom[i] = layer_bottom->islands[i].supports_force_total(); - } - for (const Structure &top : layer_top->islands) - for (const Structure::Link &bottom_link : top.islands_below) { - const 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)); - size_t bottom_island_index = &bottom - layer_bottom->islands.data(); - float &support_force = support_force_bottom[bottom_island_index]; - //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. -// 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); - } - // Let's assign proper support force to each of them: - if (!is_first_layer) { - 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; - } - } - // Now iterate over all polygons and append new points if needed. - for (Structure &s : layer_top->islands) { - // Penalization resulting from large diff from the last layer: - s.supports_force_inherited /= std::max(1.f, 0.17f * s.overhangs_area / s.area); - - add_support_points(s, point_grid); - } - - m_throw_on_cancel(); - - status += increment; - m_statusfn(int(std::round(status))); - } -} - -void SupportPointGenerator::add_support_points(SupportPointGenerator::Structure &s, SupportPointGenerator::PointGrid3D &grid3d) -{ - // Select each type of surface (overrhang, dangling, slope), derive the support - // force deficit for it and call uniformly conver with the right params - if (s.islands_below.empty()) { - // completely new island - needs support no doubt - // deficit is full, there is nothing below that would hold this island - uniformly_cover({ *s.polygon }, s, s.area, grid3d, IslandCoverageFlags(icfIsNew | icfWithBoundary) ); - return; - } - - if (! s.overhangs.empty()) { - uniformly_cover(s.overhangs, s, s.overhangs_area, grid3d); - } - - auto areafn = [](double sum, auto &p) { return sum + p.area() * SCALING_FACTOR * SCALING_FACTOR; }; - - float current = s.supports_force_total(); - if (! s.dangling_areas.empty()) { - // Let's see if there's anything that overlaps enough to need supports: - // What we now have in polygons needs support, regardless of what the forces are, so we can add them. - - // Before Tamas changes - // a * tp - current * .09f *std::sqrt(1. - a / s.area) - - // just befor - // a * ( 1 - current * s.area); - - double sum_of_dangling_area = std::accumulate(s.dangling_areas.begin(), s.dangling_areas.end(), 0., areafn); - double dangling_ratio = sum_of_dangling_area / s.area; - float deficit = current * .09f * dangling_ratio; - //uniformly_cover(s.dangling_areas, s, deficit, grid3d, icfWithBoundary); - } - - current = s.supports_force_total(); - if (! s.overhangs_slopes.empty()) { - double sum_of_overhang_area = std::accumulate(s.overhangs_slopes.begin(), s.overhangs_slopes.end(), 0., areafn); - double overhang_ratio = sum_of_overhang_area / s.area; - float deficit = current * .0015f * overhang_ratio; - //uniformly_cover(s.overhangs_slopes, s, deficit, grid3d, icfWithBoundary); - } -} - -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); - - std::vector out; - if (! triangles.empty()) - { - // Calculate area of each triangle. - auto areas = reserve_vector(triangles.size() / 3); - double aback = 0.; - for (size_t i = 0; i < triangles.size(); ) { - const Vec2f &a = triangles[i ++]; - const Vec2f v1 = triangles[i ++] - a; - const Vec2f v2 = triangles[i ++] - a; - - // Prefix sum of the areas. - areas.emplace_back(aback + 0.5f * std::abs(cross2(v1, v2))); - aback = areas.back(); - } - - size_t num_samples = size_t(ceil(areas.back() * samples_per_mm2)); - std::uniform_real_distribution<> random_triangle(0., double(areas.back())); - std::uniform_real_distribution<> random_float(0., 1.); - for (size_t i = 0; i < num_samples; ++ i) { - double r = random_triangle(rng); - size_t idx_triangle = std::min(std::upper_bound(areas.begin(), areas.end(), (float)r) - areas.begin(), areas.size() - 1) * 3; - // Select a random point on the triangle. - const Vec2f &a = triangles[idx_triangle ++]; - const Vec2f &b = triangles[idx_triangle++]; - const Vec2f &c = triangles[idx_triangle]; -#if 1 - // https://www.cs.princeton.edu/~funk/tog02.pdf - // page 814, formula 1. - double u = float(std::sqrt(random_float(rng))); - double v = float(random_float(rng)); - out.emplace_back(a * (1.f - u) + b * (u * (1.f - v)) + c * (v * u)); -#else - // Greg Turk, Graphics Gems - // https://devsplorer.wordpress.com/2019/08/07/find-a-random-point-on-a-plane-using-barycentric-coordinates-in-unity/ - double u = float(random_float(rng)); - double v = float(random_float(rng)); - if (u + v >= 1.f) { - u = 1.f - u; - v = 1.f - v; - } - out.emplace_back(a + u * (b - a) + v * (c - a)); -#endif - } - } - return out; -} - - -std::vector sample_expolygon(const ExPolygons &expolys, float samples_per_mm2, std::mt19937 &rng) -{ - std::vector out; - for (const ExPolygon &expoly : expolys) - append(out, sample_expolygon(expoly, samples_per_mm2, rng)); - - return out; -} - -void sample_expolygon_boundary(const ExPolygon & expoly, - float samples_per_mm, - std::vector &out, - std::mt19937 & /*rng*/) -{ - double point_stepping_scaled = scale_(1.f) / samples_per_mm; - for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) { - const Polygon &contour = (i_contour == 0) ? expoly.contour : - expoly.holes[i_contour - 1]; - - const Points pts = contour.equally_spaced_points(point_stepping_scaled); - for (size_t i = 0; i < pts.size(); ++ i) - out.emplace_back(unscale(pts[i].x()), - unscale(pts[i].y())); - } -} - -std::vector sample_expolygon_with_boundary(const ExPolygon &expoly, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng) -{ - std::vector out = sample_expolygon(expoly, samples_per_mm2, rng); - sample_expolygon_boundary(expoly, samples_per_mm_boundary, out, rng); - return out; -} - -std::vector sample_expolygon_with_boundary(const ExPolygons &expolys, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng) -{ - std::vector out; - for (const ExPolygon &expoly : expolys) - append(out, sample_expolygon_with_boundary(expoly, samples_per_mm2, samples_per_mm_boundary, rng)); - return out; -} - -template -static inline std::vector poisson_disk_from_samples(const std::vector &raw_samples, float radius, REFUSE_FUNCTION refuse_function) -{ - Vec2f corner_min(std::numeric_limits::max(), std::numeric_limits::max()); - for (const Vec2f &pt : raw_samples) { - corner_min.x() = std::min(corner_min.x(), pt.x()); - corner_min.y() = std::min(corner_min.y(), pt.y()); - } - - // Assign the raw samples to grid cells, sort the grid cells lexicographically. - struct RawSample - { - Vec2f coord; - Vec2i cell_id; - RawSample(const Vec2f &crd = {}, const Vec2i &id = {}): coord{crd}, cell_id{id} {} - }; - - auto raw_samples_sorted = reserve_vector(raw_samples.size()); - for (const Vec2f &pt : raw_samples) - raw_samples_sorted.emplace_back(pt, ((pt - corner_min) / radius).cast()); - - 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 { - // Resulting output sample points for this cell: - enum { - max_positions = 4 - }; - Vec2f poisson_samples[max_positions]; - int num_poisson_samples = 0; - - // Index into raw_samples: - int first_sample_idx; - int sample_cnt; - }; - - struct CellIDHash { - std::size_t operator()(const Vec2i &cell_id) const { - return std::hash()(cell_id.x()) ^ std::hash()(cell_id.y() * 593); - } - }; - - // Map from cell IDs to hash_data. Each hash_data points to the range in raw_samples corresponding to that cell. - // (We could just store the samples in hash_data. This implementation is an artifact of the reference paper, which - // is optimizing for GPU acceleration that we haven't implemented currently.) - typedef std::unordered_map Cells; - Cells cells; - { - typename Cells::iterator last_cell_id_it; - Vec2i last_cell_id(-1, -1); - for (size_t i = 0; i < raw_samples_sorted.size(); ++ i) { - const RawSample &sample = raw_samples_sorted[i]; - if (sample.cell_id == last_cell_id) { - // This sample is in the same cell as the previous, so just increase the count. Cells are - // always contiguous, since we've sorted raw_samples_sorted by cell ID. - ++ last_cell_id_it->second.sample_cnt; + std::vector grids; + grids.reserve(layer.parts.size()); + + for (const LayerPart &part : layer.parts) { + if (part.prev_parts.empty()) { + // new island - needs support no doubt + float part_z = data.heights[layer_id]; + grids.push_back(support_island(part, part_z, config)); } else { - // This is a new cell. - PoissonDiskGridEntry data; - data.first_sample_idx = int(i); - data.sample_cnt = 1; - auto result = cells.insert({sample.cell_id, data}); - last_cell_id = sample.cell_id; - last_cell_id_it = result.first; + sample_part(part, layer_id, data, config, grids, prev_grids); + } + + // collect result from grid of top part + if (part.next_parts.empty()) { + const Grid2D &part_grid = grids.back(); + LayerSupportPoints sps = part_grid.get_points(); + result.insert(result.end(), + std::make_move_iterator(sps.begin()), + std::make_move_iterator(sps.end())); } } - } + prev_grids = std::move(grids); - const int max_trials = 5; - const float radius_squared = radius * radius; - for (int trial = 0; trial < max_trials; ++ trial) { - // Create sample points for each entry in cells. - for (auto &it : cells) { - const Vec2i &cell_id = it.first; - PoissonDiskGridEntry &cell_data = it.second; - // This cell's raw sample points start at first_sample_idx. On trial 0, try the first one. On trial 1, try first_sample_idx + 1. - int next_sample_idx = cell_data.first_sample_idx + trial; - if (trial >= cell_data.sample_cnt) - // There are no more points to try for this cell. - continue; - const RawSample &candidate = raw_samples_sorted[next_sample_idx]; - // See if this point conflicts with any other points in this cell, or with any points in - // neighboring cells. Note that it's possible to have more than one point in the same cell. - bool conflict = refuse_function(candidate.coord); - for (int i = -1; i < 2 && ! conflict; ++ i) { - for (int j = -1; j < 2; ++ j) { - const auto &it_neighbor = cells.find(cell_id + Vec2i(i, j)); - if (it_neighbor != cells.end()) { - const PoissonDiskGridEntry &neighbor = it_neighbor->second; - for (int i_sample = 0; i_sample < neighbor.num_poisson_samples; ++ i_sample) - if ((neighbor.poisson_samples[i_sample] - candidate.coord).squaredNorm() < radius_squared) { - conflict = true; - break; - } - } - } - } - if (! conflict) { - // Store the new sample. - assert(cell_data.num_poisson_samples < cell_data.max_positions); - if (cell_data.num_poisson_samples < cell_data.max_positions) - cell_data.poisson_samples[cell_data.num_poisson_samples ++] = candidate.coord; - } - } - } + throw_on_cancel(); - // Copy the results to the output. - std::vector out; - for (const auto& it : cells) - for (int i = 0; i < it.second.num_poisson_samples; ++ i) - out.emplace_back(it.second.poisson_samples[i]); - return out; + int old_status_int = status_int; + status += increment; + status_int = static_cast(std::round(status)); + if (old_status_int < status_int) + statusfn(status_int); + } + return result; } +// TODO: Should be in another file +#include "libslic3r/AABBMesh.hpp" +SupportPoints Slic3r::sla::move_on_mesh_surface( + const LayerSupportPoints &points, + const AABBMesh &mesh, + double allowed_move, + ThrowOnCancel throw_on_cancel +) { + SupportPoints pts; + pts.reserve(points.size()); + for (const LayerSupportPoint &p : points) + pts.push_back(static_cast(p)); -void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure& structure, float deficit, PointGrid3D &grid3d, IslandCoverageFlags flags) -{ - //int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force)); - - float support_force_deficit = deficit; -// auto bb = get_extents(islands); - - if (flags & icfIsNew) { - auto chull = Geometry::convex_hull(islands); - auto rotbox = MinAreaBoundigBox{chull, MinAreaBoundigBox::pcConvex}; - Vec2d bbdim = {unscaled(rotbox.width()), unscaled(rotbox.height())}; - - if (bbdim.x() > bbdim.y()) std::swap(bbdim.x(), bbdim.y()); - double aspectr = bbdim.y() / bbdim.x(); - - support_force_deficit *= (1 + aspectr / 2.); - } - - if (support_force_deficit < 0) - return; - - // Number of newly added points. - const size_t poisson_samples_target = size_t(ceil(support_force_deficit / m_config.support_force())); - - const float density_horizontal = 1. / m_config.support_force(); - //FIXME why? - float poisson_radius = std::max(m_config.minimal_distance, 1.f / (5.f * density_horizontal)); -// const float poisson_radius = 1.f / (15.f * density_horizontal); - const float samples_per_mm2 = 30.f / (float(M_PI) * poisson_radius * poisson_radius); - // Minimum distance between samples, in 3D space. -// float min_spacing = poisson_radius / 3.f; - float min_spacing = poisson_radius; - - //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::vector raw_samples = - flags & icfWithBoundary ? - sample_expolygon_with_boundary(islands, samples_per_mm2, - 5.f / poisson_radius, m_rng) : - sample_expolygon(islands, samples_per_mm2, m_rng); - - std::vector poisson_samples; - for (size_t iter = 0; iter < 4; ++ iter) { - poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius, - [&structure, &grid3d, min_spacing](const Vec2f &pos) { - return grid3d.collides_with(pos, structure.layer->print_z, min_spacing); - }); - if (poisson_samples.size() >= poisson_samples_target || m_config.minimal_distance > poisson_radius-EPSILON) - break; - float coeff = 0.5f; - if (poisson_samples.size() * 2 > poisson_samples_target) - coeff = float(poisson_samples.size()) / float(poisson_samples_target); - poisson_radius = std::max(m_config.minimal_distance, poisson_radius * coeff); - min_spacing = std::max(m_config.minimal_distance, min_spacing * coeff); - } - -#ifdef SLA_SUPPORTPOINTGEN_DEBUG + // The function makes sure that all the points are really exactly placed on the mesh. + execution::for_each(ex_tbb, size_t(0), pts.size(), [&pts, &mesh, &throw_on_cancel, allowed_move](size_t idx) { - 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"); - } -#endif /* NDEBUG */ + if ((idx % 16) == 0) + // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. + throw_on_cancel(); -// assert(! poisson_samples.empty()); - if (poisson_samples_target < poisson_samples.size()) { - std::shuffle(poisson_samples.begin(), poisson_samples.end(), m_rng); - poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end()); - } - for (const Vec2f &pt : poisson_samples) { - m_output.emplace_back( - float(pt(0)), float(pt(1)), structure.layer->print_z/*structure.zlevel*/, m_config.head_diameter / 2.f, - flags & icfIsNew - ); - structure.supports_force_this_layer += m_config.support_force(); - grid3d.insert(pt, &structure); - } + Vec3f& p = pts[idx].pos; + Vec3d p_double = p.cast(); + const Vec3d up_vec(0., 0., 1.); + const Vec3d down_vec(0., 0., -1.); + // Project the point upward and downward and choose the closer intersection with the mesh. + AABBMesh::hit_result hit_up = mesh.query_ray_hit(p_double, up_vec); + AABBMesh::hit_result hit_down = mesh.query_ray_hit(p_double, down_vec); + + bool up = hit_up.is_hit(); + bool down = hit_down.is_hit(); + // no hit means support points lay exactly on triangle surface + if (!up && !down) return; + + AABBMesh::hit_result &hit = (!down || hit_up.distance() < hit_down.distance()) ? hit_up : hit_down; + if (hit.distance() <= allowed_move) { + p[2] += static_cast(hit.distance() * + hit.direction()[2]); + return; + } + + // big distance means that ray fly over triangle side (space between triangles) + int triangle_index; + Vec3d closest_point; + double distance = mesh.squared_distance(p_double, triangle_index, closest_point); + if (distance <= std::numeric_limits::epsilon()) return; // correct coordinate + p = closest_point.cast(); + }, 64 /* gransize */); + return pts; } - - -void remove_bottom_points(std::vector &pts, float lvl) -{ - // get iterator to the reorganized vector end - auto endit = std::remove_if(pts.begin(), pts.end(), [lvl] - (const sla::SupportPoint &sp) { - return sp.pos.z() <= lvl; - }); - - // erase all elements after the new end - pts.erase(endit, pts.end()); -} - -#ifdef SLA_SUPPORTPOINTGEN_DEBUG -void SupportPointGenerator::output_structures(const std::vector& structures) -{ - for (unsigned int i=0 ; i{*structures[i].polygon}, ss.str()); - } -} - -void SupportPointGenerator::output_expolygons(const ExPolygons& expolys, const std::string &filename) -{ - BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000)); - Slic3r::SVG svg_cummulative(filename, bb); - for (size_t i = 0; i < expolys.size(); ++ i) { - /*Slic3r::SVG svg("single"+std::to_string(i)+".svg", bb); - svg.draw(expolys[i]); - svg.draw_outline(expolys[i].contour, "black", scale_(0.05)); - svg.draw_outline(expolys[i].holes, "blue", scale_(0.05)); - svg.Close();*/ - - svg_cummulative.draw(expolys[i]); - svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05)); - svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05)); - } -} -#endif - -SupportPoints transformed_support_points(const ModelObject &mo, - const Transform3d &trafo) -{ - auto spts = mo.sla_support_points; - Transform3f tr = trafo.cast(); - for (sla::SupportPoint& suppt : spts) { - suppt.pos = tr * suppt.pos; - } - - return spts; -} - -} // namespace sla -} // namespace Slic3r diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index 1b1d3a909e..f6c571cdd0 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -1,262 +1,191 @@ -///|/ Copyright (c) Prusa Research 2020 - 2022 Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros, Lukáš Matěna @lukasmatena +///|/ Copyright (c) Prusa Research 2024 Filip Sykala @Jony01 ///|/ ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher ///|/ #ifndef SLA_SUPPORTPOINTGENERATOR_HPP #define SLA_SUPPORTPOINTGENERATOR_HPP -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include + +#include "libslic3r/Point.hpp" #include "libslic3r/ExPolygon.hpp" -#include "libslic3r/Polygon.hpp" -#include "libslic3r/libslic3r.h" +#include "libslic3r/SLA/SupportPoint.hpp" -namespace Slic3r { -class AABBMesh; -} // namespace Slic3r +namespace Slic3r::sla { -// #define SLA_SUPPORTPOINTGEN_DEBUG +/// +/// Configuration for automatic support placement +/// +struct SupportPointGeneratorConfig{ + /// + /// 0 mean only one support point for each island + /// lower than one mean less amount of support points + /// 1 mean fine tuned sampling + /// more than one mean bigger amout of support points + /// + float density_relative{1.f}; -namespace Slic3r { namespace sla { + /// + /// Size range for support point interface (head) + /// + MinMax head_diameter = {0.2f, 0.6f}; // [in mm] -class SupportPointGenerator { -public: - struct Config final { - float density_relative {1.f}; - float minimal_distance {1.f}; - float head_diameter {0.4f}; - - // Originally calibrated to 7.7f, reduced density by Tamas to 70% which is 11.1 (7.7 / 0.7) to adjust for new algorithm changes in tm_suppt_gen_improve - inline float support_force() const { return 11.1f / density_relative; } // a force one point can support (arbitrary force unit) - - // 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); // - // Minimal island Area to print - TODO: Should be modifiable from UI - const float minimal_island_area = pow(0.047f, 2.f); // [in mm^2] pixel_area - }; - - SupportPointGenerator(const AABBMesh& emesh, const std::vector& slices, - const std::vector& heights, const Config& config, std::function throw_on_cancel, std::function statusfn); - - SupportPointGenerator(const AABBMesh& emesh, const Config& config, std::function throw_on_cancel, std::function statusfn); - - const std::vector& output() const { return m_output; } - std::vector& output() { return m_output; } - - struct MyLayer; - - // Keep data for one area(ExPlygon) on the layer(on slice Expolygons) - struct Structure { - Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, float area) : - layer(&layer), polygon(&poly), bbox(bbox), area(area) -#ifdef SLA_SUPPORTPOINTGEN_DEBUG - , unique_id(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch())) -#endif /* SLA_SUPPORTPOINTGEN_DEBUG */ - {} - // Parent layer - with all ExPolygons in layer + layer_height - MyLayer *layer; - // Source ExPolygon - const ExPolygon* polygon = nullptr; - // Cache bounding box of polygon - const BoundingBox bbox; - // area of polygon [in mm^2] without holes - const float area = 0.f; - - // How well is this ExPolygon held to the print base? - // Positive number, the higher the better. - float supports_force_this_layer = 0.f; - float supports_force_inherited = 0.f; - float supports_force_total() const { return this->supports_force_this_layer + this->supports_force_inherited; } -#ifdef SLA_SUPPORTPOINTGEN_DEBUG - std::chrono::milliseconds unique_id; -#endif /* SLA_SUPPORTPOINTGEN_DEBUG */ - - struct Link { - Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {} - Structure *island; - float overlap_area; - }; - -#ifdef NDEBUG - // In release mode, use the optimized container. - boost::container::small_vector islands_above; - boost::container::small_vector islands_below; -#else - // In debug mode, use the standard vector, which is well handled by debugger visualizer. - std::vector islands_above; - std::vector islands_below; -#endif - // Overhangs, that are dangling considerably. - ExPolygons dangling_areas; - // Complete overhangs. - ExPolygons overhangs; - // Overhangs, where the surface must slope. - ExPolygons overhangs_slopes; - // Sum of all overhang areas from structure - float overhangs_area = 0.f; // [in mm^2] - - bool overlaps(const Structure &rhs) const { - return this->bbox.overlap(rhs.bbox) && this->polygon->overlaps(*rhs.polygon); - } - float overlap_area(const Structure &rhs) const { - double out = 0.; - if (this->bbox.overlap(rhs.bbox)) { - Polygons polys = intersection(*this->polygon, *rhs.polygon); - for (const Polygon &poly : polys) - out += poly.area(); - } - return float(out); - } - float area_below() const { - float area = 0.f; - for (const Link &below : this->islands_below) - area += below.island->area; - return area; - } - Polygons polygons_below() const { - size_t cnt = 0; - for (const Link &below : this->islands_below) - cnt += 1 + below.island->polygon->holes.size(); - Polygons out; - out.reserve(cnt); - for (const Link &below : this->islands_below) { - out.emplace_back(below.island->polygon->contour); - append(out, below.island->polygon->holes); - } - return out; - } - ExPolygons expolygons_below() const { - ExPolygons out; - out.reserve(this->islands_below.size()); - for (const Link &below : this->islands_below) - out.emplace_back(*below.island->polygon); - return out; - } - // Positive deficit of the supports. If negative, this area is well supported. If positive, more supports need to be added. - float support_force_deficit(const float tear_pressure) const { return this->area * tear_pressure - this->supports_force_total(); } - }; - - struct MyLayer { - MyLayer(const size_t layer_id, coordf_t print_z) : layer_id(layer_id), print_z(print_z) {} - // index into heights + slices - size_t layer_id; - // Absolute distance from Zero - copy value from heights - coordf_t print_z; // [in mm] - std::vector islands; - }; - - struct RichSupportPoint { - Vec3f position; - Structure *island; - }; - - struct PointGrid3D { - struct GridHash { - std::size_t operator()(const Vec3i &cell_id) const { - return std::hash()(cell_id.x()) ^ std::hash()(cell_id.y() * 593) ^ std::hash()(cell_id.z() * 7919); - } - }; - typedef std::unordered_multimap Grid; - - Vec3f cell_size; - Grid grid; - - Vec3i cell_id(const Vec3f &pos) { - return Vec3i(int(floor(pos.x() / cell_size.x())), - int(floor(pos.y() / cell_size.y())), - int(floor(pos.z() / cell_size.z()))); - } - - void insert(const Vec2f &pos, Structure *island) { - RichSupportPoint pt; - pt.position = Vec3f(pos.x(), pos.y(), float(island->layer->print_z)); - pt.island = island; - grid.emplace(cell_id(pt.position), pt); - } - - bool collides_with(const Vec2f &pos, float print_z, float radius) { - Vec3f pos3d(pos.x(), pos.y(), print_z); - Vec3i cell = cell_id(pos3d); - std::pair it_pair = grid.equal_range(cell); - if (collides_with(pos3d, radius, it_pair.first, it_pair.second)) - return true; - for (int i = -1; i < 2; ++ i) - for (int j = -1; j < 2; ++ j) - for (int k = -1; k < 1; ++ k) { - if (i == 0 && j == 0 && k == 0) - continue; - it_pair = grid.equal_range(cell + Vec3i(i, j, k)); - if (collides_with(pos3d, radius, it_pair.first, it_pair.second)) - return true; - } - return false; - } - - private: - bool collides_with(const Vec3f &pos, float radius, Grid::const_iterator it_begin, Grid::const_iterator it_end) { - for (Grid::const_iterator it = it_begin; it != it_end; ++ it) { - float dist2 = (it->second.position - pos).squaredNorm(); - if (dist2 < radius * radius) - return true; - } - return false; - } - }; - - void execute(const std::vector &slices, - const std::vector & heights); - - void seed(std::mt19937::result_type s) { m_rng.seed(s); } -private: - std::vector m_output; - - // Configuration - SupportPointGenerator::Config m_config; - - void process(const std::vector& slices, const std::vector& heights); - -public: - enum IslandCoverageFlags : uint8_t { icfNone = 0x0, icfIsNew = 0x1, icfWithBoundary = 0x2 }; - -private: - - void uniformly_cover(const ExPolygons& islands, Structure& structure, float deficit, PointGrid3D &grid3d, IslandCoverageFlags flags = icfNone); - - void add_support_points(Structure& structure, PointGrid3D &grid3d); - - void project_onto_mesh(std::vector& points) const; - -#ifdef SLA_SUPPORTPOINTGEN_DEBUG - static void output_expolygons(const ExPolygons& expolys, const std::string &filename); - static void output_structures(const std::vector &structures); -#endif // SLA_SUPPORTPOINTGEN_DEBUG - - const AABBMesh& m_emesh; - std::function m_throw_on_cancel; - std::function m_statusfn; - - std::mt19937 m_rng; + // 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); // + // Minimal island Area to print - TODO: Should be modifiable from UI + // !! Filter should be out of sampling algorithm !! + float minimal_island_area = pow(0.047f, 2.f); // [in mm^2] pixel_area }; -void remove_bottom_points(std::vector &pts, float lvl); +struct LayerPart; // forward decl. +using LayerParts = std::vector; -std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng); -void sample_expolygon_boundary(const ExPolygon &expoly, float samples_per_mm, std::vector &out, std::mt19937 &rng); +struct PartLink +{ + LayerParts::const_iterator part_it; + // float overlap_area; // sum of overlap areas + // ExPolygons overlap; // clipper intersection_ex + // ExPolygons overhang; // clipper diff_ex +}; +#ifdef NDEBUG +// In release mode, use the optimized container. +using PartLinks = boost::container::small_vector; +#else +// In debug mode, use the standard vector, which is well handled by debugger visualizer. +using PartLinks = std::vector; +#endif -}} // namespace Slic3r::sla +// Part on layer is defined by its shape +struct LayerPart { + // Pointer to expolygon stored in input + const ExPolygon *shape; -#endif // SUPPORTPOINTGENERATOR_HPP + // rectangular bounding box of shape + BoundingBox shape_extent; + + // uniformly sampled shape contour + Slic3r::Points samples; + // IMPROVE: sample only overhangs part of shape + + // Parts from previous printed layer, which is connected to current part + PartLinks prev_parts; + PartLinks next_parts; +}; + +/// +/// Extend support point with information from layer +/// +struct LayerSupportPoint: public SupportPoint +{ + // Pointer on source ExPolygon otherwise nullptr + //const LayerPart *part{nullptr}; + + // 2d coordinate on layer + // use only when part is not nullptr + Point position_on_layer; // [scaled_ unit] + + // 2d direction into expolygon mass + // used as ray to positioning 3d point on mesh surface + // Island has direction [0,0] - should be placed on surface from bottom + Point direction_to_mass; +}; +using LayerSupportPoints = std::vector; + +/// +/// One slice divided into +/// +struct Layer +{ + // index into parent Layesr + heights + slices + // [[deprecated]] Use index to layers insted of adress from item + size_t layer_id; + + // Absolute distance from Zero - copy value from heights + // [[deprecated]] Use index to layers insted of adress from item + double print_z; // [in mm] + + // data for one expolygon + LayerParts parts; +}; +using Layers = std::vector; + +/// +/// Keep state of Support Point generation +/// Used for resampling with different configuration +/// +struct SupportPointGeneratorData +{ + // Input slices of mesh + std::vector slices; + // Same size as slices + std::vector heights; + + // link to slices + Layers layers; +}; + +// call during generation of support points to check cancel event +using ThrowOnCancel = std::function; +// call to say progress of generation into gui in range from 0 to 100 +using StatusFunction= std::function; + +/// +/// Prepare data for generate support points +/// Used for interactive resampling to store permanent data between configuration changes., +/// Everything which could be prepared are stored into result. +/// Need to regenerate on mesh change(Should be connected with ObjectId) OR change of slicing heights +/// +/// Countour cut from mesh +/// Heights of the slices - Same size as slices +/// Call in meanwhile to check cancel event +/// Say progress of generation into gui +/// Data prepared for generate support points +SupportPointGeneratorData prepare_generator_data( + std::vector &&slices, + std::vector &&heights, + ThrowOnCancel throw_on_cancel, + StatusFunction statusfn +); + +/// +/// Generate support points on islands by configuration parameters +/// +/// Preprocessed data needed for sampling +/// Define density of samples +/// Call in meanwhile to check cancel event +/// Progress of generation into gui +/// Generated support points +LayerSupportPoints generate_support_points( + const SupportPointGeneratorData &data, + const SupportPointGeneratorConfig &config, + ThrowOnCancel throw_on_cancel, + StatusFunction statusfn +); +} // namespace Slic3r::sla + +// TODO: Not sure if it is neccessary & Should be in another file +namespace Slic3r{ +class AABBMesh; +namespace sla { +/// +/// Move support points on surface of mesh +/// +/// Support points to move on surface +/// Define surface for move points +/// Call in meanwhile to check cancel event +/// Support points laying on mesh surface +SupportPoints move_on_mesh_surface( + const LayerSupportPoints &points, + const AABBMesh &mesh, + double allowed_move, + ThrowOnCancel throw_on_cancel +); + +}} + +#endif // SLA_SUPPORTPOINTGENERATOR_HPP diff --git a/src/libslic3r/SLA/SupportPointGeneratorNew.cpp b/src/libslic3r/SLA/SupportPointGeneratorNew.cpp deleted file mode 100644 index 7db629f25e..0000000000 --- a/src/libslic3r/SLA/SupportPointGeneratorNew.cpp +++ /dev/null @@ -1,382 +0,0 @@ -///|/ Copyright (c) Prusa Research 2024 Filip Sykala @Jony01 -///|/ -///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher -///|/ - -#include "SupportPointGeneratorNew.hpp" - -#include // point grid - -#include "libslic3r/Execution/ExecutionTBB.hpp" // parallel preparation of data for sampling -#include "libslic3r/Execution/Execution.hpp" - -using namespace Slic3r; -using namespace Slic3r::sla; - -namespace { - -/// -/// Struct to store support points in 2d grid to faster search for nearest support points -/// -class Grid2D -{ - coord_t m_cell_size; // Squar: x and y are same - coord_t m_cell_size_half; - using Key = Point; - using Grid = std::unordered_multimap; - Grid m_grid; - -public: - /// - /// Set cell size for grid - /// - /// Granularity of stored points - /// Must be bigger than maximal used radius - explicit Grid2D(const coord_t &cell_size) - : m_cell_size(cell_size), m_cell_size_half(cell_size / 2) {} - - Key cell_id(const Point &point) const { - return Key(point.x() / m_cell_size, point.y() / m_cell_size); - } - - void add(SupportPoint &&point) { - m_grid.emplace(cell_id(point.position_on_layer), std::move(point)); - } - - using CheckFnc = std::function; - bool exist_true_in_4cell_neighbor(const Point &pos, const CheckFnc& fnc) const { - Key key = cell_id(pos); - if (exist_true_for_cell(key, pos, fnc)) return true; - Point un_cell_pos( - key.x() * m_cell_size + m_cell_size_half, - key.y() * m_cell_size + m_cell_size_half ); - Key key2( - (un_cell_pos.x() > pos.x()) ? key.x() + 1 : key.x() - 1, - (un_cell_pos.y() > pos.y()) ? key.y() + 1 : key.y() - 1); - if (exist_true_for_cell(key2, pos, fnc)) return true; - if (exist_true_for_cell({key.x(), key2.y()}, pos, fnc)) return true; - if (exist_true_for_cell({key2.x(), key.y()}, pos, fnc)) return true; - return false; - } - - void merge(Grid2D &&grid) { - // support to merge only grid with same size - assert(m_cell_size == grid.m_cell_size); - m_grid.merge(std::move(grid.m_grid)); - } - - SupportPoints get_points() const { - SupportPoints result; - result.reserve(m_grid.size()); - for (const auto& [key, support] : m_grid) - result.push_back(support); - return result; - } -private: - bool exist_true_for_cell(const Key &key, const Point &pos, const CheckFnc& fnc) const{ - auto [begin_it, end_it] = m_grid.equal_range(key); - for (Grid::const_iterator it = begin_it; it != end_it; ++it) { - const SupportPoint &support_point = it->second; - if (fnc(support_point, pos)) - return true; - } - return false; - } -}; - -/// -/// Intersection of line segment and circle -/// -/// Line segment point A, Point lay inside circle -/// Line segment point B, Point lay outside or on circle -/// Circle center point -/// squared value of Circle Radius (r2 = r*r) -/// Intersection point -Point intersection(const Point &p1, const Point &p2, const Point &cnt, double r2) { - // Vector from p1 to p2 - Vec2d dp_d((p2 - p1).cast()); - // Vector from circle center to p1 - Vec2d f_d((p1 - cnt).cast()); - - double a = dp_d.squaredNorm(); - double b = 2 * (f_d.x() * dp_d.x() + f_d.y() * dp_d.y()); - double c = f_d.squaredNorm() - r2; - - // Discriminant of the quadratic equation - double discriminant = b * b - 4 * a * c; - - // No intersection if discriminant is negative - assert(discriminant > 0); - if (discriminant < 0) - return {}; // No intersection - - // Calculate the two possible values of t (parametric parameter) - discriminant = sqrt(discriminant); - double t1 = (-b - discriminant) / (2 * a); - - // Check for valid intersection points within the line segment - if (t1 >= 0 && t1 <= 1) { - return {p1.x() + t1 * dp_d.x(), p1.y() + t1 * dp_d.y()}; - } - - // should not be in use - double t2 = (-b + discriminant) / (2 * a); - if (t2 >= 0 && t2 <= 1 && t1 != t2) { - return {p1.x() + t2 * dp_d.x(), p1.y() + t2 * dp_d.y()}; - } - return {}; -} - -/// -/// Uniformly sample Polygon, -/// Use first point and each next point is first crosing radius from last added -/// -/// Polygon to sample -/// Squared distance for sampling -/// Uniformly distributed points laying on input polygon -/// with exception of first and last point(they are closer than dist2) -Slic3r::Points sample(const Polygon &p, double dist2) { - if (p.empty()) - return {}; - - Slic3r::Points r; - r.push_back(p.front()); - const Point *prev_pt = nullptr; - for (size_t prev_i = 0; prev_i < p.size(); prev_i++) { - size_t curr_i = (prev_i != p.size() - 1) ? prev_i + 1 : 0; - const Point &pt = p.points[curr_i]; - double p_dist2 = (r.back() - pt).cast().squaredNorm(); - while (p_dist2 > dist2) { // line segment goes out of radius - if (prev_pt == nullptr) - prev_pt = &p.points[prev_i]; - r.push_back(intersection(*prev_pt, pt, r.back(), dist2)); - p_dist2 = (r.back() - pt).cast().squaredNorm(); - prev_pt = &r.back(); - } - prev_pt = nullptr; - } - return r; -} - -coord_t get_supported_radius(const SupportPoint &p, float z_distance, const SupportPointGeneratorConfig &config -) { - // TODO: calculate support radius - return scale_(5.); -} - -void sample_part( - const LayerPart &part, - size_t layer_id, - const SupportPointGeneratorData &data, - const SupportPointGeneratorConfig &config, - std::vector &grids, - std::vector &prev_grids -) { - // NOTE: first layer do not have prev part - assert(layer_id != 0); - - const Layers &layers = data.layers; - const LayerParts &prev_layer_parts = layers[layer_id - 1].parts; - const LayerParts::const_iterator &prev_part_it = part.prev_parts.front().part_it; - size_t index_of_prev_part = prev_part_it - prev_layer_parts.begin(); - if (prev_part_it->next_parts.size() == 1) { - grids.push_back(std::move(prev_grids[index_of_prev_part])); - } else { // Need a copy there are multiple parts above previus one - grids.push_back(prev_grids[index_of_prev_part]); // copy - } - // current part grid - Grid2D &part_grid = grids.back(); - - // merge other grid in case of multiple previous parts - for (size_t i = 1; i < part.prev_parts.size(); ++i) { - const LayerParts::const_iterator &prev_part_it = part.prev_parts[i].part_it; - size_t index_of_prev_part = prev_part_it - prev_layer_parts.begin(); - if (prev_part_it->next_parts.size() == 1) { - part_grid.merge(std::move(prev_grids[index_of_prev_part])); - } else { // Need a copy there are multiple parts above previus one - Grid2D grid_ = prev_grids[index_of_prev_part]; // copy - part_grid.merge(std::move(grid_)); - } - } - - float part_height = data.heights[layer_id]; - Grid2D::CheckFnc is_supported = [part_height, &config] - (const SupportPoint &support_point, const Point &p) -> bool { - float diff_height = part_height - support_point.z_height; - coord_t r_ = get_supported_radius(support_point, diff_height, config); - Point dp = support_point.position_on_layer - p; - if (std::abs(dp.x()) > r_) return false; - if (std::abs(dp.y()) > r_) return false; - double r2 = static_cast(r_); - r2 *= r2; - return dp.cast().squaredNorm() < r2; - }; - - // check distance to nearest support points from grid - float maximal_radius = scale_(5.f); - for (const Point &p : part.samples) { - if (!part_grid.exist_true_in_4cell_neighbor(p, is_supported)) { - // not supported sample, soo create new support point - part_grid.add(SupportPoint{ - /* head_front_radius */ 0.4, - SupportPointType::slope, - &part, - /* position_on_layer */ p, - part_height, - /* direction_to_mass */ Point(1,0) - }); - } - } -} - -Points uniformly_sample(const ExPolygon &island, const SupportPointGeneratorConfig &cfg) { - // TODO: Implement it - return Points{island.contour.centroid()}; -} - -Grid2D support_island(const LayerPart &part, float part_z, const SupportPointGeneratorConfig &cfg) { - // Maximal radius of supported area of one support point - double max_support_radius = 10.; // cfg.cell_size; - - // maximal radius of support - coord_t cell_size = scale_(max_support_radius); - - Grid2D part_grid(cell_size); - Points pts = uniformly_sample(*part.shape, cfg); - for (const Point &pt : pts) - part_grid.add(SupportPoint{ - /* head_front_radius */ 0.4, - SupportPointType::island, - &part, - /* position_on_layer */ pt, - part_z, - /* direction_to_mass */ Point(0,0) // from bottom - }); -} - -}; - -SupportPointGeneratorData Slic3r::sla::prepare_generator_data( - std::vector &&slices, - std::vector &&heights, - ThrowOnCancel throw_on_cancel, - StatusFunction statusfn -) { - // check input - assert(!slices.empty()); - assert(slices.size() == heights.size()); - if (slices.empty() || slices.size() != heights.size()) - return SupportPointGeneratorData{}; - - // Move input into result - SupportPointGeneratorData result; - result.slices = std::move(slices); - result.heights = std::move(heights); - - // Allocate empty layers. - result.layers = Layers(result.slices.size(), {}); - - // Generate Extents and SampleLayers - execution::for_each(ex_tbb, size_t(0), result.slices.size(), - [&result, throw_on_cancel](size_t layer_id) { - 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(); - - const double sample_distance_in_mm = scale_(2); - const double sample_distance_in_mm2 = sample_distance_in_mm * sample_distance_in_mm; - - Layer &layer = result.layers[layer_id]; - const ExPolygons &islands = result.slices[layer_id]; - layer.parts.reserve(islands.size()); - for (const ExPolygon &island : islands) - layer.parts.push_back(LayerPart{ - &island, - get_extents(island.contour), - sample(island.contour, sample_distance_in_mm2) - }); - - }, 32 /*gransize*/); - - // Link parts by intersections - execution::for_each(ex_tbb, size_t(1), result.slices.size(), - [&result, throw_on_cancel] (size_t 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(); - - LayerParts &parts_above = result.layers[layer_id].parts; - LayerParts &parts_below = result.layers[layer_id-1].parts; - for (auto it_above = parts_above.begin(); it_above < parts_above.end(); ++it_above) { - for (auto it_below = parts_below.begin(); it_below < parts_below.end(); ++it_below) { - // Improve: do some sort of parts + skip some of them - if (!it_above->shape_extent.overlap(it_below->shape_extent)) - continue; // no bounding box overlap - - // Improve: test could be done faster way - Polygons polys = intersection(*it_above->shape, *it_below->shape); - if (polys.empty()) - continue; // no intersection - - // TODO: check minimal intersection! - - it_above->prev_parts.emplace_back(PartLink{it_below}); - it_below->next_parts.emplace_back(PartLink{it_above}); - } - } - }, 8 /* gransize */); - return result; -} - -SupportPoints Slic3r::sla::generate_support_points( - const SupportPointGeneratorData &data, - const SupportPointGeneratorConfig &config, - ThrowOnCancel throw_on_cancel, - StatusFunction statusfn -){ - const Layers &layers = data.layers; - double increment = 100.0 / static_cast(layers.size()); - double status = 0; // current progress - int status_int = 0; - - SupportPoints result; - std::vector prev_grids; // same count as previous layer item size - for (size_t layer_id = 0; layer_id < layers.size(); ++layer_id) { - const Layer &layer = layers[layer_id]; - - std::vector grids; - grids.reserve(layer.parts.size()); - - for (const LayerPart &part : layer.parts) { - if (part.prev_parts.empty()) { - // new island - needs support no doubt - float part_z = data.heights[layer_id]; - grids.push_back(support_island(part, part_z, config)); - } else { - sample_part(part, layer_id, data, config, grids, prev_grids); - } - - // collect result from grid of top part - if (part.next_parts.empty()) { - const Grid2D &part_grid = grids.back(); - SupportPoints sps = part_grid.get_points(); - result.insert(result.end(), - std::make_move_iterator(sps.begin()), - std::make_move_iterator(sps.end())); - } - } - prev_grids = std::move(grids); - - throw_on_cancel(); - - int old_status_int = status_int; - status += increment; - status_int = static_cast(std::round(status)); - if (old_status_int < status_int) - statusfn(status_int); - } - return result; -} - - diff --git a/src/libslic3r/SLA/SupportPointGeneratorNew.hpp b/src/libslic3r/SLA/SupportPointGeneratorNew.hpp deleted file mode 100644 index 8588cb7943..0000000000 --- a/src/libslic3r/SLA/SupportPointGeneratorNew.hpp +++ /dev/null @@ -1,190 +0,0 @@ -///|/ Copyright (c) Prusa Research 2024 Filip Sykala @Jony01 -///|/ -///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher -///|/ -#ifndef SLA_SUPPORTPOINTGENERATOR_NEW_HPP -#define SLA_SUPPORTPOINTGENERATOR_NEW_HPP - -#include -#include - -#include - -#include "libslic3r/Point.hpp" -#include "libslic3r/ExPolygon.hpp" - -namespace Slic3r::sla { - -/// -/// Configuration for automatic support placement -/// -struct SupportPointGeneratorConfig{ - /// - /// 0 mean only one support point for each island - /// lower than one mean less amount of support points - /// 1 mean fine tuned sampling - /// more than one mean bigger amout of support points - /// - float density_relative{1.f}; - - /// - /// Size range for support point interface (head) - /// - MinMax head_diameter = {0.2f, 0.6f}; // [in mm] - - // 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); // - // Minimal island Area to print - TODO: Should be modifiable from UI - // !! Filter should be out of sampling algorithm !! - float minimal_island_area = pow(0.047f, 2.f); // [in mm^2] pixel_area -}; - -struct LayerPart; // forward decl. -using LayerParts = std::vector; - -struct PartLink -{ - LayerParts::const_iterator part_it; - // float overlap_area; // sum of overlap areas - // ExPolygons overlap; // clipper intersection_ex - // ExPolygons overhang; // clipper diff_ex -}; -#ifdef NDEBUG -// In release mode, use the optimized container. -using PartLinks = boost::container::small_vector; -#else -// In debug mode, use the standard vector, which is well handled by debugger visualizer. -using PartLinks = std::vector; -#endif - -// Part on layer is defined by its shape -struct LayerPart { - // Pointer to expolygon stored in input - const ExPolygon *shape; - - // rectangular bounding box of shape - BoundingBox shape_extent; - - // uniformly sampled shape contour - Slic3r::Points samples; - // IMPROVE: sample only overhangs part of shape - - // Parts from previous printed layer, which is connected to current part - PartLinks prev_parts; - PartLinks next_parts; -}; - -/// -/// One slice divided into -/// -struct Layer -{ - // index into parent Layesr + heights + slices - // [[deprecated]] Use index to layers insted of adress from item - size_t layer_id; - - // Absolute distance from Zero - copy value from heights - // [[deprecated]] Use index to layers insted of adress from item - double print_z; // [in mm] - - // data for one expolygon - LayerParts parts; -}; -using Layers = std::vector; - -/// -/// Keep state of Support Point generation -/// Used for resampling with different configuration -/// -struct SupportPointGeneratorData -{ - // Input slices of mesh - std::vector slices; - // Same size as slices - std::vector heights; - - // link to slices - Layers layers; -}; - -// Reason of automatic support placement usage -enum class SupportPointType { - manual_add, - island, // no move - slope, - thin, - stability, - edge -}; - -/// -/// Generated support point -/// -struct SupportPoint -{ - // radius of the touching interface - // Also define force it must keep - float head_front_radius{1.f}; - - // type - SupportPointType type{SupportPointType::manual_add}; - - // Pointer on source ExPolygon otherwise nullptr - const LayerPart *part{nullptr}; - - // 2d coordinate on layer - // use only when part is not nullptr - Point position_on_layer; // [scaled_ unit] - - // height of part - float z_height; - - // 2d direction into expolygon mass - // used as ray to positioning point on mesh surface - Point direction_to_mass; -}; -using SupportPoints = std::vector; - -// call during generation of support points to check cancel event -using ThrowOnCancel = std::function; -// call to say progress of generation into gui in range from 0 to 100 -using StatusFunction= std::function; - -/// -/// Prepare data for generate support points -/// Used for interactive resampling to store permanent data between configuration changes., -/// Everything which could be prepared are stored into result. -/// Need to regenerate on mesh change(Should be connected with ObjectId) OR change of slicing heights -/// -/// Countour cut from mesh -/// Heights of the slices - Same size as slices -/// Call in meanwhile to check cancel event -/// Say progress of generation into gui -/// Data prepared for generate support points -SupportPointGeneratorData prepare_generator_data( - std::vector &&slices, - std::vector &&heights, - ThrowOnCancel throw_on_cancel, - StatusFunction statusfn -); - -/// -/// Generate support points on islands by configuration parameters -/// -/// Preprocessed data needed for sampling -/// Define density of samples -/// Call in meanwhile to check cancel event -/// Progress of generation into gui -/// Generated support points -SupportPoints generate_support_points( - const SupportPointGeneratorData &data, - const SupportPointGeneratorConfig &config, - ThrowOnCancel throw_on_cancel, - StatusFunction statusfn -); - -} // namespace Slic3r::sla - -#endif // SLA_SUPPORTPOINTGENERATOR_NEW_HPP diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 954aed7a2a..fb05248974 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1255,8 +1255,12 @@ SLAPrintObject::get_parts_to_slice(SLAPrintObjectStep untilstep) const sla::SupportPoints SLAPrintObject::transformed_support_points() const { assert(model_object()); - - return sla::transformed_support_points(*model_object(), trafo()); + auto spts = model_object()->sla_support_points; + Transform3f tr = trafo().cast(); + for (sla::SupportPoint &suppt : spts) { + suppt.pos = tr * suppt.pos; + } + return spts; } sla::DrainHoles SLAPrintObject::transformed_drainhole_points() const diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 3b92cd861c..03805f66d8 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -51,7 +51,6 @@ #include "libslic3r/SLA/Hollowing.hpp" #include "libslic3r/SLA/JobController.hpp" #include "libslic3r/SLA/RasterBase.hpp" -#include "libslic3r/SLA/SupportPoint.hpp" #include "libslic3r/SLA/SupportTree.hpp" #include "libslic3r/SLA/SupportTreeStrategies.hpp" #include "libslic3r/SLAPrint.hpp" @@ -632,24 +631,20 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) // we will do the autoplacement. Otherwise we will just blindly copy the // frontend data into the backend cache. if (mo.sla_points_status != sla::PointsStatus::UserModified) { - - // calculate heights of slices (slices are calculated already) - const std::vector& heights = po.m_model_height_levels; - throw_if_canceled(); - sla::SupportPointGenerator::Config config; + sla::SupportPointGeneratorConfig config; const SLAPrintObjectConfig& cfg = po.config(); // the density config value is in percents: config.density_relative = float(cfg.support_points_density_relative / 100.f); - config.minimal_distance = float(cfg.support_points_minimal_distance); + switch (cfg.support_tree_type) { case sla::SupportTreeType::Default: case sla::SupportTreeType::Organic: - config.head_diameter = float(cfg.support_head_front_diameter); + config.head_diameter = {float(cfg.support_head_front_diameter), .0}; break; case sla::SupportTreeType::Branching: - config.head_diameter = float(cfg.branchingsupport_head_front_diameter); + config.head_diameter = {float(cfg.branchingsupport_head_front_diameter), .0}; break; } @@ -666,12 +661,29 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) // Construction of this object does the calculation. throw_if_canceled(); - sla::SupportPointGenerator auto_supports( - po.m_supportdata->input.emesh, po.get_model_slices(), - heights, config, [this]() { throw_if_canceled(); }, statuscb); - // Now let's extract the result. - std::vector& points = auto_supports.output(); + // TODO: filter small unprintable islands in slices + // (Island with area smaller than 1 pixel was skipped in support generator) + + std::vector slices = po.get_model_slices(); // copy + std::vector heights = po.m_model_height_levels; // copy + sla::ThrowOnCancel cancel = [this]() { throw_if_canceled(); }; + sla::StatusFunction status = statuscb; + sla::SupportPointGeneratorData data = + sla::prepare_generator_data(std::move(slices), std::move(heights), cancel, status); + + sla::LayerSupportPoints layer_support_points = + sla::generate_support_points(data, config, cancel, status); + + const AABBMesh& emesh = po.m_supportdata->input.emesh; + // Maximal move of support point to mesh surface, + // no more than height of layer + assert(po.m_model_height_levels.size() > 1); + double allowed_move = (po.m_model_height_levels[1] - po.m_model_height_levels[0]) + + std::numeric_limits::epsilon(); + sla::SupportPoints support_points = + sla::move_on_mesh_surface(layer_support_points, emesh, allowed_move, cancel); + throw_if_canceled(); MeshSlicingParamsEx params; @@ -691,9 +703,9 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) }); SuppPtMask mask{blockers, enforcers, po.config().support_enforcers_only.getBool()}; - filter_support_points_by_modifiers(points, mask, po.m_model_height_levels); + filter_support_points_by_modifiers(support_points, mask, po.m_model_height_levels); - po.m_supportdata->input.pts = points; + po.m_supportdata->input.pts = support_points; BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " @@ -717,10 +729,17 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po) // If the zero elevation mode is engaged, we have to filter out all the // points that are on the bottom of the object if (is_zero_elevation(po.config())) { - remove_bottom_points(po.m_supportdata->input.pts, - float( - po.m_supportdata->input.zoffset + - EPSILON)); + // remove_bottom_points + std::vector &pts = po.m_supportdata->input.pts; + float lvl(po.m_supportdata->input.zoffset + EPSILON); + + // get iterator to the reorganized vector end + auto endit = std::remove_if(pts.begin(), pts.end(), + [lvl](const sla::SupportPoint &sp) { + return sp.pos.z() <= lvl; }); + + // erase all elements after the new end + pts.erase(endit, pts.end()); } po.m_supportdata->input.cfg = make_support_cfg(po.m_config); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index fdba58a62b..a235cf4840 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -212,7 +212,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection) if (size_t(m_hover_id) == i && m_editing_mode) // ignore hover state unless editing mode is active render_color = { 0.f, 1.f, 1.f, 1.f }; else { // neigher hover nor picking - bool supports_new_island = m_lock_unique_islands && support_point.is_new_island; + bool supports_new_island = m_lock_unique_islands && support_point.type == sla::SupportPointType::island; if (m_editing_mode) { if (point_selected) render_color = { 1.f, 0.3f, 0.3f, 1.f}; @@ -324,7 +324,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous std::pair pos_and_normal; if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection 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_editing_cache.emplace_back(sla::SupportPoint{pos_and_normal.first, m_new_point_head_diameter/2.f}, false, pos_and_normal.second); m_parent.set_as_dirty(); m_wait_for_up_event = true; unregister_point_raycasters_for_picking(); @@ -479,7 +479,7 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point")); for (unsigned int idx=0; idx pos_and_normal; @@ -922,7 +922,7 @@ void GLGizmoSlaSupports::on_dragging(const UpdateData &data) 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].support_point.type = sla::SupportPointType::manual_add; m_editing_cache[m_hover_id].normal = pos_and_normal.second; } @@ -1124,7 +1124,7 @@ void GLGizmoSlaSupports::get_data_from_backend() const std::vector& points = po->get_support_points(); auto mat = po->trafo().inverse().cast(); for (unsigned int i=0; isla_points_status = sla::PointsStatus::AutoGenerated; break;