Recursive pruning. Some more unit tests

This commit is contained in:
Alessandro Ranellucci 2014-03-09 17:46:02 +01:00
parent 33da6adc3c
commit 2a73ab988f
7 changed files with 86 additions and 98 deletions

View file

@ -223,11 +223,13 @@ sub make_perimeters {
$self->perimeters->append(@loops);
# process thin walls by collapsing slices to single passes
# the following offset2 ensures nothing in @thin_walls is narrower than $pwidth/10
@thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$pwidth/10, +$pwidth/10)};
# the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
# (actually, something larger than that still may exist due to mitering or other causes)
my $min_width = $pwidth / 4;
@thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$min_width/2, +$min_width/2)};
if (@thin_walls) {
# the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
my @p = map @{$_->medial_axis($pwidth + $pspacing)}, @thin_walls;
my @p = map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls;
if (0) {
require "Slic3r/SVG.pm";
@ -241,9 +243,7 @@ sub make_perimeters {
}
my @paths = ();
my $min_thin_wall_length = 2*$pwidth;
for my $p (@p) {
next if $p->length < $min_thin_wall_length;
my %params = (
role => EXTR_ROLE_EXTERNAL_PERIMETER,
mm3_per_mm => $mm3_per_mm,

View file

@ -1,4 +1,4 @@
use Test::More tests => 9;
use Test::More tests => 12;
use strict;
use warnings;
@ -9,9 +9,9 @@ BEGIN {
use Slic3r;
use List::Util qw(first);
use Slic3r::Geometry qw(epsilon scale unscale);
use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon Y);
use Slic3r::Test;
goto TTT;
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('layer_height', 0.2);
@ -59,20 +59,20 @@ goto TTT;
[160, 140],
);
my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square);
my $res = $expolygon->medial_axis(scale 10);
my $res = $expolygon->medial_axis(scale 1, scale 0.5);
is scalar(@$res), 1, 'medial axis of a square shape is a single closed loop';
ok $res->[0]->length > $hole_in_square->length && $res->[0]->length < $square->length,
'medial axis loop has reasonable length';
}
TTT: {
{
my $expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_scale(
[100, 100],
[120, 100],
[120, 200],
[100, 200],
));
my $res = $expolygon->medial_axis(scale 10);
my $res = $expolygon->medial_axis(scale 1, scale 0.5);
is scalar(@$res), 1, 'medial axis of a narrow rectangle is a single line';
ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length';
@ -83,13 +83,11 @@ TTT: {
[105, 200], # extra point in the short side
[100, 200],
));
my $res2 = $expolygon->medial_axis(scale 10);
use Slic3r::SVG;
Slic3r::SVG::output(
"thin.svg",
expolygons => [$expolygon],
polylines => $res2,
);
my $res2 = $expolygon->medial_axis(scale 1, scale 0.5);
is scalar(@$res), 1, 'medial axis of a narrow rectangle with an extra vertex is still a single line';
ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has still a reasonable length';
ok !(grep { abs($_ - scale 150) < scaled_epsilon } map $_->[Y], map @$_, @$res2), "extra vertices don't influence medial axis";
}
{
@ -99,7 +97,7 @@ TTT: {
[112, 200],
[108, 200],
));
my $res = $expolygon->medial_axis(scale 10);
my $res = $expolygon->medial_axis(scale 1, scale 0.5);
is scalar(@$res), 1, 'medial axis of a narrow trapezoid is a single line';
ok unscale($res->[0]->length) >= (200-100 - (120-100)) - epsilon, 'medial axis has reasonable length';
}
@ -113,19 +111,10 @@ TTT: {
[200, 200],
[100, 200],
));
my $res = $expolygon->medial_axis(scale 20);
my $res = $expolygon->medial_axis(scale 1, scale 0.5);
is scalar(@$res), 1, 'medial axis of a L shape is a single polyline';
my $len = unscale($res->[0]->length) + 20; # 20 is the thickness of the expolygon, which is subtracted from the ends
ok $len > 80*2 && $len < 100*2, 'medial axis has reasonable length';
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"thin.svg",
expolygons => [$expolygon],
polylines => $res,
);
}
}
__END__

View file

