diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 9013ad8d5..7abbf228a 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -58,7 +58,6 @@ use Slic3r::GCode::VibrationLimit; use Slic3r::Geometry qw(PI); use Slic3r::Geometry::Clipper; use Slic3r::Layer; -use Slic3r::Layer::BridgeDetector; use Slic3r::Layer::Region; use Slic3r::Line; use Slic3r::Model; @@ -162,6 +161,7 @@ sub thread_cleanup { # prevent destruction of shared objects no warnings 'redefine'; + *Slic3r::BridgeDetector::DESTROY = sub {}; *Slic3r::Config::DESTROY = sub {}; *Slic3r::Config::Full::DESTROY = sub {}; *Slic3r::Config::GCode::DESTROY = sub {}; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 8be03162f..42fab2d89 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -207,23 +207,6 @@ sub polygon_is_convex { return 1; } -sub deg2rad { - my ($degrees) = @_; - return PI() * $degrees / 180; -} - -sub rad2deg { - my ($rad) = @_; - return $rad / PI() * 180; -} - -sub rad2deg_dir { - my ($rad) = @_; - $rad = ($rad < PI) ? (-$rad + PI/2) : ($rad + PI/2); - $rad += PI if $rad < 0; - return rad2deg($rad); -} - sub rotate_points { my ($radians, $center, @points) = @_; $center //= [0,0]; diff --git a/lib/Slic3r/Layer/BridgeDetector.pm b/lib/Slic3r/Layer/BridgeDetector.pm deleted file mode 100644 index 56c738637..000000000 --- a/lib/Slic3r/Layer/BridgeDetector.pm +++ /dev/null @@ -1,277 +0,0 @@ -package Slic3r::Layer::BridgeDetector; -use Moo; - -use List::Util qw(first sum max min); -use Slic3r::Geometry qw(PI unscale scaled_epsilon rad2deg epsilon directions_parallel_within); -use Slic3r::Geometry::Clipper qw(intersection_pl intersection_ex union offset diff_pl union_ex - intersection_ppl); - -has 'expolygon' => (is => 'ro', required => 1); -has 'lower_slices' => (is => 'rw', required => 1); # ExPolygons or ExPolygonCollection -has 'extrusion_width' => (is => 'rw', required => 1); # scaled -has 'resolution' => (is => 'rw', default => sub { PI/36 }); - -has '_edges' => (is => 'rw'); # Polylines representing the supporting edges -has '_anchors' => (is => 'rw'); # ExPolygons -has 'angle' => (is => 'rw'); - -sub BUILD { - my ($self) = @_; - - # outset our bridge by an arbitrary amout; we'll use this outer margin - # for detecting anchors - my $grown = $self->expolygon->offset(+$self->extrusion_width); - - # detect what edges lie on lower slices - $self->_edges(my $edges = []); - foreach my $lower (@{$self->lower_slices}) { - # turn bridge contour and holes into polylines and then clip them - # with each lower slice's contour - push @$edges, @{intersection_ppl($grown, [ $lower->contour ])}; - } - Slic3r::debugf " bridge has %d support(s)\n", scalar(@$edges); - - # detect anchors as intersection between our bridge expolygon and the lower slices - $self->_anchors(intersection_ex( - $grown, - [ map @$_, @{$self->lower_slices} ], - 1, # safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some @edges - )); - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("bridge.svg", - expolygons => [ $self->expolygon ], - red_expolygons => $self->lower_slices, - polylines => $self->_edges, - ); - } -} - -sub detect_angle { - my ($self) = @_; - - return undef if !@{$self->_edges}; - - my @edges = @{$self->_edges}; - my $anchors = $self->_anchors; - - if (!@$anchors) { - $self->angle(undef); - return undef; - } - - # Outset the bridge expolygon by half the amount we used for detecting anchors; - # we'll use this one to clip our test lines and be sure that their endpoints - # are inside the anchors and not on their contours leading to false negatives. - my $clip_area = $self->expolygon->offset_ex(+$self->extrusion_width/2); - - # we'll now try several directions using a rudimentary visibility check: - # bridge in several directions and then sum the length of lines having both - # endpoints within anchors - - # we test angles according to configured resolution - my @angles = map { $_*$self->resolution } 0..(PI/$self->resolution); - - # we also test angles of each bridge contour - push @angles, map $_->direction, map @{$_->lines}, @{$self->expolygon}; - - # we also test angles of each open supporting edge - # (this finds the optimal angle for C-shaped supports) - push @angles, - map Slic3r::Line->new($_->first_point, $_->last_point)->direction, - grep { !$_->first_point->coincides_with($_->last_point) } - @edges; - - # remove duplicates - my $min_resolution = PI/180; # 1 degree - # proceed in reverse order so that when we compare first value with last one (-1) - # we remove the greatest one (PI) in case they are parallel (PI, 0) - @angles = reverse sort @angles; - for (my $i = 0; $i <= $#angles; ++$i) { - if (directions_parallel_within($angles[$i], $angles[$i-1], $min_resolution)) { - splice @angles, $i, 1; - --$i; - } - } - - my %directions_coverage = (); # angle => score - my %directions_avg_length = (); # angle => score - my $line_increment = $self->extrusion_width; - my %unique_angles = map { $_ => 1 } @angles; - for my $angle (@angles) { - my $my_clip_area = [ map $_->clone, @$clip_area ]; - my $my_anchors = [ map $_->clone, @$anchors ]; - - # rotate everything - the center point doesn't matter - $_->rotate(-$angle, [0,0]) for @$my_clip_area, @$my_anchors; - - # generate lines in this direction - my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$my_anchors ]); - - my @lines = (); - for (my $y = $bounding_box->y_min; $y <= $bounding_box->y_max; $y+= $line_increment) { - push @lines, Slic3r::Polyline->new( - [$bounding_box->x_min, $y], - [$bounding_box->x_max, $y], - ); - } - - my @clipped_lines = map Slic3r::Line->new(@$_), @{ intersection_pl(\@lines, [ map @$_, @$my_clip_area ]) }; - - # remove any line not having both endpoints within anchors - @clipped_lines = grep { - my $line = $_; - (first { $_->contains_point($line->a) } @$my_anchors) - && (first { $_->contains_point($line->b) } @$my_anchors); - } @clipped_lines; - - my @lengths = map $_->length, @clipped_lines; - - # sum length of bridged lines - $directions_coverage{$angle} = sum(@lengths) // 0; - - ### The following produces more correct results in some cases and more broken in others. - ### TODO: investigate, as it looks more reliable than line clipping. - ###$directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0; - - # max length of bridged lines - $directions_avg_length{$angle} = @lengths ? (max(@lengths)) : -1; - } - - # if no direction produced coverage, then there's no bridge direction - return undef if !defined first { $_ > 0 } values %directions_coverage; - - # the best direction is the one causing most lines to be bridged (thus most coverage) - # and shortest max line length - my @sorted_directions = sort { - my $cmp; - my $coverage_diff = $directions_coverage{$a} - $directions_coverage{$b}; - if (abs($coverage_diff) < $self->extrusion_width) { - $cmp = $directions_avg_length{$b} <=> $directions_avg_length{$a}; - } else { - $cmp = ($coverage_diff > 0) ? 1 : -1; - } - $cmp; - } keys %directions_coverage; - - $self->angle($sorted_directions[-1]); - - if ($self->angle >= PI) { - $self->angle($self->angle - PI); - } - - Slic3r::debugf " Optimal infill angle is %d degrees\n", rad2deg($self->angle); - - return $self->angle; -} - -sub coverage { - my ($self, $angle) = @_; - - if (!defined $angle) { - return [] if !defined($angle = $self->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_trapezoids2(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])}; - - # not nice, we need a more robust non-numeric check - @supported = grep $_->length >= $self->extrusion_width, @supported; - - 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; -} - -# this method returns the bridge edges (as polylines) that are not supported -# but would allow the entire bridge area to be bridged with detected angle -# if supported too -sub unsupported_edges { - my ($self, $angle) = @_; - - if (!defined $angle) { - return [] if !defined($angle = $self->angle); - } - - # get bridge edges (both contour and holes) - my @bridge_edges = map $_->split_at_first_point, @{$self->expolygon}; - $_->[0]->translate(1,0) for @bridge_edges; # workaround for Clipper bug, see comments in Slic3r::Polygon::clip_as_polyline() - - # get unsupported edges - my $grown_lower = offset([ map @$_, @{$self->lower_slices} ], +$self->extrusion_width); - my $unsupported = diff_pl( - \@bridge_edges, - $grown_lower, - ); - - # split into individual segments and filter out edges parallel to the bridging angle - # TODO: angle tolerance should probably be based on segment length and flow width, - # so that we build supports whenever there's a chance that at least one or two bridge - # extrusions would be anchored within such length (i.e. a slightly non-parallel bridging - # direction might still benefit from anchors if long enough) - my $angle_tolerance = PI/180*5; - @$unsupported = map $_->as_polyline, - grep !directions_parallel_within($_->direction, $angle, $angle_tolerance), - map @{$_->lines}, - @$unsupported; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "unsupported_" . rad2deg($angle) . ".svg", - expolygons => [$self->expolygon], - green_expolygons => $self->_anchors, - red_expolygons => union_ex($grown_lower), - no_arrows => 1, - polylines => \@bridge_edges, - red_polylines => $unsupported, - ); - } - - return $unsupported; -} - -1; diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index b8d464af6..0063095e1 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -486,16 +486,17 @@ sub process_external_surfaces { # of very thin (but still working) anchors, the grown expolygon would go beyond them my $angle; if ($lower_layer) { - my $bridge_detector = Slic3r::Layer::BridgeDetector->new( - expolygon => $surface->expolygon, - lower_slices => $lower_layer->slices, - extrusion_width => $self->flow(FLOW_ROLE_INFILL, $self->height, 1)->scaled_width, + my $bridge_detector = Slic3r::BridgeDetector->new( + $surface->expolygon, + $lower_layer->slices, + $self->flow(FLOW_ROLE_INFILL, $self->height, 1)->scaled_width, ); Slic3r::debugf "Processing bridge at layer %d:\n", $self->id; - $angle = $bridge_detector->detect_angle; + $bridge_detector->detect_angle; + $angle = $bridge_detector->angle; if (defined $angle && $self->object->config->support_material) { - $self->bridged->append($_) for @{ $bridge_detector->coverage($angle) }; + $self->bridged->append($_) for @{ $bridge_detector->coverage_with_angle($angle) }; $self->unsupported_bridge_edges->append($_) for @{ $bridge_detector->unsupported_edges }; } } diff --git a/t/bridges.t b/t/bridges.t index fc7f9de1c..c7428a195 100644 --- a/t/bridges.t +++ b/t/bridges.t @@ -84,17 +84,18 @@ use Slic3r::Test; sub check_angle { my ($lower, $bridge, $expected, $tolerance, $expected_coverage) = @_; + if (ref($lower) eq 'ARRAY') { + $lower = Slic3r::ExPolygon::Collection->new(@$lower); + } + $expected_coverage //= -1; $expected_coverage = $bridge->area if $expected_coverage == -1; - my $bd = Slic3r::Layer::BridgeDetector->new( - expolygon => $bridge, - lower_slices => $lower, - extrusion_width => scale 0.5, - ); + my $bd = Slic3r::BridgeDetector->new($bridge, $lower, scale 0.5); $tolerance //= rad2deg($bd->resolution) + epsilon; - my $result = $bd->detect_angle; + $bd->detect_angle; + my $result = $bd->angle; my $coverage = $bd->coverage; is sum(map $_->area, @$coverage), $expected_coverage, 'correct coverage area'; diff --git a/xs/MANIFEST b/xs/MANIFEST index 6a44597da..ac073b1d0 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -1652,6 +1652,8 @@ src/clipper.cpp src/clipper.hpp src/libslic3r/BoundingBox.cpp src/libslic3r/BoundingBox.hpp +src/libslic3r/BridgeDetector.cpp +src/libslic3r/BridgeDetector.hpp src/libslic3r/ClipperUtils.cpp src/libslic3r/ClipperUtils.hpp src/libslic3r/Config.cpp @@ -1669,6 +1671,8 @@ src/libslic3r/ExtrusionEntityCollection.hpp src/libslic3r/Flow.cpp src/libslic3r/Flow.hpp src/libslic3r/GCode.hpp +src/libslic3r/GCodeWriter.cpp +src/libslic3r/GCodeWriter.hpp src/libslic3r/Geometry.cpp src/libslic3r/Geometry.hpp src/libslic3r/Layer.cpp @@ -1745,6 +1749,7 @@ t/18_motionplanner.t t/19_model.t t/20_print.t xsp/BoundingBox.xsp +xsp/BridgeDetector.xsp xsp/Clipper.xsp xsp/Config.xsp xsp/ExPolygon.xsp @@ -1754,6 +1759,7 @@ xsp/ExtrusionEntityCollection.xsp xsp/ExtrusionLoop.xsp xsp/ExtrusionPath.xsp xsp/Flow.xsp +xsp/GCodeWriter.xsp xsp/Geometry.xsp xsp/Layer.xsp xsp/Line.xsp diff --git a/xs/lib/Slic3r/XS.pm b/xs/lib/Slic3r/XS.pm index dbb4df69c..112c6ebc3 100644 --- a/xs/lib/Slic3r/XS.pm +++ b/xs/lib/Slic3r/XS.pm @@ -188,6 +188,7 @@ sub new { package main; for my $class (qw( + Slic3r::BridgeDetector Slic3r::Config Slic3r::Config::Full Slic3r::Config::GCode diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp index 6a0c0d318..16c8acadb 100644 --- a/xs/src/libslic3r/BoundingBox.cpp +++ b/xs/src/libslic3r/BoundingBox.cpp @@ -94,6 +94,14 @@ BoundingBoxBase::merge(const PointClass &point) template void BoundingBoxBase::merge(const Point &point); template void BoundingBoxBase::merge(const Pointf &point); +template void +BoundingBoxBase::merge(const std::vector &points) +{ + this->merge(BoundingBoxBase(points)); +} +template void BoundingBoxBase::merge(const Points &points); +template void BoundingBoxBase::merge(const Pointfs &points); + template void BoundingBoxBase::merge(const BoundingBoxBase &bb) { @@ -122,6 +130,13 @@ BoundingBox3Base::merge(const PointClass &point) } template void BoundingBox3Base::merge(const Pointf3 &point); +template void +BoundingBox3Base::merge(const std::vector &points) +{ + this->merge(BoundingBox3Base(points)); +} +template void BoundingBox3Base::merge(const Pointf3s &points); + template void BoundingBox3Base::merge(const BoundingBox3Base &bb) { diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp index 382349afa..fd106a7a5 100644 --- a/xs/src/libslic3r/BoundingBox.hpp +++ b/xs/src/libslic3r/BoundingBox.hpp @@ -23,6 +23,7 @@ class BoundingBoxBase BoundingBoxBase() : defined(false) {}; BoundingBoxBase(const std::vector &points); void merge(const PointClass &point); + void merge(const std::vector &points); void merge(const BoundingBoxBase &bb); void scale(double factor); PointClass size() const; @@ -38,6 +39,7 @@ class BoundingBox3Base : public BoundingBoxBase BoundingBox3Base() : BoundingBoxBase() {}; BoundingBox3Base(const std::vector &points); void merge(const PointClass &point); + void merge(const std::vector &points); void merge(const BoundingBox3Base &bb); PointClass size() const; void translate(coordf_t x, coordf_t y, coordf_t z); diff --git a/xs/src/libslic3r/BridgeDetector.cpp b/xs/src/libslic3r/BridgeDetector.cpp new file mode 100644 index 000000000..e1b9d4806 --- /dev/null +++ b/xs/src/libslic3r/BridgeDetector.cpp @@ -0,0 +1,331 @@ +#include "BridgeDetector.hpp" +#include "ClipperUtils.hpp" +#include "Geometry.hpp" +#include + +namespace Slic3r { + +class BridgeDirectionComparator { + public: + std::map dir_coverage, dir_avg_length; // angle => score + + BridgeDirectionComparator(double _extrusion_width) + : extrusion_width(_extrusion_width) {}; + + // the best direction is the one causing most lines to be bridged (thus most coverage) + // and shortest max line length + bool operator() (double a, double b) { + double coverage_diff = this->dir_coverage[a] - this->dir_coverage[b]; + if (fabs(coverage_diff) < this->extrusion_width) { + return (this->dir_avg_length[b] > this->dir_avg_length[a]); + } else { + return (coverage_diff > 0); + } + }; + + private: + double extrusion_width; +}; + +BridgeDetector::BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices, + coord_t _extrusion_width) + : expolygon(_expolygon), lower_slices(_lower_slices), extrusion_width(_extrusion_width), + angle(-1), resolution(PI/36.0) +{ + /* outset our bridge by an arbitrary amout; we'll use this outer margin + for detecting anchors */ + Polygons grown; + offset((Polygons)this->expolygon, grown, this->extrusion_width); + + // detect what edges lie on lower slices + for (ExPolygons::const_iterator lower = this->lower_slices.expolygons.begin(); + lower != this->lower_slices.expolygons.end(); + ++lower) { + /* turn bridge contour and holes into polylines and then clip them + with each lower slice's contour */ + intersection(grown, lower->contour, this->_edges); + } + #ifdef SLIC3R_DEBUG + printf(" bridge has %zu support(s)\n", this->_edges.size()); + #endif + + // detect anchors as intersection between our bridge expolygon and the lower slices + // safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some edges + intersection(grown, this->lower_slices, this->_anchors, true); + + /* + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output("bridge.svg", + expolygons => [ $self->expolygon ], + red_expolygons => $self->lower_slices, + polylines => $self->_edges, + ); + } + */ +} + +bool +BridgeDetector::detect_angle() +{ + if (this->_edges.empty() || this->_anchors.empty()) return false; + + /* Outset the bridge expolygon by half the amount we used for detecting anchors; + we'll use this one to clip our test lines and be sure that their endpoints + are inside the anchors and not on their contours leading to false negatives. */ + Polygons clip_area; + offset(this->expolygon, clip_area, +this->extrusion_width/2); + + /* we'll now try several directions using a rudimentary visibility check: + bridge in several directions and then sum the length of lines having both + endpoints within anchors */ + + // we test angles according to configured resolution + std::vector angles; + for (int i = 0; i <= PI/this->resolution; ++i) + angles.push_back(i * this->resolution); + + // we also test angles of each bridge contour + { + Polygons pp = this->expolygon; + for (Polygons::const_iterator p = pp.begin(); p != pp.end(); ++p) { + Lines lines; + p->lines(&lines); + for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) + angles.push_back(line->direction()); + } + } + + /* we also test angles of each open supporting edge + (this finds the optimal angle for C-shaped supports) */ + for (Polylines::const_iterator edge = this->_edges.begin(); edge != this->_edges.end(); ++edge) { + if (edge->first_point().coincides_with(edge->last_point())) continue; + angles.push_back(Line(edge->first_point(), edge->last_point()).direction()); + } + + // remove duplicates + double min_resolution = PI/180.0; // 1 degree + std::sort(angles.begin(), angles.end()); + for (size_t i = 1; i < angles.size(); ++i) { + if (Slic3r::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) { + angles.erase(angles.begin() + i); + --i; + } + } + /* compare first value with last one and remove the greatest one (PI) + in case they are parallel (PI, 0) */ + if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution)) + angles.pop_back(); + + BridgeDirectionComparator bdcomp(this->extrusion_width); + double line_increment = this->extrusion_width; + bool have_coverage = false; + for (std::vector::const_iterator angle = angles.begin(); angle != angles.end(); ++angle) { + Polygons my_clip_area = clip_area; + ExPolygons my_anchors = this->_anchors; + + // rotate everything - the center point doesn't matter + for (Polygons::iterator it = my_clip_area.begin(); it != my_clip_area.end(); ++it) + it->rotate(-*angle, Point(0,0)); + for (ExPolygons::iterator it = my_anchors.begin(); it != my_anchors.end(); ++it) + it->rotate(-*angle, Point(0,0)); + + // generate lines in this direction + BoundingBox bb; + for (ExPolygons::const_iterator it = my_anchors.begin(); it != my_anchors.end(); ++it) + bb.merge((Points)*it); + + Lines lines; + for (coord_t y = bb.min.y; y <= bb.max.y; y += line_increment) + lines.push_back(Line(Point(bb.min.x, y), Point(bb.max.x, y))); + + Lines clipped_lines; + intersection(lines, my_clip_area, clipped_lines); + + // remove any line not having both endpoints within anchors + for (size_t i = 0; i < clipped_lines.size(); ++i) { + Line &line = clipped_lines[i]; + if (!Slic3r::Geometry::contains_point(my_anchors, line.a) + || !Slic3r::Geometry::contains_point(my_anchors, line.b)) { + clipped_lines.erase(clipped_lines.begin() + i); + --i; + } + } + + std::vector lengths; + double total_length = 0; + for (Lines::const_iterator line = clipped_lines.begin(); line != clipped_lines.end(); ++line) { + double len = line->length(); + lengths.push_back(len); + total_length += len; + } + if (total_length) have_coverage = true; + + // sum length of bridged lines + bdcomp.dir_coverage[*angle] = total_length; + + /* The following produces more correct results in some cases and more broken in others. + TODO: investigate, as it looks more reliable than line clipping. */ + // $directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0; + + // max length of bridged lines + bdcomp.dir_avg_length[*angle] = !lengths.empty() + ? *std::max_element(lengths.begin(), lengths.end()) + : 0; + } + + // if no direction produced coverage, then there's no bridge direction + if (!have_coverage) return false; + + // sort directions by score + std::sort(angles.begin(), angles.end(), bdcomp); + + this->angle = angles.front(); + if (this->angle >= PI) this->angle -= PI; + + #ifdef SLIC3R_DEBUG + printf(" Optimal infill angle is %d degrees\n", (int)Slic3r::Geometry::rad2deg(this->angle)); + #endif + + return true; +} + +void +BridgeDetector::coverage(Polygons* coverage) const +{ + if (this->angle == -1) return; + return this->coverage(angle, coverage); +} + +void +BridgeDetector::coverage(double angle, Polygons* coverage) const +{ + // Clone our expolygon and rotate it so that we work with vertical lines. + ExPolygon expolygon = this->expolygon; + expolygon.rotate(PI/2.0 - angle, Point(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. */ + ExPolygons grown; + offset_ex(expolygon, grown, this->extrusion_width/2.0); + + // Compute trapezoids according to a vertical orientation + Polygons trapezoids; + for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) + it->get_trapezoids2(&trapezoids, PI/2.0); + + // get anchors, convert them to Polygons and rotate them too + Polygons anchors; + for (ExPolygons::const_iterator anchor = this->_anchors.begin(); anchor != this->_anchors.end(); ++anchor) { + Polygons pp = *anchor; + for (Polygons::iterator p = pp.begin(); p != pp.end(); ++p) + p->rotate(PI/2.0 - angle, Point(0,0)); + anchors.insert(anchors.end(), pp.begin(), pp.end()); + } + + Polygons covered; + for (Polygons::const_iterator trapezoid = trapezoids.begin(); trapezoid != trapezoids.end(); ++trapezoid) { + Lines lines = trapezoid->lines(); + Lines supported; + intersection(lines, anchors, supported); + + // not nice, we need a more robust non-numeric check + for (size_t i = 0; i < supported.size(); ++i) { + if (supported[i].length() < this->extrusion_width) { + supported.erase(supported.begin() + i); + i--; + } + } + + if (supported.size() >= 2) covered.push_back(*trapezoid); + } + + // merge trapezoids and rotate them back + Polygons _coverage; + union_(covered, _coverage); + for (Polygons::iterator p = _coverage.begin(); p != _coverage.end(); ++p) + p->rotate(-(PI/2.0 - angle), Point(0,0)); + + // intersect trapezoids with actual bridge area to remove extra margins + // and append it to result + intersection(_coverage, this->expolygon, *coverage); + + /* + 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, + ); + } + */ +} + +/* This method returns the bridge edges (as polylines) that are not supported + but would allow the entire bridge area to be bridged with detected angle + if supported too */ +void +BridgeDetector::unsupported_edges(Polylines* unsupported) const +{ + if (this->angle == -1) return; + return this->unsupported_edges(this->angle, unsupported); +} + +void +BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const +{ + // get bridge edges (both contour and holes) + Polylines bridge_edges; + { + Polygons pp = this->expolygon; + bridge_edges.insert(bridge_edges.end(), pp.begin(), pp.end()); // this uses split_at_first_point() + } + + // get unsupported edges + Polygons grown_lower; + offset(this->lower_slices, grown_lower, +this->extrusion_width); + Polylines _unsupported; + diff(bridge_edges, grown_lower, _unsupported); + + /* Split into individual segments and filter out edges parallel to the bridging angle + TODO: angle tolerance should probably be based on segment length and flow width, + so that we build supports whenever there's a chance that at least one or two bridge + extrusions would be anchored within such length (i.e. a slightly non-parallel bridging + direction might still benefit from anchors if long enough) */ + double angle_tolerance = PI / 180.0 * 5.0; + for (Polylines::const_iterator polyline = _unsupported.begin(); polyline != _unsupported.end(); ++polyline) { + Lines lines = polyline->lines(); + for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { + if (!Slic3r::Geometry::directions_parallel(line->direction(), angle)) + unsupported->push_back(*line); + } + } + + /* + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "unsupported_" . rad2deg($angle) . ".svg", + expolygons => [$self->expolygon], + green_expolygons => $self->_anchors, + red_expolygons => union_ex($grown_lower), + no_arrows => 1, + polylines => \@bridge_edges, + red_polylines => $unsupported, + ); + } + */ +} + +#ifdef SLIC3RXS +REGISTER_CLASS(BridgeDetector, "BridgeDetector"); +#endif + +} diff --git a/xs/src/libslic3r/BridgeDetector.hpp b/xs/src/libslic3r/BridgeDetector.hpp new file mode 100644 index 000000000..ab2571289 --- /dev/null +++ b/xs/src/libslic3r/BridgeDetector.hpp @@ -0,0 +1,33 @@ +#ifndef slic3r_BridgeDetector_hpp_ +#define slic3r_BridgeDetector_hpp_ + +#include +#include "ExPolygon.hpp" +#include "ExPolygonCollection.hpp" +#include + +namespace Slic3r { + +class BridgeDetector { + public: + ExPolygon expolygon; + ExPolygonCollection lower_slices; + double extrusion_width; // scaled + double resolution; + double angle; + + BridgeDetector(const ExPolygon &_expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); + bool detect_angle(); + void coverage(Polygons* coverage) const; + void coverage(double angle, Polygons* coverage) const; + void unsupported_edges(Polylines* unsupported) const; + void unsupported_edges(double angle, Polylines* unsupported) const; + + private: + Polylines _edges; // representing the supporting edges + ExPolygons _anchors; +}; + +} + +#endif diff --git a/xs/src/libslic3r/ClipperUtils.cpp b/xs/src/libslic3r/ClipperUtils.cpp index cc77e19ef..672c788de 100644 --- a/xs/src/libslic3r/ClipperUtils.cpp +++ b/xs/src/libslic3r/ClipperUtils.cpp @@ -349,6 +349,23 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, ClipperPaths_to_Slic3rMultiPoints(output, retval); } +void _clipper(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, + const Slic3r::Polygons &clip, Slic3r::Lines &retval, bool safety_offset_) +{ + // convert Lines to Polylines + Slic3r::Polylines polylines; + polylines.reserve(subject.size()); + for (Slic3r::Lines::const_iterator line = subject.begin(); line != subject.end(); ++line) + polylines.push_back(*line); + + // perform operation + _clipper(clipType, polylines, clip, polylines, safety_offset_); + + // convert Polylines to Lines + for (Slic3r::Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) + retval.push_back(*polyline); +} + void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_) { @@ -406,6 +423,7 @@ template void diff(const Slic3r::Polygons template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_); template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_); template void diff(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_); +template void diff(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines &retval, bool safety_offset_); template void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType &retval, bool safety_offset_) @@ -416,6 +434,7 @@ template void intersection(const Slic3r::P template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons &retval, bool safety_offset_); template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_); template void intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval, bool safety_offset_); +template void intersection(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines &retval, bool safety_offset_); template bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_) @@ -426,6 +445,7 @@ bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool s } template bool intersects(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_); template bool intersects(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_); +template bool intersects(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_); void xor_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_) diff --git a/xs/src/libslic3r/ClipperUtils.hpp b/xs/src/libslic3r/ClipperUtils.hpp index 795c092be..8de78a591 100644 --- a/xs/src/libslic3r/ClipperUtils.hpp +++ b/xs/src/libslic3r/ClipperUtils.hpp @@ -77,6 +77,8 @@ void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons &retval, bool safety_offset_); void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines &retval); +void _clipper(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, + const Slic3r::Polygons &clip, Slic3r::Lines &retval); template void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType &retval, bool safety_offset_ = false); diff --git a/xs/src/libslic3r/Geometry.cpp b/xs/src/libslic3r/Geometry.cpp index 8c2664aa1..4cdd9e36b 100644 --- a/xs/src/libslic3r/Geometry.cpp +++ b/xs/src/libslic3r/Geometry.cpp @@ -1,4 +1,5 @@ #include "Geometry.hpp" +#include "ExPolygon.hpp" #include "Line.hpp" #include "PolylineCollection.hpp" #include "clipper.hpp" @@ -111,6 +112,37 @@ directions_parallel(double angle1, double angle2, double max_diff) return diff < max_diff || fabs(diff - PI) < max_diff; } +template +bool +contains_point(const std::vector &vector, const Point &point) +{ + for (typename std::vector::const_iterator it = vector.begin(); it != vector.end(); ++it) { + if (it->contains_point(point)) return true; + } + return false; +} +template bool contains_point(const ExPolygons &vector, const Point &point); + +double +rad2deg(double angle) +{ + return angle / PI * 180.0; +} + +double +rad2deg_dir(double angle) +{ + angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0); + if (angle < 0) angle += PI; + return rad2deg(angle); +} + +double +deg2rad(double angle) +{ + return PI * angle / 180.0; +} + Line MedialAxis::edge_to_line(const VD::edge_type &edge) const { diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp index 8ec192dcc..a5ef6d97c 100644 --- a/xs/src/libslic3r/Geometry.hpp +++ b/xs/src/libslic3r/Geometry.hpp @@ -17,6 +17,10 @@ void chained_path(const Points &points, std::vector &retval, void chained_path(const Points &points, std::vector &retval); template void chained_path_items(Points &points, T &items, T &retval); bool directions_parallel(double angle1, double angle2, double max_diff = 0); +template bool contains_point(const std::vector &vector, const Point &point); +double rad2deg(double angle); +double rad2deg_dir(double angle); +double deg2rad(double angle); class MedialAxis { public: diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 3af5b36d0..514eb51cd 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -12,11 +12,13 @@ class Line; class MultiPoint; class Point; class Pointf; +class Pointf3; typedef Point Vector; typedef std::vector Points; typedef std::vector PointPtrs; typedef std::vector PointConstPtrs; typedef std::vector Pointfs; +typedef std::vector Pointf3s; class Point { diff --git a/xs/src/libslic3r/Polyline.cpp b/xs/src/libslic3r/Polyline.cpp index 724ae4740..f64b1a0b4 100644 --- a/xs/src/libslic3r/Polyline.cpp +++ b/xs/src/libslic3r/Polyline.cpp @@ -1,4 +1,5 @@ #include "Polyline.hpp" +#include "Line.hpp" #include "Polygon.hpp" namespace Slic3r { @@ -10,6 +11,12 @@ Polyline::operator Polylines() const return polylines; } +Polyline::operator Line() const +{ + if (this->points.size() > 2) CONFESS("Can't convert polyline with more than two points to a line"); + return Line(this->points.front(), this->points.back()); +} + Point Polyline::last_point() const { diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index 5462425cf..de5493099 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -12,6 +12,7 @@ typedef std::vector Polylines; class Polyline : public MultiPoint { public: operator Polylines() const; + operator Line() const; Point last_point() const; Point leftmost_point() const; Lines lines() const; diff --git a/xs/xsp/BridgeDetector.xsp b/xs/xsp/BridgeDetector.xsp new file mode 100644 index 000000000..e2318bcbf --- /dev/null +++ b/xs/xsp/BridgeDetector.xsp @@ -0,0 +1,37 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "libslic3r/BridgeDetector.hpp" +%} + +%name{Slic3r::BridgeDetector} class BridgeDetector { + ~BridgeDetector(); + + bool detect_angle(); + Polygons coverage() + %code{% THIS->coverage(&RETVAL); %}; + Polygons coverage_by_angle(double angle) + %code{% THIS->coverage(angle, &RETVAL); %}; + Polylines unsupported_edges() + %code{% THIS->unsupported_edges(&RETVAL); %}; + Polylines unsupported_edges_by_angle(double angle) + %code{% THIS->unsupported_edges(angle, &RETVAL); %}; + double angle() + %code{% RETVAL = THIS->angle; %}; + double resolution() + %code{% RETVAL = THIS->resolution; %}; +%{ + +BridgeDetector* +BridgeDetector::new(expolygon, lower_slices, extrusion_width) + ExPolygon* expolygon; + ExPolygonCollection* lower_slices; + long extrusion_width; + CODE: + RETVAL = new BridgeDetector(*expolygon, *lower_slices, extrusion_width); + OUTPUT: + RETVAL + +%} +}; diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp index ddfa0a369..8a587415c 100644 --- a/xs/xsp/Geometry.xsp +++ b/xs/xsp/Geometry.xsp @@ -55,4 +55,28 @@ chained_path_from(points, start_from) OUTPUT: RETVAL +double +rad2deg(angle) + double angle + CODE: + RETVAL = Slic3r::Geometry::rad2deg(angle); + OUTPUT: + RETVAL + +double +rad2deg_dir(angle) + double angle + CODE: + RETVAL = Slic3r::Geometry::rad2deg_dir(angle); + OUTPUT: + RETVAL + +double +deg2rad(angle) + double angle + CODE: + RETVAL = Slic3r::Geometry::deg2rad(angle); + OUTPUT: + RETVAL + %} diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 8429f3ee7..f7858ad9c 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -165,6 +165,10 @@ GCodeWriter* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +BridgeDetector* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + ExtrusionLoopRole T_UV ExtrusionRole T_UV FlowRole T_UV diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 351e1ccd8..1d5ec4729 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -87,6 +87,9 @@ %typemap{GCodeWriter*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; +%typemap{BridgeDetector*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; %typemap{Surface*}; %typemap{Ref}{simple};