Improvements in performance of Medial Axis algorithm.
Fixes Slicing slows or hangs on "Generating Permiters 20%" cpu load is at 100% #8164 Fixes Slicing hangs on generating perimeters with thing:3565827 (30g) #3259
This commit is contained in:
parent
0a84421ea4
commit
fe51f77839
@ -206,12 +206,10 @@ void ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const
|
|||||||
append(*expolygons, this->simplify(tolerance));
|
append(*expolygons, this->simplify(tolerance));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* polylines) const
|
||||||
ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const
|
|
||||||
{
|
{
|
||||||
// init helper object
|
// init helper object
|
||||||
Slic3r::Geometry::MedialAxis ma(max_width, min_width, this);
|
Slic3r::Geometry::MedialAxis ma(min_width, max_width, *this);
|
||||||
ma.lines = this->lines();
|
|
||||||
|
|
||||||
// compute the Voronoi diagram and extract medial axis polylines
|
// compute the Voronoi diagram and extract medial axis polylines
|
||||||
ThickPolylines pp;
|
ThickPolylines pp;
|
||||||
@ -318,11 +316,10 @@ ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polyl
|
|||||||
polylines->insert(polylines->end(), pp.begin(), pp.end());
|
polylines->insert(polylines->end(), pp.begin(), pp.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ExPolygon::medial_axis(double min_width, double max_width, Polylines* polylines) const
|
||||||
ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const
|
|
||||||
{
|
{
|
||||||
ThickPolylines tp;
|
ThickPolylines tp;
|
||||||
this->medial_axis(max_width, min_width, &tp);
|
this->medial_axis(min_width, max_width, &tp);
|
||||||
polylines->insert(polylines->end(), tp.begin(), tp.end());
|
polylines->insert(polylines->end(), tp.begin(), tp.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,10 +70,10 @@ public:
|
|||||||
Polygons simplify_p(double tolerance) const;
|
Polygons simplify_p(double tolerance) const;
|
||||||
ExPolygons simplify(double tolerance) const;
|
ExPolygons simplify(double tolerance) const;
|
||||||
void simplify(double tolerance, ExPolygons* expolygons) const;
|
void simplify(double tolerance, ExPolygons* expolygons) const;
|
||||||
void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const;
|
void medial_axis(double min_width, double max_width, ThickPolylines* polylines) const;
|
||||||
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
|
void medial_axis(double min_width, double max_width, Polylines* polylines) const;
|
||||||
Polylines medial_axis(double max_width, double min_width) const
|
Polylines medial_axis(double min_width, double max_width) const
|
||||||
{ Polylines out; this->medial_axis(max_width, min_width, &out); return out; }
|
{ Polylines out; this->medial_axis(min_width, max_width, &out); return out; }
|
||||||
Lines lines() const;
|
Lines lines() const;
|
||||||
|
|
||||||
// Number of contours (outer contour with holes).
|
// Number of contours (outer contour with holes).
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "MedialAxis.hpp"
|
#include "MedialAxis.hpp"
|
||||||
|
|
||||||
#include "clipper.hpp"
|
#include "clipper.hpp"
|
||||||
|
#include "VoronoiOffset.hpp"
|
||||||
|
|
||||||
#ifdef SLIC3R_DEBUG
|
#ifdef SLIC3R_DEBUG
|
||||||
namespace boost { namespace polygon {
|
namespace boost { namespace polygon {
|
||||||
@ -392,8 +393,7 @@ inline const typename VD::point_type retrieve_cell_point(const typename VD::cell
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename VD, typename SEGMENTS>
|
template<typename VD, typename SEGMENTS>
|
||||||
inline std::pair<typename VD::coord_type, typename VD::coord_type>
|
inline std::pair<typename VD::coord_type, typename VD::coord_type> measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments)
|
||||||
measure_edge_thickness(const VD &vd, const typename VD::edge_type& edge, const SEGMENTS &segments)
|
|
||||||
{
|
{
|
||||||
typedef typename VD::coord_type T;
|
typedef typename VD::coord_type T;
|
||||||
const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y());
|
const typename VD::point_type pa(edge.vertex0()->x(), edge.vertex0()->y());
|
||||||
@ -442,15 +442,21 @@ private:
|
|||||||
const Lines &lines;
|
const Lines &lines;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
MedialAxis::MedialAxis(double min_width, double max_width, const ExPolygon &expolygon) :
|
||||||
MedialAxis::build(ThickPolylines* polylines)
|
m_expolygon(expolygon), m_lines(expolygon.lines()), m_min_width(min_width), m_max_width(max_width)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void MedialAxis::build(ThickPolylines* polylines)
|
||||||
{
|
{
|
||||||
construct_voronoi(this->lines.begin(), this->lines.end(), &this->vd);
|
construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd);
|
||||||
|
Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines);
|
||||||
|
// static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
|
||||||
|
// std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// DEBUG: dump all Voronoi edges
|
// DEBUG: dump all Voronoi edges
|
||||||
{
|
{
|
||||||
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
|
for (VD::const_edge_iterator edge = m_vd.edges().begin(); edge != m_vd.edges().end(); ++edge) {
|
||||||
if (edge->is_infinite()) continue;
|
if (edge->is_infinite()) continue;
|
||||||
|
|
||||||
ThickPolyline polyline;
|
ThickPolyline polyline;
|
||||||
@ -463,73 +469,59 @@ MedialAxis::build(ThickPolylines* polylines)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//typedef const VD::vertex_type vert_t;
|
//typedef const VD::vertex_type vert_t;
|
||||||
typedef const VD::edge_type edge_t;
|
using edge_t = const VD::edge_type;
|
||||||
|
|
||||||
// collect valid edges (i.e. prune those not belonging to MAT)
|
// collect valid edges (i.e. prune those not belonging to MAT)
|
||||||
// note: this keeps twins, so it inserts twice the number of the valid edges
|
// note: this keeps twins, so it inserts twice the number of the valid edges
|
||||||
this->valid_edges.clear();
|
m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{});
|
||||||
{
|
for (VD::const_edge_iterator edge = m_vd.edges().begin(); edge != m_vd.edges().end(); edge += 2)
|
||||||
std::set<const VD::edge_type*> seen_edges;
|
if (edge->is_primary() && edge->is_finite() &&
|
||||||
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
|
(Voronoi::vertex_category(edge->vertex0()) == Voronoi::VertexCategory::Inside ||
|
||||||
// if we only process segments representing closed loops, none if the
|
Voronoi::vertex_category(edge->vertex1()) == Voronoi::VertexCategory::Inside) &&
|
||||||
// infinite edges (if any) would be part of our MAT anyway
|
this->validate_edge(&*edge)) {
|
||||||
if (edge->is_secondary() || edge->is_infinite()) continue;
|
// Valid skeleton edge.
|
||||||
|
this->edge_data(*edge).first.active = true;
|
||||||
// don't re-validate twins
|
|
||||||
if (seen_edges.find(&*edge) != seen_edges.end()) continue; // TODO: is this needed?
|
|
||||||
seen_edges.insert(&*edge);
|
|
||||||
seen_edges.insert(edge->twin());
|
|
||||||
|
|
||||||
if (!this->validate_edge(&*edge)) continue;
|
|
||||||
this->valid_edges.insert(&*edge);
|
|
||||||
this->valid_edges.insert(edge->twin());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
this->edges = this->valid_edges;
|
|
||||||
|
|
||||||
// iterate through the valid edges to build polylines
|
// iterate through the valid edges to build polylines
|
||||||
while (!this->edges.empty()) {
|
ThickPolyline reverse_polyline;
|
||||||
const edge_t* edge = *this->edges.begin();
|
for (VD::const_edge_iterator seed_edge = m_vd.edges().begin(); seed_edge != m_vd.edges().end(); seed_edge += 2)
|
||||||
|
if (EdgeData &seed_edge_data = this->edge_data(*seed_edge).first; seed_edge_data.active) {
|
||||||
|
// Mark this edge as visited.
|
||||||
|
seed_edge_data.active = false;
|
||||||
|
|
||||||
|
// Start a polyline.
|
||||||
|
ThickPolyline polyline;
|
||||||
|
polyline.points.emplace_back(seed_edge->vertex0()->x(), seed_edge->vertex0()->y());
|
||||||
|
polyline.points.emplace_back(seed_edge->vertex1()->x(), seed_edge->vertex1()->y());
|
||||||
|
polyline.width.emplace_back(seed_edge_data.width_start);
|
||||||
|
polyline.width.emplace_back(seed_edge_data.width_end);
|
||||||
|
// Grow the polyline in a forward direction.
|
||||||
|
this->process_edge_neighbors(&*seed_edge, &polyline);
|
||||||
|
|
||||||
// start a polyline
|
// Grow the polyline in a backward direction.
|
||||||
ThickPolyline polyline;
|
reverse_polyline.clear();
|
||||||
polyline.points.push_back(Point( edge->vertex0()->x(), edge->vertex0()->y() ));
|
this->process_edge_neighbors(seed_edge->twin(), &reverse_polyline);
|
||||||
polyline.points.push_back(Point( edge->vertex1()->x(), edge->vertex1()->y() ));
|
polyline.points.insert(polyline.points.begin(), reverse_polyline.points.rbegin(), reverse_polyline.points.rend());
|
||||||
polyline.width.push_back(this->thickness[edge].first);
|
polyline.width.insert(polyline.width.begin(), reverse_polyline.width.rbegin(), reverse_polyline.width.rend());
|
||||||
polyline.width.push_back(this->thickness[edge].second);
|
polyline.endpoints.first = reverse_polyline.endpoints.second;
|
||||||
|
|
||||||
// remove this edge and its twin from the available edges
|
assert(polyline.width.size() == polyline.points.size() * 2 - 2);
|
||||||
(void)this->edges.erase(edge);
|
|
||||||
(void)this->edges.erase(edge->twin());
|
|
||||||
|
|
||||||
// get next points
|
// Prevent loop endpoints from being extended.
|
||||||
this->process_edge_neighbors(edge, &polyline);
|
if (polyline.first_point() == polyline.last_point()) {
|
||||||
|
polyline.endpoints.first = false;
|
||||||
// get previous points
|
polyline.endpoints.second = false;
|
||||||
{
|
}
|
||||||
ThickPolyline rpolyline;
|
|
||||||
this->process_edge_neighbors(edge->twin(), &rpolyline);
|
// Append polyline to result.
|
||||||
polyline.points.insert(polyline.points.begin(), rpolyline.points.rbegin(), rpolyline.points.rend());
|
polylines->emplace_back(std::move(polyline));
|
||||||
polyline.width.insert(polyline.width.begin(), rpolyline.width.rbegin(), rpolyline.width.rend());
|
|
||||||
polyline.endpoints.first = rpolyline.endpoints.second;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(polyline.width.size() == polyline.points.size()*2 - 2);
|
|
||||||
|
|
||||||
// prevent loop endpoints from being extended
|
|
||||||
if (polyline.first_point() == polyline.last_point()) {
|
|
||||||
polyline.endpoints.first = false;
|
|
||||||
polyline.endpoints.second = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// append polyline to result
|
|
||||||
polylines->push_back(polyline);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef SLIC3R_DEBUG
|
#ifdef SLIC3R_DEBUG
|
||||||
{
|
{
|
||||||
static int iRun = 0;
|
static int iRun = 0;
|
||||||
dump_voronoi_to_svg(this->lines, this->vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str());
|
dump_voronoi_to_svg(m_lines, m_vd, polylines, debug_out_path("MedialAxis-%d.svg", iRun ++).c_str());
|
||||||
printf("Thick lines: ");
|
printf("Thick lines: ");
|
||||||
for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) {
|
for (ThickPolylines::const_iterator it = polylines->begin(); it != polylines->end(); ++ it) {
|
||||||
ThickLines lines = it->thicklines();
|
ThickLines lines = it->thicklines();
|
||||||
@ -542,56 +534,66 @@ MedialAxis::build(ThickPolylines* polylines)
|
|||||||
#endif /* SLIC3R_DEBUG */
|
#endif /* SLIC3R_DEBUG */
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void MedialAxis::build(Polylines* polylines)
|
||||||
MedialAxis::build(Polylines* polylines)
|
|
||||||
{
|
{
|
||||||
ThickPolylines tp;
|
ThickPolylines tp;
|
||||||
this->build(&tp);
|
this->build(&tp);
|
||||||
polylines->insert(polylines->end(), tp.begin(), tp.end());
|
polylines->insert(polylines->end(), tp.begin(), tp.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void MedialAxis::process_edge_neighbors(const VD::edge_type *edge, ThickPolyline* polyline)
|
||||||
MedialAxis::process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline)
|
|
||||||
{
|
{
|
||||||
while (true) {
|
for (;;) {
|
||||||
// Since rot_next() works on the edge starting point but we want
|
// Since rot_next() works on the edge starting point but we want
|
||||||
// to find neighbors on the ending point, we just swap edge with
|
// to find neighbors on the ending point, we just swap edge with
|
||||||
// its twin.
|
// its twin.
|
||||||
const VD::edge_type* twin = edge->twin();
|
const VD::edge_type *twin = edge->twin();
|
||||||
|
|
||||||
// count neighbors for this edge
|
// count neighbors for this edge
|
||||||
std::vector<const VD::edge_type*> neighbors;
|
size_t num_neighbors = 0;
|
||||||
for (const VD::edge_type* neighbor = twin->rot_next(); neighbor != twin;
|
const VD::edge_type *first_neighbor = nullptr;
|
||||||
neighbor = neighbor->rot_next()) {
|
for (const VD::edge_type *neighbor = twin->rot_next(); neighbor != twin; neighbor = neighbor->rot_next())
|
||||||
if (this->valid_edges.count(neighbor) > 0) neighbors.push_back(neighbor);
|
if (this->edge_data(*neighbor).first.active) {
|
||||||
}
|
if (num_neighbors == 0)
|
||||||
|
first_neighbor = neighbor;
|
||||||
|
++ num_neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
// if we have a single neighbor then we can continue recursively
|
// if we have a single neighbor then we can continue recursively
|
||||||
if (neighbors.size() == 1) {
|
if (num_neighbors == 1) {
|
||||||
const VD::edge_type* neighbor = neighbors.front();
|
if (std::pair<EdgeData&, bool> neighbor_data = this->edge_data(*first_neighbor);
|
||||||
|
neighbor_data.first.active) {
|
||||||
// break if this is a closed loop
|
neighbor_data.first.active = false;
|
||||||
if (this->edges.count(neighbor) == 0) return;
|
polyline->points.emplace_back(first_neighbor->vertex1()->x(), first_neighbor->vertex1()->y());
|
||||||
|
if (neighbor_data.second) {
|
||||||
Point new_point(neighbor->vertex1()->x(), neighbor->vertex1()->y());
|
polyline->width.push_back(neighbor_data.first.width_end);
|
||||||
polyline->points.push_back(new_point);
|
polyline->width.push_back(neighbor_data.first.width_start);
|
||||||
polyline->width.push_back(this->thickness[neighbor].first);
|
} else {
|
||||||
polyline->width.push_back(this->thickness[neighbor].second);
|
polyline->width.push_back(neighbor_data.first.width_start);
|
||||||
(void)this->edges.erase(neighbor);
|
polyline->width.push_back(neighbor_data.first.width_end);
|
||||||
(void)this->edges.erase(neighbor->twin());
|
}
|
||||||
edge = neighbor;
|
edge = first_neighbor;
|
||||||
} else if (neighbors.size() == 0) {
|
// Continue chaining.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (num_neighbors == 0) {
|
||||||
polyline->endpoints.second = true;
|
polyline->endpoints.second = true;
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
// T-shaped or star-shaped joint
|
// T-shaped or star-shaped joint
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// Stop chaining.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MedialAxis::validate_edge(const VD::edge_type* edge)
|
bool MedialAxis::validate_edge(const VD::edge_type* edge)
|
||||||
{
|
{
|
||||||
|
auto retrieve_segment = [this](const VD::cell_type* cell) -> const Line& { return m_lines[cell->source_index()]; };
|
||||||
|
auto retrieve_endpoint = [retrieve_segment](const VD::cell_type* cell) -> const Point& {
|
||||||
|
const Line &line = retrieve_segment(cell);
|
||||||
|
return cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT ? line.a : line.b;
|
||||||
|
};
|
||||||
|
|
||||||
// prevent overflows and detect almost-infinite edges
|
// prevent overflows and detect almost-infinite edges
|
||||||
#ifndef CLIPPERLIB_INT32
|
#ifndef CLIPPERLIB_INT32
|
||||||
if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
|
if (std::abs(edge->vertex0()->x()) > double(CLIPPER_MAX_COORD_UNSCALED) ||
|
||||||
@ -602,32 +604,18 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge)
|
|||||||
#endif // CLIPPERLIB_INT32
|
#endif // CLIPPERLIB_INT32
|
||||||
|
|
||||||
// construct the line representing this edge of the Voronoi diagram
|
// construct the line representing this edge of the Voronoi diagram
|
||||||
const Line line(
|
const Line line({ edge->vertex0()->x(), edge->vertex0()->y() },
|
||||||
Point( edge->vertex0()->x(), edge->vertex0()->y() ),
|
{ edge->vertex1()->x(), edge->vertex1()->y() });
|
||||||
Point( edge->vertex1()->x(), edge->vertex1()->y() )
|
|
||||||
);
|
|
||||||
|
|
||||||
// discard edge if it lies outside the supplied shape
|
|
||||||
// this could maybe be optimized (checking inclusion of the endpoints
|
|
||||||
// might give false positives as they might belong to the contour itself)
|
|
||||||
if (this->expolygon != NULL) {
|
|
||||||
if (line.a == line.b) {
|
|
||||||
// in this case, contains(line) returns a false positive
|
|
||||||
if (!this->expolygon->contains(line.a)) return false;
|
|
||||||
} else {
|
|
||||||
if (!this->expolygon->contains(line)) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve the original line segments which generated the edge we're checking
|
// retrieve the original line segments which generated the edge we're checking
|
||||||
const VD::cell_type* cell_l = edge->cell();
|
const VD::cell_type* cell_l = edge->cell();
|
||||||
const VD::cell_type* cell_r = edge->twin()->cell();
|
const VD::cell_type* cell_r = edge->twin()->cell();
|
||||||
const Line &segment_l = this->retrieve_segment(cell_l);
|
const Line &segment_l = retrieve_segment(cell_l);
|
||||||
const Line &segment_r = this->retrieve_segment(cell_r);
|
const Line &segment_r = retrieve_segment(cell_r);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
SVG svg("edge.svg");
|
SVG svg("edge.svg");
|
||||||
svg.draw(*this->expolygon);
|
svg.draw(m_expolygon);
|
||||||
svg.draw(line);
|
svg.draw(line);
|
||||||
svg.draw(segment_l, "red");
|
svg.draw(segment_l, "red");
|
||||||
svg.draw(segment_r, "blue");
|
svg.draw(segment_r, "blue");
|
||||||
@ -651,30 +639,30 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge)
|
|||||||
|
|
||||||
coordf_t w0 = cell_r->contains_segment()
|
coordf_t w0 = cell_r->contains_segment()
|
||||||
? segment_r.distance_to(line.a)*2
|
? segment_r.distance_to(line.a)*2
|
||||||
: (this->retrieve_endpoint(cell_r) - line.a).cast<double>().norm()*2;
|
: (retrieve_endpoint(cell_r) - line.a).cast<double>().norm()*2;
|
||||||
|
|
||||||
coordf_t w1 = cell_l->contains_segment()
|
coordf_t w1 = cell_l->contains_segment()
|
||||||
? segment_l.distance_to(line.b)*2
|
? segment_l.distance_to(line.b)*2
|
||||||
: (this->retrieve_endpoint(cell_l) - line.b).cast<double>().norm()*2;
|
: (retrieve_endpoint(cell_l) - line.b).cast<double>().norm()*2;
|
||||||
|
|
||||||
if (cell_l->contains_segment() && cell_r->contains_segment()) {
|
if (cell_l->contains_segment() && cell_r->contains_segment()) {
|
||||||
// calculate the relative angle between the two boundary segments
|
// calculate the relative angle between the two boundary segments
|
||||||
double angle = fabs(segment_r.orientation() - segment_l.orientation());
|
double angle = fabs(segment_r.orientation() - segment_l.orientation());
|
||||||
if (angle > PI) angle = 2*PI - angle;
|
if (angle > PI)
|
||||||
|
angle = 2. * PI - angle;
|
||||||
assert(angle >= 0 && angle <= PI);
|
assert(angle >= 0 && angle <= PI);
|
||||||
|
|
||||||
// fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction)
|
// fabs(angle) ranges from 0 (collinear, same direction) to PI (collinear, opposite direction)
|
||||||
// we're interested only in segments close to the second case (facing segments)
|
// we're interested only in segments close to the second case (facing segments)
|
||||||
// so we allow some tolerance.
|
// so we allow some tolerance.
|
||||||
// this filter ensures that we're dealing with a narrow/oriented area (longer than thick)
|
// this filter ensures that we're dealing with a narrow/oriented area (longer than thick)
|
||||||
// we don't run it on edges not generated by two segments (thus generated by one segment
|
// we don't run it on edges not generated by two segments (thus generated by one segment
|
||||||
// and the endpoint of another segment), since their orientation would not be meaningful
|
// and the endpoint of another segment), since their orientation would not be meaningful
|
||||||
if (PI - angle > PI/8) {
|
if (PI - angle > PI / 8.) {
|
||||||
// angle is not narrow enough
|
// angle is not narrow enough
|
||||||
|
|
||||||
// only apply this filter to segments that are not too short otherwise their
|
// only apply this filter to segments that are not too short otherwise their
|
||||||
// angle could possibly be not meaningful
|
// angle could possibly be not meaningful
|
||||||
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= this->min_width)
|
if (w0 < SCALED_EPSILON || w1 < SCALED_EPSILON || line.length() >= m_min_width)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -682,31 +670,17 @@ bool MedialAxis::validate_edge(const VD::edge_type* edge)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (w0 < this->min_width && w1 < this->min_width)
|
if ((w0 >= m_min_width || w1 >= m_min_width) &&
|
||||||
return false;
|
(w0 <= m_max_width || w1 <= m_max_width)) {
|
||||||
|
std::pair<EdgeData&, bool> ed = this->edge_data(*edge);
|
||||||
if (w0 > this->max_width && w1 > this->max_width)
|
if (ed.second)
|
||||||
return false;
|
std::swap(w0, w1);
|
||||||
|
ed.first.width_start = w0;
|
||||||
this->thickness[edge] = std::make_pair(w0, w1);
|
ed.first.width_end = w1;
|
||||||
this->thickness[edge->twin()] = std::make_pair(w1, w0);
|
return true;
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Line& MedialAxis::retrieve_segment(const VD::cell_type* cell) const
|
|
||||||
{
|
|
||||||
return this->lines[cell->source_index()];
|
|
||||||
}
|
|
||||||
|
|
||||||
const Point& MedialAxis::retrieve_endpoint(const VD::cell_type* cell) const
|
|
||||||
{
|
|
||||||
const Line& line = this->retrieve_segment(cell);
|
|
||||||
if (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
|
|
||||||
return line.a;
|
|
||||||
} else {
|
|
||||||
return line.b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} } // namespace Slicer::Geometry
|
} } // namespace Slicer::Geometry
|
||||||
|
@ -4,30 +4,43 @@
|
|||||||
#include "Voronoi.hpp"
|
#include "Voronoi.hpp"
|
||||||
#include "../ExPolygon.hpp"
|
#include "../ExPolygon.hpp"
|
||||||
|
|
||||||
namespace Slic3r { namespace Geometry {
|
namespace Slic3r::Geometry {
|
||||||
|
|
||||||
class MedialAxis {
|
class MedialAxis {
|
||||||
public:
|
public:
|
||||||
Lines lines;
|
MedialAxis(double min_width, double max_width, const ExPolygon &expolygon);
|
||||||
const ExPolygon* expolygon;
|
|
||||||
double max_width;
|
|
||||||
double min_width;
|
|
||||||
MedialAxis(double _max_width, double _min_width, const ExPolygon* _expolygon = NULL)
|
|
||||||
: expolygon(_expolygon), max_width(_max_width), min_width(_min_width) {};
|
|
||||||
void build(ThickPolylines* polylines);
|
void build(ThickPolylines* polylines);
|
||||||
void build(Polylines* polylines);
|
void build(Polylines* polylines);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Input
|
||||||
|
const ExPolygon &m_expolygon;
|
||||||
|
Lines m_lines;
|
||||||
|
// for filtering of the skeleton edges
|
||||||
|
double m_min_width;
|
||||||
|
double m_max_width;
|
||||||
|
|
||||||
|
// Voronoi Diagram.
|
||||||
using VD = VoronoiDiagram;
|
using VD = VoronoiDiagram;
|
||||||
VD vd;
|
VD m_vd;
|
||||||
std::set<const VD::edge_type*> edges, valid_edges;
|
|
||||||
std::map<const VD::edge_type*, std::pair<coordf_t,coordf_t> > thickness;
|
// Annotations of the VD skeleton edges.
|
||||||
|
struct EdgeData {
|
||||||
|
bool active { false };
|
||||||
|
double width_start { 0 };
|
||||||
|
double width_end { 0 };
|
||||||
|
};
|
||||||
|
// Returns a reference to EdgeData and a "reversed" boolean.
|
||||||
|
std::pair<EdgeData&, bool> edge_data(const VD::edge_type &edge) {
|
||||||
|
size_t edge_id = &edge - &m_vd.edges().front();
|
||||||
|
return { m_edge_data[edge_id / 2], (edge_id & 1) != 0 };
|
||||||
|
}
|
||||||
|
std::vector<EdgeData> m_edge_data;
|
||||||
|
|
||||||
void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline);
|
void process_edge_neighbors(const VD::edge_type* edge, ThickPolyline* polyline);
|
||||||
bool validate_edge(const VD::edge_type* edge);
|
bool validate_edge(const VD::edge_type* edge);
|
||||||
const Line& retrieve_segment(const VD::cell_type* cell) const;
|
|
||||||
const Point& retrieve_endpoint(const VD::cell_type* cell) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} } // namespace Slicer::Geometry
|
} // namespace Slicer::Geometry
|
||||||
|
|
||||||
#endif // slic3r_Geometry_MedialAxis_hpp_
|
#endif // slic3r_Geometry_MedialAxis_hpp_
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Polygon offsetting using Voronoi diagram prodiced by boost::polygon.
|
// Polygon offsetting using Voronoi diagram produced by boost::polygon.
|
||||||
|
|
||||||
#ifndef slic3r_VoronoiOffset_hpp_
|
#ifndef slic3r_VoronoiOffset_hpp_
|
||||||
#define slic3r_VoronoiOffset_hpp_
|
#define slic3r_VoronoiOffset_hpp_
|
||||||
|
@ -920,7 +920,7 @@ void PerimeterGenerator::process_classic(
|
|||||||
float(min_width / 2.));
|
float(min_width / 2.));
|
||||||
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
|
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
|
||||||
for (ExPolygon &ex : expp)
|
for (ExPolygon &ex : expp)
|
||||||
ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
|
ex.medial_axis(min_width, ext_perimeter_width + ext_perimeter_spacing2, &thin_walls);
|
||||||
}
|
}
|
||||||
if (params.spiral_vase && offsets.size() > 1) {
|
if (params.spiral_vase && offsets.size() > 1) {
|
||||||
// Remove all but the largest area polygon.
|
// Remove all but the largest area polygon.
|
||||||
@ -1066,7 +1066,7 @@ void PerimeterGenerator::process_classic(
|
|||||||
offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
|
offset2_ex(gaps, - float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
|
||||||
ThickPolylines polylines;
|
ThickPolylines polylines;
|
||||||
for (const ExPolygon &ex : gaps_ex)
|
for (const ExPolygon &ex : gaps_ex)
|
||||||
ex.medial_axis(max, min, &polylines);
|
ex.medial_axis(min, max, &polylines);
|
||||||
if (! polylines.empty()) {
|
if (! polylines.empty()) {
|
||||||
ExtrusionEntityCollection gap_fill;
|
ExtrusionEntityCollection gap_fill;
|
||||||
variable_width(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities);
|
variable_width(polylines, erGapFill, params.solid_infill_flow, gap_fill.entities);
|
||||||
|
@ -16,7 +16,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
|
|||||||
auto hole_in_square = Polygon::new_scale({ {140, 140}, {140, 160}, {160, 160}, {160, 140} });
|
auto hole_in_square = Polygon::new_scale({ {140, 140}, {140, 160}, {160, 160}, {160, 140} });
|
||||||
ExPolygon expolygon{ square, hole_in_square };
|
ExPolygon expolygon{ square, hole_in_square };
|
||||||
WHEN("Medial axis is extracted") {
|
WHEN("Medial axis is extracted") {
|
||||||
Polylines res = expolygon.medial_axis(scaled<double>(40.), scaled<double>(0.5));
|
Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(40.));
|
||||||
THEN("medial axis of a square shape is a single path") {
|
THEN("medial axis of a square shape is a single path") {
|
||||||
REQUIRE(res.size() == 1);
|
REQUIRE(res.size() == 1);
|
||||||
}
|
}
|
||||||
@ -32,7 +32,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
|
|||||||
GIVEN("narrow rectangle") {
|
GIVEN("narrow rectangle") {
|
||||||
ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 200}, {100, 200} }) };
|
ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 200}, {100, 200} }) };
|
||||||
WHEN("Medial axis is extracted") {
|
WHEN("Medial axis is extracted") {
|
||||||
Polylines res = expolygon.medial_axis(scaled<double>(20.), scaled<double>(0.5));
|
Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(20.));
|
||||||
THEN("medial axis of a narrow rectangle is a single line") {
|
THEN("medial axis of a narrow rectangle is a single line") {
|
||||||
REQUIRE(res.size() == 1);
|
REQUIRE(res.size() == 1);
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
|
|||||||
{100, 200}
|
{100, 200}
|
||||||
})};
|
})};
|
||||||
WHEN("Medial axis is extracted") {
|
WHEN("Medial axis is extracted") {
|
||||||
Polylines res = expolygon.medial_axis(scaled<double>(1.), scaled<double>(0.5));
|
Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(1.));
|
||||||
THEN("medial axis of a narrow rectangle with an extra vertex is still a single line") {
|
THEN("medial axis of a narrow rectangle with an extra vertex is still a single line") {
|
||||||
REQUIRE(res.size() == 1);
|
REQUIRE(res.size() == 1);
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
|
|||||||
{1687689,4235755},{1218962,3499999},{827499,2748020},{482284,1920196},{219954,1088186},{31126,236479},{0,0},{1005754,0}
|
{1687689,4235755},{1218962,3499999},{827499,2748020},{482284,1920196},{219954,1088186},{31126,236479},{0,0},{1005754,0}
|
||||||
}};
|
}};
|
||||||
WHEN("Medial axis is extracted") {
|
WHEN("Medial axis is extracted") {
|
||||||
Polylines res = expolygon.medial_axis(scaled<double>(1.324888), scaled<double>(0.25));
|
Polylines res = expolygon.medial_axis(scaled<double>(0.25), scaled<double>(1.324888));
|
||||||
THEN("medial axis of a semicircumference is a single line") {
|
THEN("medial axis of a semicircumference is a single line") {
|
||||||
REQUIRE(res.size() == 1);
|
REQUIRE(res.size() == 1);
|
||||||
}
|
}
|
||||||
@ -103,7 +103,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
|
|||||||
GIVEN("narrow trapezoid") {
|
GIVEN("narrow trapezoid") {
|
||||||
ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {112, 200}, {108, 200} }) };
|
ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {112, 200}, {108, 200} }) };
|
||||||
WHEN("Medial axis is extracted") {
|
WHEN("Medial axis is extracted") {
|
||||||
Polylines res = expolygon.medial_axis(scaled<double>(20.), scaled<double>(0.5));
|
Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(20.));
|
||||||
THEN("medial axis of a narrow trapezoid is a single line") {
|
THEN("medial axis of a narrow trapezoid is a single line") {
|
||||||
REQUIRE(res.size() == 1);
|
REQUIRE(res.size() == 1);
|
||||||
}
|
}
|
||||||
@ -115,7 +115,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
|
|||||||
GIVEN("L shape") {
|
GIVEN("L shape") {
|
||||||
ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 180}, {200, 180}, {200, 200}, {100, 200}, }) };
|
ExPolygon expolygon{ Polygon::new_scale({ {100, 100}, {120, 100}, {120, 180}, {200, 180}, {200, 200}, {100, 200}, }) };
|
||||||
WHEN("Medial axis is extracted") {
|
WHEN("Medial axis is extracted") {
|
||||||
Polylines res = expolygon.medial_axis(scaled<double>(20.), scaled<double>(0.5));
|
Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(20.));
|
||||||
THEN("medial axis of an L shape is a single line") {
|
THEN("medial axis of an L shape is a single line") {
|
||||||
REQUIRE(res.size() == 1);
|
REQUIRE(res.size() == 1);
|
||||||
}
|
}
|
||||||
@ -134,7 +134,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
|
|||||||
{-220815482,-37738966},{-221117540,-37738966},{-221117540,-51762024},{-203064906,-51762024},
|
{-220815482,-37738966},{-221117540,-37738966},{-221117540,-51762024},{-203064906,-51762024},
|
||||||
}};
|
}};
|
||||||
WHEN("Medial axis is extracted") {
|
WHEN("Medial axis is extracted") {
|
||||||
Polylines res = expolygon.medial_axis(819998., 102499.75);
|
Polylines res = expolygon.medial_axis(102499.75, 819998.);
|
||||||
THEN("medial axis is a single line") {
|
THEN("medial axis is a single line") {
|
||||||
REQUIRE(res.size() == 1);
|
REQUIRE(res.size() == 1);
|
||||||
}
|
}
|
||||||
@ -147,7 +147,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
|
|||||||
GIVEN("narrow triangle") {
|
GIVEN("narrow triangle") {
|
||||||
ExPolygon expolygon{ Polygon::new_scale({ {50, 100}, {1000, 102}, {50, 104} }) };
|
ExPolygon expolygon{ Polygon::new_scale({ {50, 100}, {1000, 102}, {50, 104} }) };
|
||||||
WHEN("Medial axis is extracted") {
|
WHEN("Medial axis is extracted") {
|
||||||
Polylines res = expolygon.medial_axis(scaled<double>(4.), scaled<double>(0.5));
|
Polylines res = expolygon.medial_axis(scaled<double>(0.5), scaled<double>(4.));
|
||||||
THEN("medial axis of a narrow triangle is a single line") {
|
THEN("medial axis of a narrow triangle is a single line") {
|
||||||
REQUIRE(res.size() == 1);
|
REQUIRE(res.size() == 1);
|
||||||
}
|
}
|
||||||
@ -159,7 +159,7 @@ SCENARIO("Medial Axis", "[ThinWalls]") {
|
|||||||
GIVEN("GH #2474") {
|
GIVEN("GH #2474") {
|
||||||
ExPolygon expolygon{{ {91294454,31032190},{11294481,31032190},{11294481,29967810},{44969182,29967810},{89909960,29967808},{91294454,29967808} }};
|
ExPolygon expolygon{{ {91294454,31032190},{11294481,31032190},{11294481,29967810},{44969182,29967810},{89909960,29967808},{91294454,29967808} }};
|
||||||
WHEN("Medial axis is extracted") {
|
WHEN("Medial axis is extracted") {
|
||||||
Polylines res = expolygon.medial_axis(1871238, 500000);
|
Polylines res = expolygon.medial_axis(500000, 1871238);
|
||||||
THEN("medial axis is a single line") {
|
THEN("medial axis is a single line") {
|
||||||
REQUIRE(res.size() == 1);
|
REQUIRE(res.size() == 1);
|
||||||
}
|
}
|
||||||
|
@ -1918,7 +1918,6 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]")
|
|||||||
Lines lines = to_lines(poly);
|
Lines lines = to_lines(poly);
|
||||||
construct_voronoi(lines.begin(), lines.end(), &vd);
|
construct_voronoi(lines.begin(), lines.end(), &vd);
|
||||||
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
|
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
|
||||||
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
|
|
||||||
static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
|
static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
|
||||||
std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);
|
std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user