@ -135,19 +135,15 @@ ExPolygon::simplify(double tolerance, ExPolygons &expolygons) const
}
void
ExPolygon::medial_axis(double width, Polylines* polylines) const
ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines) const
{
// init helper object
Slic3r::Geometry::MedialAxis ma(width);
Slic3r::Geometry::MedialAxis ma(max_width, min_width);
// populate list of segments for the Voronoi diagram
ExPolygons expp;
this->simplify(scale_(0.01), expp);
for (ExPolygons::const_iterator expolygon = expp.begin(); expolygon != expp.end(); ++expolygon) {
expolygon->contour.lines(&ma.lines);
for (Polygons::const_iterator hole = expolygon->holes.begin(); hole != expolygon->holes.end(); ++hole)
hole->lines(&ma.lines);
}
this->contour.lines(&ma.lines);
for (Polygons::const_iterator hole = this->holes.begin(); hole != this->holes.end(); ++hole)
hole->lines(&ma.lines);
// compute the Voronoi diagram
ma.build(polylines);

View file

@ -26,7 +26,7 @@ class ExPolygon
Polygons simplify_p(double tolerance) const;
ExPolygons simplify(double tolerance) const;
void simplify(double tolerance, ExPolygons &expolygons) const;
void medial_axis(double width, Polylines* polylines) const;
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
#ifdef SLIC3RXS
void from_SV(SV* poly_sv);

View file

@ -119,68 +119,63 @@ MedialAxis::build(Polylines* polylines)
// note: this keeps twins, so it contains twice the number of the valid edges
this->edges.clear();
for (VD::const_edge_iterator edge = this->vd.edges().begin(); edge != this->vd.edges().end(); ++edge) {
if (this->is_valid_edge(*edge)) this->edges.insert(&*edge);
// if we only process segments representing closed loops, none if the
// infinite edges (if any) would be part of our MAT anyway
if (edge->is_secondary() || edge->is_infinite()) continue;
this->edges.insert(&*edge);
}
// count valid segments for each vertex
std::map< const VD::vertex_type*,std::list<const VD::edge_type*> > vertex_edges;
std::list<const VD::vertex_type*> entry_nodes;
std::map< const VD::vertex_type*,std::set<const VD::edge_type*> > vertex_edges;
std::set<const VD::vertex_type*> entry_nodes;
for (VD::const_vertex_iterator vertex = this->vd.vertices().begin(); vertex != this->vd.vertices().end(); ++vertex) {
// get a reference to the list of valid edges originating from this vertex
std::list<const VD::edge_type*>& edges = vertex_edges[&*vertex];
std::set<const VD::edge_type*>& edges = vertex_edges[&*vertex];
// get one random edge originating from this vertex
const VD::edge_type* edge = vertex->incident_edge();
do {
if (this->edges.count(edge) > 0) // only count valid edges
edges.push_back(edge);
edges.insert(edge);
edge = edge->rot_next(); // next edge originating from this vertex
} while (edge != vertex->incident_edge());
// if there's only one edge starting at this vertex then it's a leaf
if (edges.size() == 1) entry_nodes.push_back(&*vertex);
size_t edge_count = edges.size();
if (edge_count == 1) {
entry_nodes.insert(&*vertex);
}
}
// iterate through the leafs to prune short branches
for (std::list<const VD::vertex_type*>::const_iterator vertex = entry_nodes.begin(); vertex != entry_nodes.end(); ++vertex) {
const VD::vertex_type* v = *vertex;
// prune recursively
while (!entry_nodes.empty()) {
// get a random entry node
const VD::vertex_type* v = *entry_nodes.begin();
// get edge starting from v
assert(!vertex_edges[v].empty());
const VD::edge_type* edge = *vertex_edges[v].begin();
// start a polyline from this vertex
Polyline polyline;
polyline.points.push_back(Point(v->x(), v->y()));
// keep track of visited edges to prevent infinite loops
std::set<const VD::edge_type*> visited_edges;
do {
// get edge starting from v
const VD::edge_type* edge = vertex_edges[v].front();
if (!this->is_valid_edge(*edge)) {
// if edge is not valid, erase it from edge list
(void)this->edges.erase(edge);
(void)this->edges.erase(edge->twin());
// if we picked the edge going backwards (thus the twin of the previous edge)
if (visited_edges.count(edge->twin()) > 0) {
edge = vertex_edges[v].back();
}
// decrement edge counters for the affected nodes
const VD::vertex_type* v1 = edge->vertex1();
(void)vertex_edges[v].erase(edge);
(void)vertex_edges[v1].erase(edge->twin());
// avoid getting twice on the same edge
if (visited_edges.count(edge) > 0) break;
visited_edges.insert(edge);
// get ending vertex for this edge and append it to the polyline
v = edge->vertex1();
polyline.points.push_back(Point( v->x(), v->y() ));
// if two edges start at this vertex (one forward one backwards) then
// it's not branching and we can go on
} while (vertex_edges[v].size() == 2);
// if this branch is too short, invalidate all of its edges so that
// they will be ignored when building actual polylines in the loop below
if (polyline.length() < this->width) {
for (std::set<const VD::edge_type*>::const_iterator edge = visited_edges.begin(); edge != visited_edges.end(); ++edge) {
(void)this->edges.erase(*edge);
(void)this->edges.erase((*edge)->twin());
// also, check whether the end vertex is a new leaf
if (vertex_edges[v1].size() == 1) {
entry_nodes.insert(v1);
} else if (vertex_edges[v1].empty()) {
entry_nodes.erase(v1);
}
}
// remove node from the set to prevent it from being visited again
entry_nodes.erase(v);
}
// iterate through the valid edges to build polylines
@ -204,8 +199,9 @@ MedialAxis::build(Polylines* polylines)
this->process_edge_neighbors(*edge.twin(), &pp);
polyline.points.insert(polyline.points.begin(), pp.rbegin(), pp.rend());
// append polyline to result
polylines->push_back(polyline);
// append polyline to result if it's not too small
if (polyline.length() > this->max_width)
polylines->push_back(polyline);
}
}
@ -236,10 +232,6 @@ MedialAxis::process_edge_neighbors(const VD::edge_type& edge, Points* points)
bool
MedialAxis::is_valid_edge(const VD::edge_type& edge) const
{
// if we only process segments representing closed loops, none if the
// infinite edges (if any) would be part of our MAT anyway
if (edge.is_secondary() || edge.is_infinite()) return false;
/* If the cells sharing this edge have a common vertex, we're not interested
in this edge. Why? Because it means that the edge lies on the bisector of
two contiguous input lines and it was included in the Voronoi graph because
@ -264,7 +256,7 @@ MedialAxis::is_valid_edge(const VD::edge_type& edge) const
// so we allow some tolerance (say, 30°)
if (fabs(angle) < PI - PI/3) return false;
/*
// each vertex is equidistant to both cell segments
// but such distance might differ between the two vertices;
// in this case it means the shape is getting narrow (like a corner)
@ -274,19 +266,28 @@ MedialAxis::is_valid_edge(const VD::edge_type& edge) const
Point v1( edge.vertex1()->x(), edge.vertex1()->y() );
double dist0 = v0.distance_to(segment1);
double dist1 = v1.distance_to(segment1);
/*
double diff = fabs(dist1 - dist0);
double dist_between_segments1 = segment1.a.distance_to(segment2);
double dist_between_segments2 = segment1.b.distance_to(segment2);
printf("w = %f, dist0 = %f, dist1 = %f, diff = %f, seglength = %f, edgelen = %f, s2s = %f / %f\n",
unscale(this->width),
unscale(dist0), unscale(dist1), unscale(diff), unscale(segment1.length()),
printf("w = %f/%f, dist0 = %f, dist1 = %f, diff = %f, seg1len = %f, seg2len = %f, edgelen = %f, s2s = %f / %f\n",
unscale(this->max_width), unscale(this->min_width),
unscale(dist0), unscale(dist1), unscale(diff),
unscale(segment1.length()), unscale(segment2.length()),
unscale(this->edge_to_line(edge).length()),
unscale(dist_between_segments1), unscale(dist_between_segments2)
);
if (dist0 < SCALED_EPSILON && dist1 < SCALED_EPSILON) {
printf(" => too thin, skipping\n");
//return false;
*/
// if this segment is the centerline for a very thin area, we might want to skip it
// in case the area is too thin
if (dist0 < this->min_width/2 || dist1 < this->min_width/2) {
//printf(" => too thin, skipping\n");
return false;
}
/*
// if distance between this edge and the thin area boundary is greater
// than half the max width, then it's not a true medial axis segment
if (dist1 > this->width*2) {
@ -295,9 +296,10 @@ MedialAxis::is_valid_edge(const VD::edge_type& edge) const
}
*/
return true;
}
return true;
return false;
}
Line

View file

@ -20,8 +20,9 @@ class MedialAxis {
public:
Points points;
Lines lines;
double width;
MedialAxis(double _width) : width(_width) {};
double max_width;
double min_width;
MedialAxis(double _max_width, double _min_width) : max_width(_max_width), min_width(_min_width) {};
void build(Polylines* polylines);
private:

View file

@ -25,8 +25,8 @@
bool contains_point(Point* point);
ExPolygons simplify(double tolerance);
Polygons simplify_p(double tolerance);
Polylines medial_axis(double width)
%code{% THIS->medial_axis(width, &RETVAL); %};
Polylines medial_axis(double max_width, double min_width)
%code{% THIS->medial_axis(max_width, min_width, &RETVAL); %};
%{
ExPolygon*