From 9be57f750da4bf254999fc292dc9ba72f8a73eaf Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 8 Apr 2014 14:51:55 +0200 Subject: [PATCH] Some fixes to bridge direction detection. Includes regression tests --- lib/Slic3r/Layer/BridgeDetector.pm | 62 +++++++++++++++--------------- lib/Slic3r/Polygon.pm | 33 ++++++++++++++++ t/bridges.t | 41 ++++++++++++-------- 3 files changed, 89 insertions(+), 47 deletions(-) diff --git a/lib/Slic3r/Layer/BridgeDetector.pm b/lib/Slic3r/Layer/BridgeDetector.pm index 04de6c20d..48fe03be3 100644 --- a/lib/Slic3r/Layer/BridgeDetector.pm +++ b/lib/Slic3r/Layer/BridgeDetector.pm @@ -2,7 +2,7 @@ package Slic3r::Layer::BridgeDetector; use Moo; use List::Util qw(first sum); -use Slic3r::Geometry qw(PI); +use Slic3r::Geometry qw(PI scaled_epsilon rad2deg epsilon); use Slic3r::Geometry::Clipper qw(intersection_pl intersection_ex); has 'lower_slices' => (is => 'ro', required => 1); # ExPolygons or ExPolygonCollection @@ -20,20 +20,7 @@ sub detect_angle { foreach my $lower (@lower) { # turn bridge contour and holes into polylines and then clip them # with each lower slice's contour - my @clipped = @{intersection_pl([ map $_->split_at_first_point, @$grown ], [$lower->contour])}; - if (@clipped == 2) { - # If the split_at_first_point() call above happens to split the polygon inside the clipping area - # we would get two consecutive polylines instead of a single one, so we use this ugly hack to - # recombine them back into a single one in order to trigger the @edges == 2 logic below. - # This needs to be replaced with something way better. - if (points_coincide($clipped[0][0], $clipped[-1][-1])) { - @clipped = (Slic3r::Polyline->new(@{$clipped[-1]}, @{$clipped[0]})); - } - if (points_coincide($clipped[-1][0], $clipped[0][-1])) { - @clipped = (Slic3r::Polyline->new(@{$clipped[0]}, @{$clipped[1]})); - } - } - push @edges, @clipped; + push @edges, map @{$_->clip_as_polyline([$lower->contour])}, @$grown; } Slic3r::debugf " bridge has %d support(s)\n", scalar(@edges); @@ -54,8 +41,9 @@ sub detect_angle { my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges; my @midpoints = map $_->midpoint, @chords; my $line_between_midpoints = Slic3r::Line->new(@midpoints); - $bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction); - } elsif (@edges == 1) { + $bridge_angle = $line_between_midpoints->direction; + } elsif (@edges == 1 && $edges[0][0]->coincides_with($edges[0][-1])) { + # Don't use this logic if $edges[0] is actually a closed loop # TODO: this case includes both U-shaped bridges and plain overhangs; # we need a trapezoidation algorithm to detect the actual bridged area # and separate it from the overhang area. @@ -63,7 +51,7 @@ sub detect_angle { # our supporting edge is a straight line if (@{$edges[0]} > 2) { my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]); - $bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction); + $bridge_angle = $line->direction; } } elsif (@edges) { # inset the bridge expolygon; we'll use this one to clip our test lines @@ -83,43 +71,55 @@ sub detect_angle { my %directions = (); # angle => score my $angle_increment = PI/36; # 5° my $line_increment = $self->infill_flow->scaled_width; - for (my $angle = 0; $angle <= PI; $angle += $angle_increment) { + for (my $angle = 0; $angle < PI; $angle += $angle_increment) { + my $my_inset = [ map $_->clone, @$inset ]; + my $my_anchors = [ map $_->clone, @$anchors ]; + # rotate everything - the center point doesn't matter - $_->rotate($angle, [0,0]) for @$inset, @$anchors; + $_->rotate($angle, [0,0]) for @$my_inset, @$my_anchors; # generate lines in this direction - my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$anchors ]); + my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$my_anchors ]); my @lines = (); for (my $x = $bounding_box->x_min; $x <= $bounding_box->x_max; $x += $line_increment) { - push @lines, Slic3r::Polyline->new([$x, $bounding_box->y_min], [$x, $bounding_box->y_max]); + push @lines, Slic3r::Polyline->new( + [$x, $bounding_box->y_min + scaled_epsilon], + [$x, $bounding_box->y_max - scaled_epsilon], + ); } - - my @clipped_lines = map Slic3r::Line->new(@$_), @{ intersection_pl(\@lines, [ map @$_, @$inset ]) }; + + my @clipped_lines = map Slic3r::Line->new(@$_), @{ intersection_pl(\@lines, [ map @$_, @$my_inset ]) }; # remove any line not having both endpoints within anchors # NOTE: these calls to contains_point() probably need to check whether the point # is on the anchor boundaries too @clipped_lines = grep { my $line = $_; - !(first { $_->contains_point($line->a) } @$anchors) - && !(first { $_->contains_point($line->b) } @$anchors); + (first { $_->contains_point($line->a) } @$my_anchors) + && (first { $_->contains_point($line->b) } @$my_anchors); } @clipped_lines; # sum length of bridged lines - $directions{-$angle} = sum(map $_->length, @clipped_lines) // 0; + $directions{$angle} = sum(map $_->length, @clipped_lines) // 0; } # this could be slightly optimized with a max search instead of the sort my @sorted_directions = sort { $directions{$a} <=> $directions{$b} } keys %directions; - + # the best direction is the one causing most lines to be bridged - $bridge_angle = Slic3r::Geometry::rad2deg_dir($sorted_directions[-1]); + $bridge_angle = $sorted_directions[-1]; } } - Slic3r::debugf " Optimal infill angle is %d degrees\n", $bridge_angle - if defined $bridge_angle; + if (defined $bridge_angle) { + if ($bridge_angle >= PI - epsilon) { + $bridge_angle -= PI; + } + + Slic3r::debugf " Optimal infill angle is %d degrees\n", rad2deg($bridge_angle) + if defined $bridge_angle; + } return $bridge_angle; } diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index ce5001ef6..c43356ae0 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -8,6 +8,7 @@ use parent 'Slic3r::Polyline'; use Slic3r::Geometry qw( polygon_segment_having_point PI X1 X2 Y1 Y2 epsilon); +use Slic3r::Geometry::Clipper qw(intersection_pl); sub wkt { my $self = shift; @@ -50,4 +51,36 @@ sub concave_points { -1 .. ($#points-1); } +sub clip_as_polyline { + my ($self, $polygons) = @_; + + my $self_pl = $self->split_at_first_point; + + # Clipper will remove a polyline segment if first point coincides with last one. + # Until that bug is not fixed upstream, we move one of those points slightly. + $self_pl->[0]->translate(1, 0); + + my @polylines = @{intersection_pl([$self_pl], $polygons)}; + if (@polylines == 2) { + # If the split_at_first_point() call above happens to split the polygon inside the clipping area + # we would get two consecutive polylines instead of a single one, so we use this ugly hack to + # recombine them back into a single one in order to trigger the @edges == 2 logic below. + # This needs to be replaced with something way better. + if ($polylines[0][-1]->coincides_width($self_pl->[-1]) && $polylines[-1][0]->coincides_width($self_pl->[0])) { + my $p = $polylines[0]->clone; + $p->pop_back; + $p->append(@{$polylines[-1]}); + return [$p]; + } + if ($polylines[0][0]->coincides_width($self_pl->[0]) && $polylines[-1][-1]->coincides_width($self_pl->[-1])) { + my $p = $polylines[-1]->clone; + $p->pop_back; + $p->append(@{$polylines[0]}); + return [$p]; + } + } + + return [ @polylines ]; +} + 1; \ No newline at end of file diff --git a/t/bridges.t b/t/bridges.t index 8a5d7c48a..20a8f081c 100644 --- a/t/bridges.t +++ b/t/bridges.t @@ -1,4 +1,4 @@ -use Test::More tests => 1; +use Test::More tests => 2; use strict; use warnings; @@ -9,26 +9,35 @@ BEGIN { use List::Util qw(first); use Slic3r; -use Slic3r::Geometry qw(scale); +use Slic3r::Geometry qw(scale epsilon rad2deg); use Slic3r::Test; my $flow = Slic3r::Flow->new(width => 0.5, spacing => 0.45, nozzle_diameter => 0.5); { - my $lower = Slic3r::ExPolygon->new( - Slic3r::Polygon->new_scale([0,0], [20,0], [20,10], [0,10]), - Slic3r::Polygon->new_scale([2,2], [2,8], [18,8], [18,2]), - ); - my $bridge = $lower->[1]->clone; - $bridge->reverse; - $bridge = Slic3r::ExPolygon->new($bridge); - my $bd = Slic3r::Layer::BridgeDetector->new( - lower_slices => [$lower], - perimeter_flow => $flow, - infill_flow => $flow, - ); - # 0 is North/South - is $bd->detect_angle($bridge), 0, 'correct bridge angle detected'; + my $test = sub { + my ($bridge_size, $expected_angle) = @_; + + my ($x, $y) = @$bridge_size; + my $lower = Slic3r::ExPolygon->new( + Slic3r::Polygon->new_scale([-2,-2], [$x+2,-2], [$x+2,$y+2], [-2,$y+2]), + Slic3r::Polygon->new_scale([0,0], [0,$y], [$x,$y], [$x,0]), + ); + $lower->translate(scale 20, scale 20); # avoid negative coordinates for easier SVG preview + my $bridge = $lower->[1]->clone; + $bridge->reverse; + $bridge = Slic3r::ExPolygon->new($bridge); + my $bd = Slic3r::Layer::BridgeDetector->new( + lower_slices => [$lower], + perimeter_flow => $flow, + infill_flow => $flow, + ); + + ok abs(rad2deg($bd->detect_angle($bridge)) - $expected_angle) < epsilon, 'correct bridge angle detected'; + }; + + $test->([20,10], 90); + $test->([10,20], 0); } __END__