New coverage detection for bridges. Includes implementation of ExPolygon::get_trapezoids()

visilibity
Alessandro Ranellucci 2014-04-24 13:43:24 +02:00
parent d458a7c4d2
commit 6201aacf88
21 changed files with 358 additions and 27 deletions

View File

@ -1,9 +1,9 @@
package Slic3r::Layer::BridgeDetector;
use Moo;
use List::Util qw(first sum max);
use List::Util qw(first sum max min);
use Slic3r::Geometry qw(PI unscale scaled_epsilon rad2deg epsilon);
use Slic3r::Geometry::Clipper qw(intersection_pl intersection_ex);
use Slic3r::Geometry::Clipper qw(intersection_pl intersection_ex union);
has 'expolygon' => (is => 'ro', required => 1);
has 'lower_slices' => (is => 'rw', required => 1); # ExPolygons or ExPolygonCollection
@ -153,4 +153,61 @@ sub detect_angle {
return $self->angle;
}
sub coverage {
my ($self, $angle) = @_;
if (!defined $angle) {
return [] if !defined($angle = $self->detect_angle);
}
# Clone our expolygon and rotate it so that we work with vertical lines.
my $expolygon = $self->expolygon->clone;
$expolygon->rotate(PI/2 - $angle, [0,0]);
# Outset the bridge expolygon by half the amount we used for detecting anchors;
# we'll use this one to generate our trapezoids and be sure that their vertices
# are inside the anchors and not on their contours leading to false negatives.
my $grown = $expolygon->offset_ex(+$self->extrusion_width/2);
# Compute trapezoids according to a vertical orientation
my $trapezoids = [ map @{$_->get_trapezoids(PI/2)}, @$grown ];
# get anchors and rotate them too
my $anchors = [ map $_->clone, @{$self->_anchors} ];
$_->rotate(PI/2 - $angle, [0,0]) for @$anchors;
my @covered = (); # polygons
foreach my $trapezoid (@$trapezoids) {
my @polylines = map $_->as_polyline, @{$trapezoid->lines};
my @supported = @{intersection_pl(\@polylines, [map @$_, @$anchors])};
if (@supported >= 2) {
push @covered, $trapezoid;
}
}
# merge trapezoids and rotate them back
my $coverage = union(\@covered);
$_->rotate(-(PI/2 - $angle), [0,0]) for @$coverage;
# intersect trapezoids with actual bridge area to remove extra margins
$coverage = intersection_ex($coverage, [ @{$self->expolygon} ]);
if (0) {
my @lines = map @{$_->lines}, @$trapezoids;
$_->rotate(-(PI/2 - $angle), [0,0]) for @lines;
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"coverage_" . rad2deg($angle) . ".svg",
expolygons => [$self->expolygon],
green_expolygons => $self->_anchors,
red_expolygons => $coverage,
lines => \@lines,
);
}
return $coverage;
}
1;

View File

