diff --git a/lib/Slic3r/Layer/BridgeDetector.pm b/lib/Slic3r/Layer/BridgeDetector.pm index 5b2098e5e..9d7d33f76 100644 --- a/lib/Slic3r/Layer/BridgeDetector.pm +++ b/lib/Slic3r/Layer/BridgeDetector.pm @@ -55,101 +55,98 @@ sub detect_angle { my @edges = @{$self->_edges}; my $anchors = $self->_anchors; - if (@edges == 2) { - my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges; - my @midpoints = map $_->midpoint, @chords; - my $line_between_midpoints = Slic3r::Line->new(@midpoints); - $self->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. - # in the mean time, we're treating as overhangs all cases where - # our supporting edge is a straight line - if (@{$edges[0]} > 2) { - my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]); - $self->angle($line->direction); - } - } elsif (@edges) { - # 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); - - if (@$anchors) { - # 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 - my %directions_coverage = (); # angle => score - my %directions_avg_length = (); # angle => score - my $line_increment = $self->extrusion_width; - for (my $angle = 0; $angle < PI; $angle += $self->resolution) { - 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 - # 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) } @$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; - - # 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 (!@$anchors) { + $self->angle(undef); + return undef; } - if (defined $self->angle) { - if ($self->angle >= PI) { - $self->angle($self->angle - PI); + # 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; + + 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 (keys %unique_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], + ); } - Slic3r::debugf " Optimal infill angle is %d degrees\n", rad2deg($self->angle); + my @clipped_lines = map Slic3r::Line->new(@$_), @{ intersection_pl(\@lines, [ map @$_, @$my_clip_area ]) }; + + # 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) } @$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; + + # 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; }