mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-04-22 14:09:41 +08:00
1253 lines
51 KiB
C++
1253 lines
51 KiB
C++
///|/ Copyright (c) Prusa Research 2024 Filip Sykala @Jony01
|
|
///|/
|
|
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
|
///|/
|
|
|
|
#include "SupportPointGenerator.hpp"
|
|
|
|
#include "libslic3r/Execution/ExecutionTBB.hpp" // parallel preparation of data for sampling
|
|
#include "libslic3r/Execution/Execution.hpp"
|
|
#include "libslic3r/KDTreeIndirect.hpp"
|
|
#include "libslic3r/ClipperUtils.hpp"
|
|
#include "libslic3r/AABBTreeLines.hpp" // closest point to layer part
|
|
// SupportIslands
|
|
#include "libslic3r/SLA/SupportIslands/UniformSupportIsland.hpp"
|
|
|
|
using namespace Slic3r;
|
|
using namespace Slic3r::sla;
|
|
|
|
//#define PERMANENT_SUPPORTS
|
|
|
|
namespace {
|
|
/// <summary>
|
|
/// Struct to store support points in KD tree to fast search for nearest ones.
|
|
/// </summary>
|
|
class NearPoints
|
|
{
|
|
/// <summary>
|
|
/// Structure made for KDTree as function to
|
|
/// acess support point coordinate by index into global support point storage
|
|
/// </summary>
|
|
struct PointAccessor {
|
|
// multiple trees points into same data storage of all support points
|
|
LayerSupportPoints *m_supports_ptr;
|
|
explicit PointAccessor(LayerSupportPoints *supports_ptr) : m_supports_ptr(supports_ptr) {}
|
|
// accessor to coordinate for kd tree
|
|
const coord_t &operator()(size_t idx, size_t dimension) const {
|
|
return m_supports_ptr->at(idx).position_on_layer[dimension];
|
|
}
|
|
};
|
|
|
|
PointAccessor m_points;
|
|
using Tree = KDTreeIndirect<2, coord_t, PointAccessor>;
|
|
Tree m_tree;
|
|
public:
|
|
/// <summary>
|
|
/// Constructor get pointer on the global storage of support points
|
|
/// </summary>
|
|
/// <param name="supports_ptr">Pointer on Support vector</param>
|
|
explicit NearPoints(LayerSupportPoints* supports_ptr)
|
|
: m_points(supports_ptr), m_tree(m_points) {}
|
|
|
|
NearPoints get_copy(){
|
|
NearPoints copy(m_points.m_supports_ptr);
|
|
copy.m_tree = m_tree.get_copy(); // copy tree
|
|
return copy;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove support points from KD-Tree which lay out of expolygons
|
|
/// </summary>
|
|
/// <param name="shapes">Define area where could be support points</param>
|
|
void remove_out_of(const ExPolygons &shapes) {
|
|
std::vector<size_t> indices = get_indices();
|
|
auto it = std::remove_if(indices.begin(), indices.end(),
|
|
[&pts = *m_points.m_supports_ptr, &shapes](size_t point_index) {
|
|
const Point& p = pts.at(point_index).position_on_layer;
|
|
return !std::any_of(shapes.begin(), shapes.end(),
|
|
[&p](const ExPolygon &shape) {
|
|
return shape.contains(p);
|
|
});
|
|
});
|
|
if (it == indices.end())
|
|
return; // no point to remove
|
|
indices.erase(it, indices.end());
|
|
m_tree.clear();
|
|
m_tree.build(indices); // consume indices
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add point new support point into global storage of support points
|
|
/// and pointer into tree structure of nearest points
|
|
/// </summary>
|
|
/// <param name="point">New added point</param>
|
|
void add(LayerSupportPoint &&point) {
|
|
// IMPROVE: only add to existing tree, do not reconstruct tree
|
|
std::vector<size_t> indices = get_indices();
|
|
LayerSupportPoints &pts = *m_points.m_supports_ptr;
|
|
size_t index = pts.size();
|
|
pts.emplace_back(std::move(point));
|
|
indices.push_back(index);
|
|
m_tree.clear();
|
|
m_tree.build(indices); // consume indices
|
|
}
|
|
|
|
using CheckFnc = std::function<bool(const LayerSupportPoint &, const Point&)>;
|
|
/// <summary>
|
|
/// Iterate over support points in 2d radius and search any of fnc with True.
|
|
/// Made for check wheather surrounding supports support current point p.
|
|
/// </summary>
|
|
/// <param name="pos">Center of search circle</param>
|
|
/// <param name="radius">Search circle radius</param>
|
|
/// <param name="fnc">Function to check supports point</param>
|
|
/// <returns>True wheny any of check function return true, otherwise False</returns>
|
|
bool exist_true_in_radius(const Point &pos, coord_t radius, const CheckFnc &fnc) const {
|
|
std::vector<size_t> point_indices = find_nearby_points(m_tree, pos, radius);
|
|
return std::any_of(point_indices.begin(), point_indices.end(),
|
|
[&points = *m_points.m_supports_ptr, &pos, &fnc](size_t point_index){
|
|
return fnc(points.at(point_index), pos);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Merge another tree structure into current one.
|
|
/// Made for connection of two mesh parts.
|
|
/// </summary>
|
|
/// <param name="near_point">Another near points</param>
|
|
void merge(NearPoints &&near_point) {
|
|
// need to have same global storage of support points
|
|
assert(m_points.m_supports_ptr == near_point.m_points.m_supports_ptr);
|
|
|
|
// IMPROVE: merge trees instead of rebuild
|
|
std::vector<size_t> indices = get_indices();
|
|
std::vector<size_t> indices2 = near_point.get_indices();
|
|
// merge
|
|
indices.insert(indices.end(),
|
|
std::move_iterator(indices2.begin()),
|
|
std::move_iterator(indices2.end()));
|
|
// remove duplicit indices - Diamond case
|
|
std::sort(indices.begin(), indices.end());
|
|
auto it = std::unique(indices.begin(), indices.end());
|
|
indices.erase(it, indices.end());
|
|
// rebuild tree
|
|
m_tree.clear();
|
|
m_tree.build(indices); // consume indices
|
|
}
|
|
|
|
private:
|
|
std::vector<size_t> get_indices() const {
|
|
std::vector<size_t> indices = m_tree.get_nodes(); // copy
|
|
// nodes in tree contain "max values for size_t" on unused leaf of tree,
|
|
// when count of indices is not exactly power of 2
|
|
auto it = std::remove_if(indices.begin(), indices.end(),
|
|
[max_index = m_points.m_supports_ptr->size()](size_t i) { return i >= max_index; });
|
|
indices.erase(it, indices.end());
|
|
return indices;
|
|
}
|
|
};
|
|
|
|
/// <summary>
|
|
/// Intersection of line segment and circle
|
|
/// </summary>
|
|
/// <param name="p1">Line segment point A, Point lay inside circle</param>
|
|
/// <param name="p2">Line segment point B, Point lay outside or on circle</param>
|
|
/// <param name="cnt">Circle center point</param>
|
|
/// <param name="r2">squared value of Circle Radius (r2 = r*r)</param>
|
|
/// <returns>Intersection point</returns>
|
|
Point intersection_line_circle(const Point &p1, const Point &p2, const Point &cnt, double r2) {
|
|
// Vector from p1 to p2
|
|
Vec2d dp_d((p2 - p1).cast<double>());
|
|
// Vector from circle center to p1
|
|
Vec2d f_d((p1 - cnt).cast<double>());
|
|
|
|
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 {};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move grid from previous layer to current one
|
|
/// for given part
|
|
/// </summary>
|
|
/// <param name="prev_layer_parts">Grids generated in previous layer</param>
|
|
/// <param name="part">Current layer part to process</param>
|
|
/// <param name="prev_grids">Grids which will be moved to current grid</param>
|
|
/// <returns>Grid for given part</returns>
|
|
NearPoints create_near_points(
|
|
const LayerParts &prev_layer_parts,
|
|
const LayerPart &part,
|
|
std::vector<NearPoints> &prev_grids
|
|
) {
|
|
const LayerParts::const_iterator &prev_part_it = part.prev_parts.front();
|
|
size_t index_of_prev_part = prev_part_it - prev_layer_parts.begin();
|
|
NearPoints near_points = (prev_part_it->next_parts.size() == 1)?
|
|
std::move(prev_grids[index_of_prev_part]) :
|
|
// Need a copy there are multiple parts above previus one
|
|
prev_grids[index_of_prev_part].get_copy(); // copy
|
|
|
|
// 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];
|
|
size_t index_of_prev_part = prev_part_it - prev_layer_parts.begin();
|
|
if (prev_part_it->next_parts.size() == 1) {
|
|
near_points.merge(std::move(prev_grids[index_of_prev_part]));
|
|
} else { // Need a copy there are multiple parts above previus one
|
|
NearPoints grid_ = prev_grids[index_of_prev_part].get_copy(); // copy
|
|
near_points.merge(std::move(grid_));
|
|
}
|
|
}
|
|
return near_points;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add support point to near_points when it is neccessary
|
|
/// </summary>
|
|
/// <param name="part">Current part - keep samples</param>
|
|
/// <param name="config">Configuration to sample</param>
|
|
/// <param name="near_points">Keep previous sampled suppport points</param>
|
|
/// <param name="part_z">current z coordinate of part</param>
|
|
/// <param name="maximal_radius">Max distance to seach support for sample</param>
|
|
void support_part_overhangs(
|
|
const LayerPart &part,
|
|
const SupportPointGeneratorConfig &config,
|
|
NearPoints &near_points,
|
|
float part_z,
|
|
coord_t maximal_radius
|
|
) {
|
|
NearPoints::CheckFnc is_supported = []
|
|
(const LayerSupportPoint &support_point, const Point &p) -> bool {
|
|
// Debug visualization of all sampled outline
|
|
//return false;
|
|
coord_t r = support_point.current_radius;
|
|
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 = sqr(static_cast<double>(r));
|
|
return dp.cast<double>().squaredNorm() < r2;
|
|
};
|
|
|
|
for (const Point &p : part.samples) {
|
|
if (!near_points.exist_true_in_radius(p, maximal_radius, is_supported)) {
|
|
// not supported sample, soo create new support point
|
|
near_points.add(LayerSupportPoint{
|
|
SupportPoint{
|
|
Vec3f{unscale<float>(p.x()), unscale<float>(p.y()), part_z},
|
|
/* head_front_radius */ config.head_diameter / 2,
|
|
SupportPointType::slope
|
|
},
|
|
/* position_on_layer */ p,
|
|
/* radius_curve_index */ 0,
|
|
/* current_radius */ static_cast<coord_t>(scale_(config.support_curve.front().x()))
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sample part as Island
|
|
/// Result store to grid
|
|
/// </summary>
|
|
/// <param name="part">Island to support</param>
|
|
/// <param name="near_points">OUT place to store new supports</param>
|
|
/// <param name="part_z">z coordinate of part</param>
|
|
/// <param name="cfg"></param>
|
|
void support_island(const LayerPart &part, NearPoints& near_points, float part_z,
|
|
const SupportPointGeneratorConfig &cfg) {
|
|
SupportIslandPoints samples = uniform_support_island(*part.shape, cfg.island_configuration);
|
|
for (const SupportIslandPointPtr &sample : samples)
|
|
near_points.add(LayerSupportPoint{
|
|
SupportPoint{
|
|
Vec3f{
|
|
unscale<float>(sample->point.x()),
|
|
unscale<float>(sample->point.y()),
|
|
part_z
|
|
},
|
|
/* head_front_radius */ cfg.head_diameter / 2,
|
|
SupportPointType::island
|
|
},
|
|
/* position_on_layer */ sample->point,
|
|
/* radius_curve_index */ 0,
|
|
/* current_radius */ static_cast<coord_t>(scale_(cfg.support_curve.front().x()))
|
|
});
|
|
}
|
|
|
|
void support_peninsulas(const Peninsulas& peninsulas, NearPoints& near_points, float part_z,
|
|
const SupportPointGeneratorConfig &cfg) {
|
|
for (const Peninsula& peninsula: peninsulas) {
|
|
SupportIslandPoints peninsula_supports =
|
|
uniform_support_peninsula(peninsula, cfg.island_configuration);
|
|
for (const SupportIslandPointPtr &support : peninsula_supports)
|
|
near_points.add(LayerSupportPoint{
|
|
SupportPoint{
|
|
Vec3f{
|
|
unscale<float>(support->point.x()),
|
|
unscale<float>(support->point.y()),
|
|
part_z
|
|
},
|
|
/* head_front_radius */ cfg.head_diameter / 2,
|
|
SupportPointType::island
|
|
},
|
|
/* position_on_layer */ support->point,
|
|
/* radius_curve_index */ 0,
|
|
/* current_radius */ static_cast<coord_t>(scale_(cfg.support_curve.front().x()))
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy parts from link to output
|
|
/// </summary>
|
|
/// <param name="part_links">Links between part of mesh</param>
|
|
/// <returns>Collected polygons from links</returns>
|
|
Polygons get_polygons(const PartLinks& part_links) {
|
|
size_t cnt = 0;
|
|
for (const PartLink &part_link : part_links)
|
|
cnt += 1 + part_link->shape->holes.size();
|
|
|
|
Polygons out;
|
|
out.reserve(cnt);
|
|
for (const PartLink &part_link : part_links) {
|
|
const ExPolygon &shape = *part_link->shape;
|
|
out.emplace_back(shape.contour);
|
|
append(out, shape.holes);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Uniformly sample Polyline,
|
|
/// Use first point and each next point is first crosing radius from last added
|
|
/// </summary>
|
|
/// <param name="b">Begin of polyline points to sample</param>
|
|
/// <param name="e">End of polyline points to sample</param>
|
|
/// <param name="dist2">Squared distance for sampling</param>
|
|
/// <returns>Uniformly distributed points laying on input polygon
|
|
/// with exception of first and last point(they are closer than dist2)</returns>
|
|
Slic3r::Points sample(Points::const_iterator b, Points::const_iterator e, double dist2) {
|
|
assert(e-b >= 2);
|
|
if (e - b < 2)
|
|
return {}; // at least one line(2 points)
|
|
|
|
// IMPROVE1: start of sampling e.g. center of Polyline
|
|
// IMPROVE2: Random offset(To remove align of point between slices)
|
|
// IMPROVE3: Sample small overhangs with memory for last sample(OR in center)
|
|
Slic3r::Points r;
|
|
r.push_back(*b);
|
|
|
|
//Points::const_iterator prev_pt = e;
|
|
const Point *prev_pt = nullptr;
|
|
for (Points::const_iterator it = b; it+1 < e; ++it){
|
|
const Point &pt = *(it+1);
|
|
double p_dist2 = (r.back() - pt).cast<double>().squaredNorm();
|
|
while (p_dist2 > dist2) { // line segment goes out of radius
|
|
if (prev_pt == nullptr)
|
|
prev_pt = &(*it);
|
|
r.push_back(intersection_line_circle(*prev_pt, pt, r.back(), dist2));
|
|
p_dist2 = (r.back() - pt).cast<double>().squaredNorm();
|
|
prev_pt = &r.back();
|
|
}
|
|
prev_pt = nullptr;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
bool contain_point(const Point &p, const Points &sorted_points) {
|
|
auto it = std::lower_bound(sorted_points.begin(), sorted_points.end(), p);
|
|
if (it == sorted_points.end())
|
|
return false;
|
|
++it; // next point should be same as searched
|
|
if (it == sorted_points.end())
|
|
return false;
|
|
return it->x() == p.x() && it->y() == p.y();
|
|
};
|
|
|
|
#ifndef NDEBUG
|
|
bool exist_same_points(const ExPolygon &shape, const Points& prev_points) {
|
|
auto shape_points = to_points(shape);
|
|
return shape_points.end() !=
|
|
std::find_if(shape_points.begin(), shape_points.end(), [&prev_points](const Point &p) {
|
|
return contain_point(p, prev_points);
|
|
});
|
|
}
|
|
#endif // NDEBUG
|
|
|
|
Points sample_overhangs(const LayerPart& part, double dist2) {
|
|
const ExPolygon &shape = *part.shape;
|
|
|
|
// Collect previous expolygons by links collected in loop before
|
|
Polygons prev_polygons = get_polygons(part.prev_parts);
|
|
assert(!prev_polygons.empty());
|
|
ExPolygons overhangs = diff_ex(shape, prev_polygons);
|
|
if (overhangs.empty()) // above part is smaller in whole contour
|
|
return {};
|
|
|
|
Points prev_points = to_points(prev_polygons);
|
|
std::sort(prev_points.begin(), prev_points.end());
|
|
|
|
// TODO: solve case when shape and prev points has same point
|
|
assert(!exist_same_points(shape, prev_points));
|
|
|
|
auto sample_overhang = [&prev_points, dist2](const Polygon &polygon, Points &samples) {
|
|
const Points &pts = polygon.points;
|
|
// first point which is not part of shape
|
|
Points::const_iterator first_bad = pts.end();
|
|
Points::const_iterator start_it = pts.end();
|
|
for (auto it = pts.begin(); it != pts.end(); ++it) {
|
|
const Point &p = *it;
|
|
if (contain_point(p, prev_points)) {
|
|
if (first_bad == pts.end()) {
|
|
// remember begining
|
|
first_bad = it;
|
|
}
|
|
if (start_it != pts.end()) {
|
|
// finish sampling
|
|
append(samples, sample(start_it, it, dist2));
|
|
// prepare for new start
|
|
start_it = pts.end();
|
|
}
|
|
} else if (start_it == pts.end()) {
|
|
start_it = it;
|
|
}
|
|
}
|
|
|
|
// sample last segment
|
|
if (start_it == pts.end()) { // tail is without points
|
|
if (first_bad != pts.begin()) // only begining
|
|
append(samples, sample(pts.begin(), first_bad, dist2));
|
|
} else { // tail contain points
|
|
if (first_bad == pts.begin()) { // only tail
|
|
append(samples, sample(start_it, pts.end(), dist2));
|
|
} else if (start_it == pts.begin()) { // whole polygon is overhang
|
|
assert(first_bad == pts.end());
|
|
Points pts2 = pts; // copy
|
|
pts2.push_back(pts.front());
|
|
append(samples, sample(pts2.begin(), pts2.end(), dist2));
|
|
} else { // need connect begining and tail
|
|
Points pts2;
|
|
pts2.reserve((pts.end() - start_it) +
|
|
(first_bad - pts.begin()));
|
|
for (auto it = start_it; it < pts.end(); ++it)
|
|
pts2.push_back(*it);
|
|
for (auto it = pts.begin(); it < first_bad; ++it)
|
|
pts2.push_back(*it);
|
|
append(samples, sample(pts2.begin(), pts2.end(), dist2));
|
|
}
|
|
}
|
|
};
|
|
|
|
Points samples;
|
|
for (const ExPolygon &overhang : overhangs) {
|
|
sample_overhang(overhang.contour, samples);
|
|
for (const Polygon &hole : overhang.holes) {
|
|
sample_overhang(hole, samples);
|
|
}
|
|
}
|
|
return samples;
|
|
}
|
|
|
|
coord_t calc_influence_radius(float z_distance, const SupportPointGeneratorConfig &config) {
|
|
float island_support_distance = config.support_curve.front().x() / config.density_relative;
|
|
if (z_distance >= island_support_distance)
|
|
return 0.f;
|
|
// IMPROVE: use curve interpolation instead of sqrt(stored in config).
|
|
|
|
// shape of supported area before permanent supports is sphere with radius of island_support_distance
|
|
return static_cast<coord_t>(scale_(
|
|
std::sqrt(sqr(island_support_distance) - sqr(z_distance))
|
|
));
|
|
}
|
|
|
|
void prepare_supports_for_layer(LayerSupportPoints &supports, float layer_z,
|
|
const SupportPointGeneratorConfig &config) {
|
|
auto set_radius = [&config](LayerSupportPoint &support, float radius) {
|
|
if (!is_approx(config.density_relative, 1.f, 1e-4f)) // exist relative density
|
|
radius /= config.density_relative;
|
|
support.current_radius = static_cast<coord_t>(scale_(radius));
|
|
};
|
|
|
|
const std::vector<Vec2f>& curve = config.support_curve;
|
|
// calculate support area for each support point as radius
|
|
// IMPROVE: use some offsets of previous supported island
|
|
for (LayerSupportPoint &support : supports) {
|
|
size_t &index = support.radius_curve_index;
|
|
if (index + 1 >= curve.size())
|
|
continue; // already contain maximal radius
|
|
|
|
// find current segment
|
|
float diff_z = layer_z - support.pos.z();
|
|
if (diff_z < 0.) {
|
|
// permanent support influence distribution of support points printed before.
|
|
support.current_radius = calc_influence_radius(-diff_z, config);
|
|
continue;
|
|
}
|
|
while ((index + 1) < curve.size() && diff_z > curve[index + 1].y())
|
|
++index;
|
|
|
|
if ((index+1) >= curve.size()) {
|
|
// set maximal radius
|
|
set_radius(support, curve.back().x());
|
|
continue;
|
|
}
|
|
// interpolate radius on input curve
|
|
Vec2f a = curve[index];
|
|
Vec2f b = curve[index+1];
|
|
assert(a.y() <= diff_z && diff_z <= b.y());
|
|
float t = (diff_z - a.y()) / (b.y() - a.y());
|
|
assert(0 <= t && t <= 1);
|
|
set_radius(support, a.x() + t * (b.x() - a.x()));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Near points do not have to contain support points out of part.
|
|
/// Due to be able support in same area again(overhang above another overhang)
|
|
/// Wanted Side effect, it supports thiny part of overhangs
|
|
/// </summary>
|
|
/// <param name="near_points"></param>
|
|
/// <param name="part"></param>
|
|
/// <param name="config"></param>
|
|
void remove_supports_out_of_part(NearPoints& near_points, const LayerPart &part,
|
|
const SupportPointGeneratorConfig &config) {
|
|
// Offsetting is made in data preparation - speed up caused by paralelization
|
|
//ExPolygons extend_shape = offset_ex(*part.shape, config.removing_delta, ClipperLib::jtSquare);
|
|
near_points.remove_out_of(part.extend_shape);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Detect existence of peninsula on current layer part
|
|
/// </summary>
|
|
/// <param name="part">IN/OUT island part containing peninsulas</param>
|
|
/// <param name="min_peninsula_width">minimal width of overhang to become peninsula</param>
|
|
/// <param name="self_supported_width">supported distance from mainland</param>
|
|
void create_peninsulas(LayerPart &part, const PrepareSupportConfig &config) {
|
|
assert(config.peninsula_min_width > config.peninsula_self_supported_width);
|
|
const Polygons below_polygons = get_polygons(part.prev_parts);
|
|
const Polygons below_expanded = expand(below_polygons, config.peninsula_min_width, ClipperLib::jtSquare);
|
|
const ExPolygon &part_shape = *part.shape;
|
|
ExPolygons over_peninsula = diff_ex(part_shape, below_expanded);
|
|
if (over_peninsula.empty())
|
|
return; // only tiny overhangs
|
|
|
|
Polygons below_self_supported = expand(below_polygons, config.peninsula_self_supported_width, ClipperLib::jtSquare);
|
|
// exist layer part over peninsula limit
|
|
ExPolygons peninsulas_shape = diff_ex(part_shape, below_self_supported);
|
|
|
|
// IMPROVE: Anotate source of diff by ClipperLib_Z
|
|
Lines below_lines = to_lines(below_self_supported);
|
|
auto get_angle = [](const Line &l) {
|
|
Point diff = l.b - l.a;
|
|
if (diff.x() < 0) // Only positive direction X
|
|
diff = -diff;
|
|
return atan2(diff.y(), diff.x());
|
|
};
|
|
std::vector<double> belowe_line_angle; // define direction of line with positive X
|
|
belowe_line_angle.reserve(below_lines.size());
|
|
for (const Line& l : below_lines)
|
|
belowe_line_angle.push_back(get_angle(l));
|
|
std::vector<size_t> idx(below_lines.size());
|
|
std::iota(idx.begin(), idx.end(), 0);
|
|
auto is_lower = [&belowe_line_angle](size_t i1, size_t i2) {
|
|
return belowe_line_angle[i1] < belowe_line_angle[i2]; };
|
|
std::sort(idx.begin(), idx.end(), is_lower);
|
|
|
|
// Check, wheather line exist in set of belowe lines
|
|
// True .. line exist in previous layer (or partialy overlap previous line), connection to land
|
|
// False .. line is made by border of current layer part(peninsula coast)
|
|
auto exist_belowe = [&get_angle, &idx, &is_lower, &below_lines, &belowe_line_angle]
|
|
(const Line &l) {
|
|
// allowed angle epsilon
|
|
const double angle_epsilon = 1e-3; // < 0.06 DEG
|
|
const double paralel_epsilon = scale_(1e-2); // 10 um
|
|
double angle = get_angle(l);
|
|
double low_angle = angle - angle_epsilon;
|
|
bool is_over = false;
|
|
if (low_angle <= -M_PI_2) {
|
|
low_angle += M_PI;
|
|
is_over = true;
|
|
}
|
|
double hi_angle = angle + angle_epsilon;
|
|
if (hi_angle >= M_PI_2) {
|
|
hi_angle -= M_PI;
|
|
is_over = true;
|
|
}
|
|
int mayorit_idx = 0;
|
|
if (Point d = l.a - l.b;
|
|
abs(d.x()) < abs(d.y()))
|
|
mayorit_idx = 1;
|
|
|
|
coord_t low = l.a[mayorit_idx];
|
|
coord_t high = l.b[mayorit_idx];
|
|
if (low > high)
|
|
std::swap(low, high);
|
|
|
|
auto is_lower_angle = [&belowe_line_angle](size_t index, double angle) {
|
|
return belowe_line_angle[index] < angle; };
|
|
auto it_idx = std::lower_bound(idx.begin(), idx.end(), low_angle, is_lower_angle);
|
|
if (it_idx == idx.end()) {
|
|
if (is_over) {
|
|
it_idx = idx.begin();
|
|
is_over = false;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
while (is_over || belowe_line_angle[*it_idx] < hi_angle) {
|
|
const Line &l2 = below_lines[*it_idx];
|
|
coord_t l2_low = l2.a[mayorit_idx];
|
|
coord_t l2_high = l2.b[mayorit_idx];
|
|
if (low > high)
|
|
std::swap(low, high);
|
|
if ((l2_high >= low && l2_low <= high) && (
|
|
((l2.a == l.a && l2.b == l.b) ||(l2.a == l.b && l2.b == l.a)) || // speed up - same line
|
|
l.perp_distance_to(l2.a) < paralel_epsilon)) // check distance of parallel lines
|
|
return true;
|
|
++it_idx;
|
|
if (it_idx == idx.end()){
|
|
if (is_over) {
|
|
it_idx = idx.begin();
|
|
is_over = false;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// anotate source of peninsula: overhang VS previous layer
|
|
for (const ExPolygon &peninsula : peninsulas_shape) {
|
|
// Check that peninsula is wide enough(min_peninsula_width)
|
|
if (intersection_ex(ExPolygons{peninsula}, over_peninsula).empty())
|
|
continue;
|
|
|
|
// need to know shape and edges of peninsula
|
|
Lines lines = to_lines(peninsula);
|
|
std::vector<bool> is_outline(lines.size());
|
|
// when line overlap with belowe lines it is not outline
|
|
for (size_t i = 0; i < lines.size(); i++)
|
|
is_outline[i] = !exist_belowe(lines[i]);
|
|
part.peninsulas.push_back(Peninsula{peninsula, is_outline});
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
#include "libslic3r/Execution/ExecutionSeq.hpp"
|
|
SupportPointGeneratorData Slic3r::sla::prepare_generator_data(
|
|
std::vector<ExPolygons> &&slices,
|
|
const std::vector<float> &heights,
|
|
const PrepareSupportConfig &config,
|
|
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);
|
|
|
|
// 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, &heights, throw_on_cancel](size_t layer_id) {
|
|
if ((layer_id % 128) == 0)
|
|
// Don't call the following function too often as it flushes
|
|
// CPU write caches due to synchronization primitves.
|
|
throw_on_cancel();
|
|
|
|
Layer &layer = result.layers[layer_id];
|
|
layer.print_z = heights[layer_id]; // copy
|
|
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 - only hangout part of expolygon could be known after linking
|
|
});
|
|
}
|
|
}, 4 /*gransize*/);
|
|
|
|
double sample_distance_in_um = scale_(config.discretize_overhang_step);
|
|
double sample_distance_in_um2 = sample_distance_in_um * sample_distance_in_um;
|
|
|
|
// Link parts by intersections
|
|
execution::for_each(ex_tbb, size_t(1), result.slices.size(),
|
|
[&result, sample_distance_in_um2, throw_on_cancel](size_t layer_id) {
|
|
if ((layer_id % 16) == 0)
|
|
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.push_back(it_below);
|
|
it_below->next_parts.push_back(it_above);
|
|
}
|
|
|
|
if (it_above->prev_parts.empty())
|
|
continue;
|
|
}
|
|
}, 8 /* gransize */);
|
|
|
|
// Sample overhangs part of island
|
|
execution::for_each(ex_tbb, size_t(1), result.slices.size(),
|
|
[&result, sample_distance_in_um2, throw_on_cancel](size_t layer_id) {
|
|
if ((layer_id % 32) == 0)
|
|
throw_on_cancel();
|
|
|
|
LayerParts &parts = result.layers[layer_id].parts;
|
|
for (auto it_part = parts.begin(); it_part < parts.end(); ++it_part) {
|
|
if (it_part->prev_parts.empty())
|
|
continue; // island
|
|
|
|
// IMPROVE: overhangs could be calculated with Z coordninate
|
|
// soo one will know source shape of point and do not have to search this
|
|
// information Get inspiration at
|
|
// https://github.com/Prusa-Development/PrusaSlicerPrivate/blob/e00c46f070ec3d6fc325640b0dd10511f8acf5f7/src/libslic3r/PerimeterGenerator.cpp#L399
|
|
it_part->samples = sample_overhangs(*it_part, sample_distance_in_um2);
|
|
}
|
|
}, 8 /* gransize */);
|
|
|
|
// Detect peninsula
|
|
execution::for_each(ex_tbb, size_t(1), result.slices.size(),
|
|
[&layers = result.layers, &config, throw_on_cancel](size_t layer_id) {
|
|
if ((layer_id % 32) == 0)
|
|
throw_on_cancel();
|
|
LayerParts &parts = layers[layer_id].parts;
|
|
for (auto it_part = parts.begin(); it_part < parts.end(); ++it_part) {
|
|
if (it_part->prev_parts.empty())
|
|
continue; // island
|
|
create_peninsulas(*it_part, config);
|
|
}
|
|
}, 8 /* gransize */);
|
|
|
|
// calc extended parts, more info PrepareSupportConfig::removing_delta
|
|
execution::for_each(ex_tbb, size_t(1), result.slices.size(),
|
|
[&layers = result.layers, delta = config.removing_delta, throw_on_cancel](size_t layer_id) {
|
|
if ((layer_id % 16) == 0)
|
|
throw_on_cancel();
|
|
LayerParts &parts = layers[layer_id].parts;
|
|
for (auto it_part = parts.begin(); it_part < parts.end(); ++it_part)
|
|
it_part->extend_shape = offset_ex(*it_part->shape, delta, ClipperLib::jtSquare);
|
|
}, 8 /* gransize */);
|
|
return result;
|
|
}
|
|
|
|
#include "libslic3r/NSVGUtils.hpp"
|
|
#include "libslic3r/Utils.hpp"
|
|
std::vector<Vec2f> load_curve_from_file() {
|
|
std::string filePath = Slic3r::resources_dir() + "/data/sla_support.svg";
|
|
EmbossShape::SvgFile svg_file{filePath};
|
|
NSVGimage *image = init_image(svg_file);
|
|
if (image == nullptr) {
|
|
// In test is not known resource_dir!
|
|
// File is not located soo return DEFAULT permanent radius 5mm is returned
|
|
return {Vec2f{5.f,.0f}, Vec2f{5.f, 1.f}};
|
|
}
|
|
for (NSVGshape *shape_ptr = image->shapes; shape_ptr != NULL; shape_ptr = shape_ptr->next) {
|
|
const NSVGshape &shape = *shape_ptr;
|
|
if (!(shape.flags & NSVG_FLAGS_VISIBLE)) continue; // is visible
|
|
if (shape.fill.type != NSVG_PAINT_NONE) continue; // is not used fill
|
|
if (shape.stroke.type == NSVG_PAINT_NONE) continue; // exist stroke
|
|
if (shape.strokeWidth < 1e-5f) continue; // is visible stroke width
|
|
if (shape.stroke.color != 4278190261) continue; // is red
|
|
|
|
// use only first path
|
|
const NSVGpath *path = shape.paths;
|
|
size_t count_points = path->npts;
|
|
assert(count_points > 1);
|
|
--count_points;
|
|
std::vector<Vec2f> points;
|
|
points.reserve(count_points/3+1);
|
|
points.push_back({path->pts[0], path->pts[1]});
|
|
for (size_t i = 0; i < count_points; i += 3) {
|
|
const float *p = &path->pts[i * 2];
|
|
points.push_back({p[6], p[7]});
|
|
}
|
|
assert(points.size() >= 2);
|
|
return points;
|
|
}
|
|
|
|
// red curve line is not found
|
|
assert(false);
|
|
return {};
|
|
}
|
|
|
|
#ifdef PERMANENT_SUPPORTS
|
|
// Processing permanent support points
|
|
// Permanent are manualy edited points by user
|
|
namespace {
|
|
|
|
size_t get_index_of_closest_part(const Point &coor, const LayerParts &parts, double max_allowed_distance_sq) {
|
|
size_t count_lines = 0;
|
|
std::vector<size_t> part_lines_ends;
|
|
part_lines_ends.reserve(parts.size());
|
|
for (const LayerPart &part : parts) {
|
|
count_lines += count_points(*part.shape);
|
|
part_lines_ends.push_back(count_lines);
|
|
}
|
|
Linesf lines;
|
|
lines.reserve(count_lines);
|
|
for (const LayerPart &part : parts)
|
|
append(lines, to_linesf({*part.shape}));
|
|
AABBTreeIndirect::Tree<2, double> tree =
|
|
AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
|
|
|
|
size_t line_idx = std::numeric_limits<size_t>::max();
|
|
Vec2d coor_d = coor.cast<double>();
|
|
Vec2d hit_point;
|
|
[[maybe_unused]] double distance_sq =
|
|
AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, coor_d, line_idx, hit_point);
|
|
|
|
if (distance_sq >= max_allowed_distance_sq) // point is farer than 1mm from any layer part
|
|
return parts.size(); // this support point should not be used any more
|
|
|
|
// Find part index of closest line
|
|
for (size_t part_index = 0; part_index < part_lines_ends.size(); ++part_index)
|
|
if (line_idx < part_lines_ends[part_index]) {
|
|
// check point lais inside prev or next part shape
|
|
// When assert appear check that part index is really the correct one
|
|
assert(union_ex(
|
|
get_polygons(parts[part_index].prev_parts),
|
|
get_polygons(parts[part_index].next_parts))[0].contains(coor));
|
|
return part_index;
|
|
}
|
|
|
|
assert(false); // not found
|
|
return parts.size();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Guess range of layers by its centers
|
|
/// NOTE: not valid range for variable layer height but divide space
|
|
/// </summary>
|
|
/// <param name="layers"></param>
|
|
/// <param name="layer_id"></param>
|
|
/// <returns>Range of layers</returns>
|
|
MinMax<float> get_layer_range(const Layers &layers, size_t layer_id) {
|
|
assert(layer_id < layers.size());
|
|
if (layer_id >= layers.size())
|
|
return MinMax<float>{0., 0.};
|
|
|
|
float print_z = layers[layer_id].print_z;
|
|
float min = (layer_id == 0) ? 0.f : (layers[layer_id - 1].print_z + print_z) / 2.f;
|
|
float max = ((layer_id + 1) < layers.size()) ?
|
|
(layers[layer_id + 1].print_z + print_z) / 2.f :
|
|
print_z + (print_z - min); // last layer guess height by prev layer center
|
|
return MinMax<float>{min, max};
|
|
}
|
|
|
|
size_t get_index_of_layer_part(const Point& coor, const LayerParts& parts, double max_allowed_distance_sq) {
|
|
size_t part_index = parts.size();
|
|
// find part for support point
|
|
for (const LayerPart &part : parts) {
|
|
if (part.shape_extent.contains(coor) && part.shape->contains(coor)) {
|
|
// parts do not overlap each other
|
|
assert(part_index >= parts.size());
|
|
part_index = &part - &parts.front();
|
|
}
|
|
}
|
|
if (part_index >= parts.size()) { // support point is not in any part
|
|
part_index = get_index_of_closest_part(coor, parts, max_allowed_distance_sq);
|
|
// if (part_index >= parts.size()) // support is too far from any part
|
|
}
|
|
return part_index;
|
|
}
|
|
|
|
LayerParts::const_iterator get_closest_part(const PartLinks &links, Vec2d &coor) {
|
|
if (links.size() == 1)
|
|
return links.front();
|
|
|
|
Point coor_p = coor.cast<coord_t>();
|
|
// Note: layer part MUST not overlap each other
|
|
for (const PartLink &link : links) {
|
|
LayerParts::const_iterator part_it = link;
|
|
if (part_it->shape_extent.contains(coor_p) &&
|
|
part_it->shape->contains(coor_p)) {
|
|
return part_it;
|
|
}
|
|
}
|
|
|
|
size_t count_lines = 0;
|
|
std::vector<size_t> part_lines_ends;
|
|
part_lines_ends.reserve(links.size());
|
|
for (const PartLink &link: links) {
|
|
count_lines += count_points(*link->shape);
|
|
part_lines_ends.push_back(count_lines);
|
|
}
|
|
Linesf lines;
|
|
lines.reserve(count_lines);
|
|
for (const PartLink &link : links)
|
|
append(lines, to_linesf({*link->shape}));
|
|
AABBTreeIndirect::Tree<2, double> tree =
|
|
AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
|
|
|
|
size_t line_idx = std::numeric_limits<size_t>::max();
|
|
Vec2d hit_point;
|
|
[[maybe_unused]] double distance_sq =
|
|
AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, coor, line_idx, hit_point);
|
|
|
|
// Find part index of closest line
|
|
for (size_t part_index = 0; part_index < part_lines_ends.size(); ++part_index) {
|
|
if (line_idx >= part_lines_ends[part_index])
|
|
continue;
|
|
|
|
// check point lais inside prev or next part shape
|
|
// When assert appear check that part index is really the correct one
|
|
assert(union_ex(
|
|
get_polygons(links[part_index]->prev_parts),
|
|
get_polygons(links[part_index]->next_parts))[0].contains(coor.cast<coord_t>()));
|
|
coor = hit_point; // update closest point
|
|
return links[part_index];
|
|
}
|
|
|
|
assert(false); // not found
|
|
return links.front();
|
|
}
|
|
|
|
struct PartId {
|
|
// index into layers
|
|
size_t layer_id;
|
|
// index into parts of the previously addresed layer.
|
|
size_t part_id;
|
|
};
|
|
|
|
/// <summary>
|
|
/// Dive into previous layers a trace influence over layer parts before support point
|
|
/// </summary>
|
|
/// <param name="part_id">Index of part that point will appear</param>
|
|
/// <param name="layer_id">Index of layer where point will appear</param>
|
|
/// <param name="p">Permanent support point</param>
|
|
/// <param name="layers">All layers</param>
|
|
/// <param name="config"></param>
|
|
/// <returns>First influence: Layer_index + Part_index</returns>
|
|
PartId get_index_of_first_influence(
|
|
const PartId& partid,
|
|
const SupportPoint &p,
|
|
const Point& coor,
|
|
const Layers &layers,
|
|
const SupportPointGeneratorConfig &config) {
|
|
// find layer part for support
|
|
|
|
float max_influence_distance = std::max(
|
|
2 * p.head_front_radius,
|
|
config.support_curve.front().x());
|
|
|
|
const LayerParts& parts = layers[partid.layer_id].parts;
|
|
LayerParts::const_iterator current_part_it = parts.cbegin() + partid.part_id;
|
|
LayerParts::const_iterator prev_part_it = current_part_it; // stop influence just before island
|
|
Vec2d coor_d = coor.cast<double>();
|
|
|
|
auto get_part_id = [&layers](size_t layer_index, const LayerParts::const_iterator& part_it) {
|
|
const LayerParts &parts = layers[layer_index].parts;
|
|
size_t part_index = part_it - parts.cbegin();
|
|
assert(part_index < parts.size());
|
|
return PartId{layer_index, part_index};
|
|
};
|
|
// Detect not propagate into island
|
|
// Island supports has different behavior
|
|
// p.pos.z() - p.head_front_radius >= layer.print_z
|
|
for (size_t i = 0; i <= partid.layer_id; ++i) {
|
|
size_t current_layer_id = partid.layer_id - i;
|
|
const Layer &layer = layers[current_layer_id];
|
|
float z_distance = p.pos.z() - layer.print_z;
|
|
if (z_distance >= max_influence_distance)
|
|
return get_part_id(current_layer_id, current_part_it); // above layer index
|
|
|
|
const PartLinks &prev_parts = current_part_it->prev_parts;
|
|
if (prev_parts.empty()){
|
|
// Island support
|
|
return (z_distance < p.head_front_radius) ?
|
|
get_part_id(current_layer_id, current_part_it) :
|
|
get_part_id(current_layer_id + 1, prev_part_it);
|
|
}
|
|
|
|
prev_part_it = current_part_it;
|
|
current_part_it = get_closest_part(prev_parts, coor_d);
|
|
}
|
|
|
|
// It is unreachable!
|
|
// The first layer is always island
|
|
assert(false);
|
|
return PartId{std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
|
|
}
|
|
|
|
struct PermanentSupport {
|
|
SupportPoints::const_iterator point_it; // reference to permanent
|
|
|
|
// Define wheere layer part when start influene support area
|
|
// When part is island also affect distribution of supports on island
|
|
PartId influence;
|
|
|
|
// Position of support point in layer
|
|
PartId part;
|
|
Point layer_position;
|
|
};
|
|
using PermanentSupports = std::vector<PermanentSupport>;
|
|
|
|
/// <summary>
|
|
/// Prepare permanent supports for layer's parts
|
|
/// </summary>
|
|
/// <param name="permanent_supports">Permanent supports</param>
|
|
/// <param name="layers">Define heights of layers</param>
|
|
/// <param name="config">Define how to propagate to previous layers</param>
|
|
/// <returns>Supoorts to add into layer parts</returns>
|
|
PermanentSupports prepare_permanent_supports(
|
|
const SupportPoints &permanent_supports,
|
|
const Layers &layers,
|
|
const SupportPointGeneratorConfig &config
|
|
) {
|
|
// How to propagate permanent support position into previous layers? and how deep? requirements
|
|
// are chained. IMHO it should start togetjer from islands and permanent than propagate over surface
|
|
|
|
// permanent supports MUST be sorted by z
|
|
assert(std::is_sorted(permanent_supports.begin(), permanent_supports.end(),
|
|
[](const SupportPoint &a, const SupportPoint &b) { return a.pos.z() < b.pos.z(); }));
|
|
|
|
size_t permanent_index = 0;
|
|
PermanentSupports result;
|
|
for (size_t layer_id = 0; layer_id < layers.size(); ++layer_id) {
|
|
float layer_max_z = get_layer_range(layers, layer_id).max;
|
|
if (permanent_index >= permanent_supports.size())
|
|
break; // no more permanent supports
|
|
|
|
if (permanent_supports[permanent_index].pos.z() >= layer_max_z)
|
|
continue; // no permanent support for this layer
|
|
|
|
const Layer &layer = layers[layer_id];
|
|
for (; permanent_index < permanent_supports.size(); ++permanent_index) {
|
|
SupportPoints::const_iterator point_it = permanent_supports.begin()+permanent_index;
|
|
if (point_it->pos.z() > layer_max_z)
|
|
// support point belongs to another layer
|
|
// Points are sorted by z
|
|
break;
|
|
|
|
// find layer part for support
|
|
Point coor(static_cast<coord_t>(scale_(point_it->pos.x())),
|
|
static_cast<coord_t>(scale_(point_it->pos.y())));
|
|
|
|
double allowed_distance_sq = std::max(config.max_allowed_distance_sq,
|
|
sqr(scale_(point_it->head_front_radius)));
|
|
size_t part_index = get_index_of_layer_part(coor, layer.parts, allowed_distance_sq);
|
|
if (part_index >= layer.parts.size())
|
|
continue; // support point is not in any part
|
|
|
|
PartId part_id{layer_id, part_index};
|
|
// find part of first inlfuenced layer and part for this support point
|
|
PartId influence = get_index_of_first_influence(part_id, *point_it, coor, layers, config);
|
|
result.push_back(PermanentSupport{point_it, influence, part_id, coor});
|
|
}
|
|
}
|
|
|
|
// sort by layer index and part index
|
|
std::sort(result.begin(), result.end(), [](const PermanentSupport& s1, const PermanentSupport& s2) {
|
|
return s1.influence.layer_id != s2.influence.layer_id ?
|
|
s1.influence.layer_id < s2.influence.layer_id :
|
|
s1.influence.part_id < s2.influence.part_id; });
|
|
|
|
return result;
|
|
}
|
|
|
|
bool exist_permanent_support(const PermanentSupports& supports, size_t current_support_index,
|
|
size_t layer_index, size_t part_index) {
|
|
if (current_support_index >= supports.size())
|
|
return false;
|
|
|
|
const PartId &influence = supports[current_support_index].influence;
|
|
assert(influence.layer_id >= layer_index);
|
|
return influence.layer_id == layer_index &&
|
|
influence.part_id == part_index;
|
|
}
|
|
|
|
/// <summary>
|
|
/// copy permanent supports into near points
|
|
/// </summary>
|
|
/// <param name="near_points">OUTPUT for all permanent supports for this layer and part</param>
|
|
/// <param name="supports">Copied from</param>
|
|
/// <param name="support_index">current index into supports</param>
|
|
/// <param name="layer_index">current layer index</param>
|
|
/// <param name="part_index">current part index</param>
|
|
void copy_permanent_supports(NearPoints& near_points, const PermanentSupports& supports, size_t& support_index,
|
|
float print_z, size_t layer_index, size_t part_index, const SupportPointGeneratorConfig &config) {
|
|
while (exist_permanent_support(supports, support_index, layer_index, part_index)) {
|
|
const PermanentSupport &support = supports[support_index];
|
|
near_points.add(LayerSupportPoint{
|
|
/* SupportPoint */ *support.point_it,
|
|
/* position_on_layer */ support.layer_position,
|
|
/* radius_curve_index */ 0, // before support point - earlier influence on point distribution
|
|
/* current_radius */ calc_influence_radius(fabs(support.point_it->pos.z() - print_z), config)
|
|
});
|
|
++support_index;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
#endif // PERMANENT_SUPPORTS
|
|
|
|
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<double>(layers.size());
|
|
double status = 0; // current progress
|
|
int status_int = 0;
|
|
|
|
// Hack to set curve for testing
|
|
if (config.support_curve.empty())
|
|
const_cast<SupportPointGeneratorConfig &>(config).support_curve = load_curve_from_file();
|
|
|
|
// Maximal radius of supported area of one support point
|
|
double max_support_radius = config.support_curve.back().x();
|
|
// check distance to nearest support points from grid
|
|
coord_t maximal_radius = static_cast<coord_t>(scale_(max_support_radius));
|
|
|
|
// Storage for support points used by grid
|
|
LayerSupportPoints result;
|
|
|
|
#ifdef PERMANENT_SUPPORTS
|
|
// Index into data.permanent_supports
|
|
size_t permanent_index = 0;
|
|
PermanentSupports permanent_supports =
|
|
prepare_permanent_supports(data.permanent_supports, layers, config);
|
|
#endif // PERMANENT_SUPPORTS
|
|
|
|
// grid index == part in layer index
|
|
std::vector<NearPoints> 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];
|
|
prepare_supports_for_layer(result, layer.print_z, config);
|
|
|
|
// grid index == part in layer index
|
|
std::vector<NearPoints> grids;
|
|
grids.reserve(layer.parts.size());
|
|
|
|
for (const LayerPart &part : layer.parts) {
|
|
if (part.prev_parts.empty()) { // Island ?
|
|
// only island add new grid
|
|
grids.emplace_back(&result);
|
|
// new island - needs support no doubt
|
|
support_island(part, grids.back(), layer.print_z, config);
|
|
continue;
|
|
}
|
|
|
|
// first layer should have empty prev_part
|
|
assert(layer_id != 0);
|
|
const LayerParts &prev_layer_parts = layers[layer_id - 1].parts;
|
|
NearPoints near_points = create_near_points(prev_layer_parts, part, prev_grids);
|
|
remove_supports_out_of_part(near_points, part, config);
|
|
#ifdef PERMANENT_SUPPORTS
|
|
size_t part_id = &part - &layer.parts.front();
|
|
copy_permanent_supports(near_points, permanent_supports, permanent_index, layer.print_z, layer_id, part_id, config);
|
|
#endif // PERMANENT_SUPPORTS
|
|
if (!part.peninsulas.empty())
|
|
support_peninsulas(part.peninsulas, near_points, layer.print_z, config);
|
|
support_part_overhangs(part, config, near_points, layer.print_z, maximal_radius);
|
|
grids.push_back(std::move(near_points));
|
|
}
|
|
prev_grids = std::move(grids);
|
|
|
|
throw_on_cancel();
|
|
|
|
int old_status_int = status_int;
|
|
status += increment;
|
|
status_int = static_cast<int>(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<SupportPoint>(p));
|
|
|
|
// 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)
|
|
{
|
|
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();
|
|
|
|
Vec3f& p = pts[idx].pos;
|
|
Vec3d p_double = p.cast<double>();
|
|
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<float>(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<float>::epsilon()) return; // correct coordinate
|
|
p = closest_point.cast<float>();
|
|
}, 64 /* gransize */);
|
|
return pts;
|
|
}
|