diff --git a/lib/Slic3r/Layer/BridgeDetector.pm b/lib/Slic3r/Layer/BridgeDetector.pm index 54e4c9b4..80ca6741 100644 --- a/lib/Slic3r/Layer/BridgeDetector.pm +++ b/lib/Slic3r/Layer/BridgeDetector.pm @@ -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; diff --git a/t/bridges.t b/t/bridges.t index ff20af2f..daddd7f6 100644 --- a/t/bridges.t +++ b/t/bridges.t @@ -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 ]; diff --git a/xs/Build.PL b/xs/Build.PL index 9478c9e9..ca5b9310 100644 --- a/xs/Build.PL +++ b/xs/Build.PL @@ -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 => { diff --git a/xs/src/ExPolygon.cpp b/xs/src/ExPolygon.cpp index bfeb3093..0bbaf7c0 100644 --- a/xs/src/ExPolygon.cpp +++ b/xs/src/ExPolygon.cpp @@ -47,7 +47,7 @@ ExPolygon::translate(double x, double y) } void -ExPolygon::rotate(double angle, Point* center) +ExPolygon::rotate(double angle, const Point ¢er) { 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() { diff --git a/xs/src/ExPolygon.hpp b/xs/src/ExPolygon.hpp index 3b817520..9b1017cf 100644 --- a/xs/src/ExPolygon.hpp +++ b/xs/src/ExPolygon.hpp @@ -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 ¢er); 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 +namespace boost { namespace polygon { + template <> + struct polygon_traits { + 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 { + //expects stl style iterators + template + 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 { typedef polygon_with_holes_concept type; }; + + template <> + struct polygon_with_holes_traits { + 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 { + template + 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 { typedef polygon_set_concept type; }; + + //next we map to the concept through traits + template <> + struct polygon_set_traits { + 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 { + template + 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 diff --git a/xs/src/ExPolygonCollection.cpp b/xs/src/ExPolygonCollection.cpp index 8d339d95..979e3777 100644 --- a/xs/src/ExPolygonCollection.cpp +++ b/xs/src/ExPolygonCollection.cpp @@ -32,7 +32,7 @@ ExPolygonCollection::translate(double x, double y) } void -ExPolygonCollection::rotate(double angle, Point* center) +ExPolygonCollection::rotate(double angle, const Point ¢er) { for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { (*it).rotate(angle, center); diff --git a/xs/src/ExPolygonCollection.hpp b/xs/src/ExPolygonCollection.hpp index 09f56a16..0037b26d 100644 --- a/xs/src/ExPolygonCollection.hpp +++ b/xs/src/ExPolygonCollection.hpp @@ -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 ¢er); bool contains_point(const Point* point) const; void simplify(double tolerance); void convex_hull(Polygon* hull) const; diff --git a/xs/src/Line.cpp b/xs/src/Line.cpp index dd6b1eec..c0b2807f 100644 --- a/xs/src/Line.cpp +++ b/xs/src/Line.cpp @@ -38,7 +38,7 @@ Line::translate(double x, double y) } void -Line::rotate(double angle, Point* center) +Line::rotate(double angle, const Point ¢er) { this->a.rotate(angle, center); this->b.rotate(angle, center); diff --git a/xs/src/Line.hpp b/xs/src/Line.hpp index a3a229ce..a08999a5 100644 --- a/xs/src/Line.hpp +++ b/xs/src/Line.hpp @@ -3,7 +3,6 @@ #include #include "Point.hpp" -#include 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 ¢er); void reverse(); double length() const; Point* midpoint() const; @@ -48,6 +47,7 @@ typedef std::vector Lines; } // start Boost +#include namespace boost { namespace polygon { template <> struct geometry_concept { typedef segment_concept type; }; diff --git a/xs/src/MultiPoint.cpp b/xs/src/MultiPoint.cpp index c31b2338..a8dca5b5 100644 --- a/xs/src/MultiPoint.cpp +++ b/xs/src/MultiPoint.cpp @@ -19,7 +19,7 @@ MultiPoint::translate(double x, double y) } void -MultiPoint::rotate(double angle, Point* center) +MultiPoint::rotate(double angle, const Point ¢er) { for (Points::iterator it = points.begin(); it != points.end(); ++it) { (*it).rotate(angle, center); diff --git a/xs/src/MultiPoint.hpp b/xs/src/MultiPoint.hpp index ba06e042..4f3d366c 100644 --- a/xs/src/MultiPoint.hpp +++ b/xs/src/MultiPoint.hpp @@ -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 ¢er); void reverse(); Point* first_point() const; virtual Point* last_point() const = 0; diff --git a/xs/src/Point.cpp b/xs/src/Point.cpp index 21ec8390..b1ca9a0d 100644 --- a/xs/src/Point.cpp +++ b/xs/src/Point.cpp @@ -34,12 +34,12 @@ Point::translate(double x, double y) } void -Point::rotate(double angle, Point* center) +Point::rotate(double angle, const Point ¢er) { 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 diff --git a/xs/src/Point.hpp b/xs/src/Point.hpp index 8f3cec55..f970a96c 100644 --- a/xs/src/Point.hpp +++ b/xs/src/Point.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include 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 ¢er); 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 namespace boost { namespace polygon { + template <> + struct geometry_concept { typedef coordinate_concept type; }; + + template <> + struct coordinate_traits { + 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 { typedef point_concept type; }; @@ -95,6 +108,23 @@ namespace boost { namespace polygon { return (orient == HORIZONTAL) ? point.x : point.y; } }; + + template <> + struct point_mutable_traits { + 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 diff --git a/xs/src/Polygon.hpp b/xs/src/Polygon.hpp index 6ab62ab0..fa043393 100644 --- a/xs/src/Polygon.hpp +++ b/xs/src/Polygon.hpp @@ -41,4 +41,87 @@ class Polygon : public MultiPoint { } +// start Boost +#include +namespace boost { namespace polygon { + template <> + struct geometry_concept{ typedef polygon_concept type; }; + + template <> + struct polygon_traits { + 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 { + // expects stl style iterators + template + 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 { typedef polygon_set_concept type; }; + + //next we map to the concept through traits + template <> + struct polygon_set_traits { + 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 { + template + 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 diff --git a/xs/t/04_expolygon.t b/xs/t/04_expolygon.t index 38c029a5..3c1131b2 100644 --- a/xs/t/04_expolygon.t +++ b/xs/t/04_expolygon.t @@ -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__ diff --git a/xs/xsp/ExPolygon.xsp b/xs/xsp/ExPolygon.xsp index 6c2ad1c4..19e98c08 100644 --- a/xs/xsp/ExPolygon.xsp +++ b/xs/xsp/ExPolygon.xsp @@ -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, ¢er); + THIS->rotate(angle, center); %} }; diff --git a/xs/xsp/ExPolygonCollection.xsp b/xs/xsp/ExPolygonCollection.xsp index 793c30b9..e8516fe9 100644 --- a/xs/xsp/ExPolygonCollection.xsp +++ b/xs/xsp/ExPolygonCollection.xsp @@ -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); diff --git a/xs/xsp/Line.xsp b/xs/xsp/Line.xsp index 23b0250c..7f9f332b 100644 --- a/xs/xsp/Line.xsp +++ b/xs/xsp/Line.xsp @@ -48,7 +48,7 @@ Line::rotate(angle, center_sv) CODE: Point center; center.from_SV_check(center_sv); - THIS->rotate(angle, ¢er); + THIS->rotate(angle, center); bool Line::coincides_with(line_sv) diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index 79439d09..5a24edb5 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -37,7 +37,7 @@ Point::rotate(angle, center_sv) CODE: Point center; center.from_SV_check(center_sv); - THIS->rotate(angle, ¢er); + THIS->rotate(angle, center); bool Point::coincides_with(point_sv) diff --git a/xs/xsp/Polygon.xsp b/xs/xsp/Polygon.xsp index 7d2a364e..2aafe9ef 100644 --- a/xs/xsp/Polygon.xsp +++ b/xs/xsp/Polygon.xsp @@ -56,7 +56,7 @@ Polygon::rotate(angle, center_sv) CODE: Point center; center.from_SV_check(center_sv); - THIS->rotate(angle, ¢er); + THIS->rotate(angle, center); %} }; diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp index 8a7bd780..aadbf06c 100644 --- a/xs/xsp/Polyline.xsp +++ b/xs/xsp/Polyline.xsp @@ -70,7 +70,7 @@ Polyline::rotate(angle, center_sv) CODE: Point center; center.from_SV_check(center_sv); - THIS->rotate(angle, ¢er); + THIS->rotate(angle, center); Polygons Polyline::grow(delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtSquare, miterLimit = 3)