From 17f76c4951f98106257646be7c1fbcc3befd425c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 18 Apr 2013 18:43:46 +0200 Subject: [PATCH] Move bridge detection to its own method --- lib/Slic3r/Layer/Region.pm | 212 +++++++++++++++++++------------------ 1 file changed, 108 insertions(+), 104 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index e58b77b05..bdabbd09c 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -517,114 +517,118 @@ sub process_external_surfaces { } # detect bridge direction (skip bottom layer) - if ($self->id > 0) { - my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; # surfaces - my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons - - foreach my $surface (@bottom) { - # detect what edges lie on lower slices - my @edges = (); # polylines - foreach my $lower (@lower) { - # turn bridge contour and holes into polylines and then clip them - # with each lower slice's contour - my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @{$surface->expolygon}; - 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]})); - } + $self->_detect_bridges if $self->id > 0; +} + +sub _detect_bridges { + my $self = shift; + + my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; # surfaces + my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons + + foreach my $surface (@bottom) { + # detect what edges lie on lower slices + my @edges = (); # polylines + foreach my $lower (@lower) { + # turn bridge contour and holes into polylines and then clip them + # with each lower slice's contour + my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @{$surface->expolygon}; + 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]})); } - push @edges, @clipped; - } - - Slic3r::debugf "Found bridge on layer %d with %d support(s)\n", $self->id, scalar(@edges); - next if !@edges; - - my $bridge_angle = undef; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("bridge_$surface.svg", - expolygons => [ $surface->expolygon ], - red_expolygons => [ @lower ], - polylines => [ @edges ], - ); - } - - 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); - $bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction); - } elsif (@edges == 1) { - # 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]); - $bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction); + if (points_coincide($clipped[-1][0], $clipped[0][-1])) { + @clipped = (Slic3r::Polyline->new(@{$clipped[0]}, @{$clipped[1]})); } - } elsif (@edges) { - # inset the bridge expolygon; we'll use this one to clip our test lines - my $inset = [ $surface->expolygon->offset_ex($self->infill_flow->scaled_width) ]; - - # detect anchors as intersection between our bridge expolygon and the lower slices - my $anchors = intersection_ex( - [ $surface->p ], - [ map @$_, @lower ], - ); - - # 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 = (); # 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) { - # rotate everything - the center point doesn't matter - $_->rotate($angle, [0,0]) for @$inset, @$anchors; - - # generate lines in this direction - my $bounding_box = [ Slic3r::Geometry::bounding_box([ map @$_, map @$_, @$anchors ]) ]; - my @lines = (); - for (my $x = $bounding_box->[X1]; $x <= $bounding_box->[X2]; $x += $line_increment) { - push @lines, [ [$x, $bounding_box->[Y1]], [$x, $bounding_box->[Y2]] ]; - } - - # TODO: use a multi_polygon_multi_linestring_intersection() call - my @clipped_lines = map @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection($_, \@lines) }, @$inset; - - # remove any line not having both endpoints within anchors - @clipped_lines = grep { - my $line = $_; - !(first { $_->encloses_point_quick($line->[A]) } @$anchors) - && !(first { $_->encloses_point_quick($line->[B]) } @$anchors); - } @clipped_lines; - - # sum length of bridged lines - $directions{-$angle} = sum(map Slic3r::Geometry::line_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]); } - - Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n", - $self->id, $bridge_angle if defined $bridge_angle; - - $surface->bridge_angle($bridge_angle); + push @edges, @clipped; } + + Slic3r::debugf "Found bridge on layer %d with %d support(s)\n", $self->id, scalar(@edges); + next if !@edges; + + my $bridge_angle = undef; + + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output("bridge_$surface.svg", + expolygons => [ $surface->expolygon ], + red_expolygons => [ @lower ], + polylines => [ @edges ], + ); + } + + 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); + $bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction); + } elsif (@edges == 1) { + # 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]); + $bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction); + } + } elsif (@edges) { + # inset the bridge expolygon; we'll use this one to clip our test lines + my $inset = [ $surface->expolygon->offset_ex($self->infill_flow->scaled_width) ]; + + # detect anchors as intersection between our bridge expolygon and the lower slices + my $anchors = intersection_ex( + [ $surface->p ], + [ map @$_, @lower ], + ); + + # 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 = (); # 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) { + # rotate everything - the center point doesn't matter + $_->rotate($angle, [0,0]) for @$inset, @$anchors; + + # generate lines in this direction + my $bounding_box = [ Slic3r::Geometry::bounding_box([ map @$_, map @$_, @$anchors ]) ]; + my @lines = (); + for (my $x = $bounding_box->[X1]; $x <= $bounding_box->[X2]; $x += $line_increment) { + push @lines, [ [$x, $bounding_box->[Y1]], [$x, $bounding_box->[Y2]] ]; + } + + # TODO: use a multi_polygon_multi_linestring_intersection() call + my @clipped_lines = map @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection($_, \@lines) }, @$inset; + + # remove any line not having both endpoints within anchors + @clipped_lines = grep { + my $line = $_; + !(first { $_->encloses_point_quick($line->[A]) } @$anchors) + && !(first { $_->encloses_point_quick($line->[B]) } @$anchors); + } @clipped_lines; + + # sum length of bridged lines + $directions{-$angle} = sum(map Slic3r::Geometry::line_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]); + } + + Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n", + $self->id, $bridge_angle if defined $bridge_angle; + + $surface->bridge_angle($bridge_angle); } }