diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index d91b185ea..9dedc7a96 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -14,7 +14,7 @@ our $clipper = Math::Clipper->new; sub safety_offset { my ($polygons, $factor) = @_; - return Math::Clipper::offset($polygons, $factor || (scale 1e-05), 100000, JT_MITER, 2); + return Math::Clipper::offset($polygons, $factor // (scale 1e-05), 100000, JT_MITER, 2); } sub offset { diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 3a1d2b0fa..2a2736719 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -118,11 +118,28 @@ sub make_surfaces { } sub _merge_loops { - my ($loops) = @_; + my ($loops, $safety_offset) = @_; - my $safety_offset = scale 0.1; - # merge everything - my $expolygons = [ map $_->offset_ex(-$safety_offset), @{union_ex(safety_offset($loops, $safety_offset))} ]; + # Input loops are not suitable for evenodd nor nonzero fill types, as we might get + # two consecutive concentric loops having the same winding order - and we have to + # respect such order. In that case, evenodd would create wrong inversions, and nonzero + # would ignore holes inside two concentric contours. + # So we're ordering loops and collapse consecutive concentric loops having the same + # winding order. + # TODO: find a faster algorithm for this. + my @loops = sort { $a->encloses_point($b->[0]) ? 0 : 1 } @$loops; # outer first + $safety_offset //= scale 0.1; + @loops = @{ safety_offset(\@loops, $safety_offset) }; + my $expolygons = []; + while (my $loop = shift @loops) { + bless $loop, 'Slic3r::Polygon'; + if ($loop->is_counter_clockwise) { + $expolygons = union_ex([ $loop, map @$_, @$expolygons ]); + } else { + $expolygons = diff_ex([ map @$_, @$expolygons ], [$loop]); + } + } + $expolygons = [ map $_->offset_ex(-$safety_offset), @$expolygons ]; Slic3r::debugf " %d surface(s) having %d holes detected from %d polylines\n", scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops); diff --git a/t/loops.t b/t/loops.t index c36505e4e..d0340b200 100644 --- a/t/loops.t +++ b/t/loops.t @@ -13,6 +13,12 @@ use Slic3r; use Slic3r::Test; { + # We only need to slice at one height, so we'll build a non-manifold mesh + # that still produces complete loops at that height. Triangular walls are + # enough for this purpose. + # Basically we want to check what happens when three concentric loops happen + # to be at the same height, the two external ones being ccw and the other being + # a hole, thus cw. my (@vertices, @facets) = (); Slic3r::Test::add_facet($_, \@vertices, \@facets) for # external surface below the slicing Z @@ -39,7 +45,7 @@ use Slic3r::Test; is scalar(@$loops), 3, 'correct number of loops detected'; is scalar(grep $_->is_counter_clockwise, @$loops), 2, 'correct number of ccw loops detected'; - my @surfaces = Slic3r::Layer::Region::_merge_loops($loops); + my @surfaces = Slic3r::Layer::Region::_merge_loops($loops, 0); is scalar(@surfaces), 1, 'one surface detected'; is scalar(@{$surfaces[0]->expolygon})-1, 1, 'surface has one hole'; }