@ -1,4 +1,4 @@
use Test::More tests => 6;
use Test::More tests => 12;
use strict;
use warnings;
@ -7,7 +7,7 @@ BEGIN {
use lib "$FindBin::Bin/../lib";
}
use List::Util qw(first);
use List::Util qw(first sum);
use Slic3r;
use Slic3r::Geometry qw(scale epsilon deg2rad rad2deg PI);
use Slic3r::Test;
@ -68,7 +68,10 @@ use Slic3r::Test;
}
sub check_angle {
my ($lower, $bridge, $expected, $tolerance) = @_;
my ($lower, $bridge, $expected, $tolerance, $expected_coverage) = @_;
$expected_coverage //= -1;
$expected_coverage = $bridge->area if $expected_coverage == -1;
my $bd = Slic3r::Layer::BridgeDetector->new(
expolygon => $bridge,
@ -78,6 +81,8 @@ sub check_angle {
$tolerance //= rad2deg($bd->resolution) + epsilon;
my $result = $bd->detect_angle;
my $coverage = $bd->coverage;
is sum(map $_->area, @$coverage), $expected_coverage, 'correct coverage area';
# our epsilon is equal to the steps used by the bridge detection algorithm
###use XXX; YYY [ rad2deg($result), $expected ];

View File

@ -24,7 +24,7 @@ my $build = Module::Build::WithXSpp->new(
# _GLIBCXX_USE_C99 : to get the long long type for g++
# HAS_BOOL : stops Perl/lib/CORE/handy.h from doing "# define bool char" for MSVC
# NOGDI : prevents inclusion of wingdi.h which defines functions Polygon() and Polyline() in global namespace
extra_compiler_flags => [qw(-D_GLIBCXX_USE_C99 -DHAS_BOOL -DNOGDI -DSLIC3RXS), ($ENV{SLIC3R_DEBUG} ? ' -DSLIC3R_DEBUG -g' : '')],
extra_compiler_flags => [qw(-D_GLIBCXX_USE_C99 -DHAS_BOOL -DNOGDI -DSLIC3RXS), ($ENV{SLIC3R_DEBUG} ? ' -DSLIC3R_DEBUG -g -ftemplate-backtrace-limit=0' : '')],
# Provides extra C typemaps that are auto-merged
extra_typemap_modules => {

View File

@ -47,7 +47,7 @@ ExPolygon::translate(double x, double y)
}
void
ExPolygon::rotate(double angle, Point* center)
ExPolygon::rotate(double angle, const Point &center)
{
contour.rotate(angle, center);
for (Polygons::iterator it = holes.begin(); it != holes.end(); ++it) {
@ -158,6 +158,24 @@ ExPolygon::medial_axis(double max_width, double min_width, Polylines* polylines)
intersection(*polylines, *this, *polylines);
}
void
ExPolygon::get_trapezoids(Polygons* polygons) const
{
ExPolygons expp;
expp.push_back(*this);
boost::polygon::get_trapezoids(*polygons, expp);
}
void
ExPolygon::get_trapezoids(Polygons* polygons, double angle) const
{
ExPolygon clone = *this;
clone.rotate(PI/2 - angle, Point(0,0));
clone.get_trapezoids(polygons);
for (Polygons::iterator polygon = polygons->begin(); polygon != polygons->end(); ++polygon)
polygon->rotate(-(PI/2 - angle), Point(0,0));
}
#ifdef SLIC3RXS
SV*
ExPolygon::to_AV() {

View File

@ -18,7 +18,7 @@ class ExPolygon
operator Polygons() const;
void scale(double factor);
void translate(double x, double y);
void rotate(double angle, Point* center);
void rotate(double angle, const Point &center);
double area() const;
bool is_valid() const;
bool contains_line(const Line* line) const;
@ -27,6 +27,8 @@ class ExPolygon
ExPolygons simplify(double tolerance) const;
void simplify(double tolerance, ExPolygons &expolygons) const;
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
void get_trapezoids(Polygons* polygons) const;
void get_trapezoids(Polygons* polygons, double angle) const;
#ifdef SLIC3RXS
void from_SV(SV* poly_sv);
@ -40,4 +42,108 @@ class ExPolygon
}
// start Boost
#include <boost/polygon/polygon.hpp>
namespace boost { namespace polygon {
template <>
struct polygon_traits<ExPolygon> {
typedef coord_t coordinate_type;
typedef Points::const_iterator iterator_type;
typedef Point point_type;
// Get the begin iterator
static inline iterator_type begin_points(const ExPolygon& t) {
return t.contour.points.begin();
}
// Get the end iterator
static inline iterator_type end_points(const ExPolygon& t) {
return t.contour.points.end();
}
// Get the number of sides of the polygon
static inline std::size_t size(const ExPolygon& t) {
return t.contour.points.size();
}
// Get the winding direction of the polygon
static inline winding_direction winding(const ExPolygon& t) {
return unknown_winding;
}
};
template <>
struct polygon_mutable_traits<ExPolygon> {
//expects stl style iterators
template <typename iT>
static inline ExPolygon& set_points(ExPolygon& expolygon, iT input_begin, iT input_end) {
expolygon.contour.points.assign(input_begin, input_end);
// skip last point since Boost will set last point = first point
expolygon.contour.points.pop_back();
return expolygon;
}
};
template <>
struct geometry_concept<ExPolygon> { typedef polygon_with_holes_concept type; };
template <>
struct polygon_with_holes_traits<ExPolygon> {
typedef Polygons::const_iterator iterator_holes_type;
typedef Polygon hole_type;
static inline iterator_holes_type begin_holes(const ExPolygon& t) {
return t.holes.begin();
}
static inline iterator_holes_type end_holes(const ExPolygon& t) {
return t.holes.end();
}
static inline unsigned int size_holes(const ExPolygon& t) {
return t.holes.size();
}
};
template <>
struct polygon_with_holes_mutable_traits<ExPolygon> {
template <typename iT>
static inline ExPolygon& set_holes(ExPolygon& t, iT inputBegin, iT inputEnd) {
t.holes.assign(inputBegin, inputEnd);
return t;
}
};
//first we register CPolygonSet as a polygon set
template <>
struct geometry_concept<ExPolygons> { typedef polygon_set_concept type; };
//next we map to the concept through traits
template <>
struct polygon_set_traits<ExPolygons> {
typedef coord_t coordinate_type;
typedef ExPolygons::const_iterator iterator_type;
typedef ExPolygons operator_arg_type;
static inline iterator_type begin(const ExPolygons& polygon_set) {
return polygon_set.begin();
}
static inline iterator_type end(const ExPolygons& polygon_set) {
return polygon_set.end();
}
//don't worry about these, just return false from them
static inline bool clean(const ExPolygons& polygon_set) { return false; }
static inline bool sorted(const ExPolygons& polygon_set) { return false; }
};
template <>
struct polygon_set_mutable_traits<ExPolygons> {
template <typename input_iterator_type>
static inline void set(ExPolygons& expolygons, input_iterator_type input_begin, input_iterator_type input_end) {
expolygons.assign(input_begin, input_end);
}
};
} }
// end Boost
#endif

View File

@ -32,7 +32,7 @@ ExPolygonCollection::translate(double x, double y)
}
void
ExPolygonCollection::rotate(double angle, Point* center)
ExPolygonCollection::rotate(double angle, const Point &center)
{
for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
(*it).rotate(angle, center);

View File

@ -13,7 +13,7 @@ class ExPolygonCollection
operator Polygons() const;
void scale(double factor);
void translate(double x, double y);
void rotate(double angle, Point* center);
void rotate(double angle, const Point &center);
bool contains_point(const Point* point) const;
void simplify(double tolerance);
void convex_hull(Polygon* hull) const;

View File

@ -38,7 +38,7 @@ Line::translate(double x, double y)
}
void
Line::rotate(double angle, Point* center)
Line::rotate(double angle, const Point &center)
{
this->a.rotate(angle, center);
this->b.rotate(angle, center);

View File

@ -3,7 +3,6 @@
#include <myinit.h>
#include "Point.hpp"
#include <boost/polygon/polygon.hpp>
namespace Slic3r {
@ -21,7 +20,7 @@ class Line
operator Polyline() const;
void scale(double factor);
void translate(double x, double y);
void rotate(double angle, Point* center);
void rotate(double angle, const Point &center);
void reverse();
double length() const;
Point* midpoint() const;
@ -48,6 +47,7 @@ typedef std::vector<Line> Lines;
}
// start Boost
#include <boost/polygon/polygon.hpp>
namespace boost { namespace polygon {
template <>
struct geometry_concept<Line> { typedef segment_concept type; };

View File

@ -19,7 +19,7 @@ MultiPoint::translate(double x, double y)
}
void
MultiPoint::rotate(double angle, Point* center)
MultiPoint::rotate(double angle, const Point &center)
{
for (Points::iterator it = points.begin(); it != points.end(); ++it) {
(*it).rotate(angle, center);

View File

@ -14,7 +14,7 @@ class MultiPoint
Points points;
void scale(double factor);
void translate(double x, double y);
void rotate(double angle, Point* center);
void rotate(double angle, const Point &center);
void reverse();
Point* first_point() const;
virtual Point* last_point() const = 0;

View File

@ -34,12 +34,12 @@ Point::translate(double x, double y)
}
void
Point::rotate(double angle, Point* center)
Point::rotate(double angle, const Point &center)
{
double cur_x = (double)this->x;
double cur_y = (double)this->y;
this->x = (coord_t)round( (double)center->x + cos(angle) * (cur_x - (double)center->x) - sin(angle) * (cur_y - (double)center->y) );
this->y = (coord_t)round( (double)center->y + cos(angle) * (cur_y - (double)center->y) + sin(angle) * (cur_x - (double)center->x) );
this->x = (coord_t)round( (double)center.x + cos(angle) * (cur_x - (double)center.x) - sin(angle) * (cur_y - (double)center.y) );
this->y = (coord_t)round( (double)center.y + cos(angle) * (cur_y - (double)center.y) + sin(angle) * (cur_x - (double)center.x) );
}
bool

View File

@ -4,7 +4,6 @@
#include <myinit.h>
#include <vector>
#include <math.h>
#include <boost/polygon/polygon.hpp>
#include <string>
namespace Slic3r {
@ -27,7 +26,7 @@ class Point
std::string wkt() const;
void scale(double factor);
void translate(double x, double y);
void rotate(double angle, Point* center);
void rotate(double angle, const Point &center);
bool coincides_with(const Point &point) const;
bool coincides_with(const Point* point) const;
int nearest_point_index(Points &points) const;
@ -83,7 +82,21 @@ class Pointf3 : public Pointf
}
// start Boost
#include <boost/polygon/polygon.hpp>
namespace boost { namespace polygon {
template <>
struct geometry_concept<coord_t> { typedef coordinate_concept type; };
template <>
struct coordinate_traits<coord_t> {
typedef coord_t coordinate_type;
typedef long double area_type;
typedef long long manhattan_area_type;
typedef unsigned long long unsigned_area_type;
typedef long long coordinate_difference;
typedef long double coordinate_distance;
};
template <>
struct geometry_concept<Point> { typedef point_concept type; };
@ -95,6 +108,23 @@ namespace boost { namespace polygon {
return (orient == HORIZONTAL) ? point.x : point.y;
}
};
template <>
struct point_mutable_traits<Point> {
typedef coord_t coordinate_type;
static inline void set(Point& point, orientation_2d orient, coord_t value) {
if (orient == HORIZONTAL)
point.x = value;
else
point.y = value;
}
static inline Point construct(coord_t x_value, coord_t y_value) {
Point retval;
retval.x = x_value;
retval.y = y_value;
return retval;
}
};
} }
// end Boost

View File

@ -41,4 +41,87 @@ class Polygon : public MultiPoint {
}
// start Boost
#include <boost/polygon/polygon.hpp>
namespace boost { namespace polygon {
template <>
struct geometry_concept<Polygon>{ typedef polygon_concept type; };
template <>
struct polygon_traits<Polygon> {
typedef coord_t coordinate_type;
typedef Points::const_iterator iterator_type;
typedef Point point_type;
// Get the begin iterator
static inline iterator_type begin_points(const Polygon& t) {
return t.points.begin();
}
// Get the end iterator
static inline iterator_type end_points(const Polygon& t) {
return t.points.end();
}
// Get the number of sides of the polygon
static inline std::size_t size(const Polygon& t) {
return t.points.size();
}
// Get the winding direction of the polygon
static inline winding_direction winding(const Polygon& t) {
return unknown_winding;
}
};
template <>
struct polygon_mutable_traits<Polygon> {
// expects stl style iterators
template <typename iT>
static inline Polygon& set_points(Polygon& polygon, iT input_begin, iT input_end) {
polygon.points.clear();
while (input_begin != input_end) {
polygon.points.push_back(Point());
boost::polygon::assign(polygon.points.back(), *input_begin);
++input_begin;
}
// skip last point since Boost will set last point = first point
polygon.points.pop_back();
return polygon;
}
};
template <>
struct geometry_concept<Polygons> { typedef polygon_set_concept type; };
//next we map to the concept through traits
template <>
struct polygon_set_traits<Polygons> {
typedef coord_t coordinate_type;
typedef Polygons::const_iterator iterator_type;
typedef Polygons operator_arg_type;
static inline iterator_type begin(const Polygons& polygon_set) {
return polygon_set.begin();
}
static inline iterator_type end(const Polygons& polygon_set) {
return polygon_set.end();
}
//don't worry about these, just return false from them
static inline bool clean(const Polygons& polygon_set) { return false; }
static inline bool sorted(const Polygons& polygon_set) { return false; }
};
template <>
struct polygon_set_mutable_traits<Polygons> {
template <typename input_iterator_type>
static inline void set(Polygons& polygons, input_iterator_type input_begin, input_iterator_type input_end) {
polygons.assign(input_begin, input_end);
}
};
} }
// end Boost
#endif

View File

@ -3,8 +3,9 @@
use strict;
use warnings;
use List::Util qw(first);
use Slic3r::XS;
use Test::More tests => 21;
use Test::More tests => 31;
use constant PI => 4 * atan2(1, 1);
@ -104,4 +105,32 @@ is $expolygon->area, 100*100-20*20, 'area';
is_deeply $collection->[0]->clone->pp, $collection->[0]->pp, 'clone collection item';
}
{
my $expolygon = Slic3r::ExPolygon->new($square);
my $polygons = $expolygon->get_trapezoids(PI/2);
is scalar(@$polygons), 1, 'correct number of trapezoids returned';
is scalar(@{$polygons->[0]}), 4, 'trapezoid has 4 points';
is $polygons->[0]->area, $expolygon->area, 'trapezoid has correct area';
}
{
my $polygons = $expolygon->get_trapezoids(PI/2);
is scalar(@$polygons), 4, 'correct number of trapezoids returned';
# trapezoid polygons might have more than 4 points in case of collinear segments
$polygons = [ map @{$_->simplify(1)}, @$polygons ];
ok !defined(first { @$_ != 4 } @$polygons), 'all trapezoids have 4 points';
is scalar(grep { $_->area == 40*100 } @$polygons), 2, 'trapezoids have expected area';
is scalar(grep { $_->area == 20*40 } @$polygons), 2, 'trapezoids have expected area';
}
{
my $expolygon = Slic3r::ExPolygon->new([ [0,100],[100,0],[200,0],[300,100],[200,200],[100,200] ]);
my $polygons = $expolygon->get_trapezoids(PI/2);
is scalar(@$polygons), 3, 'correct number of trapezoids returned';
is scalar(grep { $_->area == 100*200/2 } @$polygons), 2, 'trapezoids have expected area';
is scalar(grep { $_->area == 100*200 } @$polygons), 1, 'trapezoids have expected area';
}
__END__

View File

@ -27,6 +27,8 @@
Polygons simplify_p(double tolerance);
Polylines medial_axis(double max_width, double min_width)
%code{% THIS->medial_axis(max_width, min_width, &RETVAL); %};
Polygons get_trapezoids(double angle)
%code{% THIS->get_trapezoids(&RETVAL, angle); %};
%{
ExPolygon*
@ -49,7 +51,7 @@ ExPolygon::rotate(angle, center_sv)
CODE:
Point center;
center.from_SV_check(center_sv);
THIS->rotate(angle, &center);
THIS->rotate(angle, center);
%}
};

View File

@ -13,7 +13,8 @@
%code{% THIS->expolygons.clear(); %};
void scale(double factor);
void translate(double x, double y);
void rotate(double angle, Point* center);
void rotate(double angle, Point* center)
%code{% THIS->rotate(angle, *center); %};
int count()
%code{% RETVAL = THIS->expolygons.size(); %};
bool contains_point(Point* point);

View File

@ -48,7 +48,7 @@ Line::rotate(angle, center_sv)
CODE:
Point center;
center.from_SV_check(center_sv);
THIS->rotate(angle, &center);
THIS->rotate(angle, center);
bool
Line::coincides_with(line_sv)

View File

@ -37,7 +37,7 @@ Point::rotate(angle, center_sv)
CODE:
Point center;
center.from_SV_check(center_sv);
THIS->rotate(angle, &center);
THIS->rotate(angle, center);
bool
Point::coincides_with(point_sv)

View File

@ -56,7 +56,7 @@ Polygon::rotate(angle, center_sv)
CODE:
Point center;
center.from_SV_check(center_sv);
THIS->rotate(angle, &center);
THIS->rotate(angle, center);
%}
};

View File

@ -70,7 +70,7 @@ Polyline::rotate(angle, center_sv)
CODE:
Point center;
center.from_SV_check(center_sv);
THIS->rotate(angle, &center);
THIS->rotate(angle, center);
Polygons
Polyline::grow(delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtSquare, miterLimit = 3)