<commit_before>package Slic3r::Layer::Region;
use Moo;

use List::Util qw(sum first);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(PI A B scale chained_path_items points_coincide);
use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex 
    offset offset2 offset2_ex PFT_EVENODD union_pt traverse_pt diff intersection);
use Slic3r::Surface ':types';

has 'layer' => (
    is          => 'ro',
    weak_ref    => 1,
    required    => 1,
    trigger     => 1,
    handles     => [qw(id slice_z print_z height flow)],
);
has 'region'            => (is => 'ro', required => 1, handles => [qw(extruders)]);
has 'perimeter_flow'    => (is => 'rw');
has 'infill_flow'       => (is => 'rw');
has 'solid_infill_flow' => (is => 'rw');
has 'top_infill_flow'   => (is => 'rw');
has 'infill_area_threshold' => (is => 'lazy');
has 'overhang_width'    => (is => 'lazy');

# collection of surfaces generated by slicing the original geometry
# divided by type top/bottom/internal
has 'slices' => (is => 'rw', default => sub { [] });

# collection of polygons or polylines representing thin walls contained 
# in the original geometry
has 'thin_walls' => (is => 'rw', default => sub { [] });

# collection of polygons or polylines representing thin infill regions that
# need to be filled with a medial axis
has 'thin_fills' => (is => 'rw', default => sub { [] });

# collection of surfaces for infill generation
has 'fill_surfaces' => (is => 'rw', default => sub { [] });

# ordered collection of extrusion paths/loops to build all perimeters
has 'perimeters' => (is => 'rw', default => sub { [] });

# ordered collection of extrusion paths to fill surfaces
has 'fills' => (is => 'rw', default => sub { [] });

sub BUILD {
    my $self = shift;
    $self->_update_flows;
}

sub _trigger_layer {
    my $self = shift;
    $self->_update_flows;
}

sub _update_flows {
    my $self = shift;
    return if !$self->region;
    
    if ($self->id == 0) {
        for (qw(perimeter infill solid_infill top_infill)) {
            my $method = "${_}_flow";
            $self->$method
                ($self->region->first_layer_flows->{$_} || $self->region->flows->{$_});
        } 
    } else {
        $self->perimeter_flow($self->region->flows->{perimeter});
        $self->infill_flow($self->region->flows->{infill});
        $self->solid_infill_flow($self->region->flows->{solid_infill});
        $self->top_infill_flow($self->region->flows->{top_infill});
    }
}

sub _build_overhang_width {
    my $self = shift;
    my $threshold_rad = PI/2 - atan2($self->perimeter_flow->width / $self->height / 2, 1);
    return scale($self->height * ((cos $threshold_rad) / (sin $threshold_rad)));
}

sub _build_infill_area_threshold {
    my $self = shift;
    return $self->solid_infill_flow->scaled_spacing ** 2;
}

# build polylines from lines
sub make_surfaces {
    my $self = shift;
    my ($loops) = @_;
    
    return if !@$loops;
    $self->slices([ _merge_loops($loops) ]);
    
    # detect thin walls by offsetting slices by half extrusion inwards
    if ($Slic3r::Config->thin_walls) {
        $self->thin_walls([]);
        # we use spacing here because there could be a case where
        # the slice collapses with width but doesn't collapse with spacing,
        # thus causing both perimeters and medial axis to be generated
        my $width = $self->perimeter_flow->scaled_spacing;
        my $diff = diff_ex(
            [ map $_->p, @{$self->slices} ],
            [ offset2([ map $_->p, @{$self->slices} ], -$width*0.5, +$width*0.5) ],
            1,
        );
        
        my $area_threshold = $width ** 2;
        if (@$diff = grep { $_->area > $area_threshold } @$diff) {
            @{$self->thin_walls} = map $_->medial_axis($width), @$diff;
            Slic3r::debugf "  %d thin walls detected\n", scalar(@{$self->thin_walls});
        }
    }
    
    if (0) {
        require "Slic3r/SVG.pm";
        Slic3r::SVG::output("surfaces.svg",
            polygons        => [ map $_->contour, @{$self->slices} ],
            red_polygons    => [ map $_->p, map @{$_->holes}, @{$self->slices} ],
        );
    }
}

sub _merge_loops {
    my ($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.0499;
    @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);
    
    return map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), @$expolygons;
}

sub make_perimeters {
    my $self = shift;
    
    my $perimeter_spacing   = $self->perimeter_flow->scaled_spacing;
    my $infill_spacing      = $self->solid_infill_flow->scaled_spacing;
    my $gap_area_threshold  = $self->perimeter_flow->scaled_width ** 2;
    
    $self->perimeters([]);
    $self->fill_surfaces([]);
    $self->thin_fills([]);
    
    my @contours    = ();    # array of Polygons with ccw orientation<commit_msg>Fix wrong slices when many overlapping facets were present in the model<commit_after>package Slic3r::Layer::Region;
use Moo;

use List::Util qw(sum first);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(PI A B scale chained_path_items points_coincide);
use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex 
    offset offset2 offset2_ex PFT_EVENODD union_pt traverse_pt diff intersection
    union diff);
use Slic3r::Surface ':types';

has 'layer' => (
    is          => 'ro',
    weak_ref    => 1,
    required    => 1,
    trigger     => 1,
    handles     => [qw(id slice_z print_z height flow)],
);
has 'region'            => (is => 'ro', required => 1, handles => [qw(extruders)]);
has 'perimeter_flow'    => (is => 'rw');
has 'infill_flow'       => (is => 'rw');
has 'solid_infill_flow' => (is => 'rw');
has 'top_infill_flow'   => (is => 'rw');
has 'infill_area_threshold' => (is => 'lazy');
has 'overhang_width'    => (is => 'lazy');

# collection of surfaces generated by slicing the original geometry
# divided by type top/bottom/internal
has 'slices' => (is => 'rw', default => sub { [] });

# collection of polygons or polylines representing thin walls contained 
# in the original geometry
has 'thin_walls' => (is => 'rw', default => sub { [] });

# collection of polygons or polylines representing thin infill regions that
# need to be filled with a medial axis
has 'thin_fills' => (is => 'rw', default => sub { [] });

# collection of surfaces for infill generation
has 'fill_surfaces' => (is => 'rw', default => sub { [] });

# ordered collection of extrusion paths/loops to build all perimeters
has 'perimeters' => (is => 'rw', default => sub { [] });

# ordered collection of extrusion paths to fill surfaces
has 'fills' => (is => 'rw', default => sub { [] });

sub BUILD {
    my $self = shift;
    $self->_update_flows;
}

sub _trigger_layer {
    my $self = shift;
    $self->_update_flows;
}

sub _update_flows {
    my $self = shift;
    return if !$self->region;
    
    if ($self->id == 0) {
        for (qw(perimeter infill solid_infill top_infill)) {
            my $method = "${_}_flow";
            $self->$method
                ($self->region->first_layer_flows->{$_} || $self->region->flows->{$_});
        } 
    } else {
        $self->perimeter_flow($self->region->flows->{perimeter});
        $self->infill_flow($self->region->flows->{infill});
        $self->solid_infill_flow($self->region->flows->{solid_infill});
        $self->top_infill_flow($self->region->flows->{top_infill});
    }
}

sub _build_overhang_width {
    my $self = shift;
    my $threshold_rad = PI/2 - atan2($self->perimeter_flow->width / $self->height / 2, 1);
    return scale($self->height * ((cos $threshold_rad) / (sin $threshold_rad)));
}

sub _build_infill_area_threshold {
    my $self = shift;
    return $self->solid_infill_flow->scaled_spacing ** 2;
}

# build polylines from lines
sub make_surfaces {
    my $self = shift;
    my ($loops) = @_;
    
    return if !@$loops;
    $self->slices([ _merge_loops($loops) ]);
    
    # detect thin walls by offsetting slices by half extrusion inwards
    if ($Slic3r::Config->thin_walls) {
        $self->thin_walls([]);
        # we use spacing here because there could be a case where
        # the slice collapses with width but doesn't collapse with spacing,
        # thus causing both perimeters and medial axis to be generated
        my $width = $self->perimeter_flow->scaled_spacing;
        my $diff = diff_ex(
            [ map $_->p, @{$self->slices} ],
            [ offset2([ map $_->p, @{$self->slices} ], -$width*0.5, +$width*0.5) ],
            1,
        );
        
        my $area_threshold = $width ** 2;
        if (@$diff = grep { $_->area > $area_threshold } @$diff) {
            @{$self->thin_walls} = map $_->medial_axis($width), @$diff;
            Slic3r::debugf "  %d thin walls detected\n", scalar(@{$self->thin_walls});
        }
    }
    
    if (0) {
        require "Slic3r/SVG.pm";
        Slic3r::SVG::output("surfaces.svg",
            polygons        => [ map $_->contour, @{$self->slices} ],
            red_polygons    => [ map $_->p, map @{$_->holes}, @{$self->slices} ],
        );
    }
}

sub _merge_loops {
    my ($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
    # we don't perform a safety offset now because it might reverse cw loops
    my $slices = [];
    foreach my $loop (@loops) {
        $slices = $loop->is_counter_clockwise
            ? union([ $loop, @$slices ])
            : diff($slices, [$loop]);
    }
    
    # perform a safety offset to merge very close facets (TODO: find test case for this)
    $safety_offset //= scale 0.0499;
    $slices = [ offset2_ex($slices, +$safety_offset, -$safety_offset) ];
    
    Slic3r::debugf "  %d surface(s) having %d holes detected from %d polylines\n",
        scalar(@$slices), scalar(map $_->holes, @$slices), scalar(@$loops) if $Slic3r::debug;
    
    return map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), @$slices;
}

sub make_perimeters {
    my $self = shift;
    
    my $perimeter_spacing   = $self->perimeter_flow->scaled_spacing;
    my $infill_spacing      = $self->solid_infill_flow->scaled_spacing;
    my $gap_area_threshold  = $self->perimeter_flow->scaled_width ** 2;
    
    $self->perimeters([]);
    $self->fill_surfaces([]);
    $self->thin_fills([]);
    
    my @contours    = ();    # array of Polygons with ccw orientation
{'fraction_non_alphanumeric': 0.15177458232898347, 'fraction_numerical': 0.00689884465131743, 'mean_word_length': 2.9423328964613367, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 224, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6058417', 'n_tokens_mistral': 4138, 'n_tokens_neox': 3981, 'n_words': 1081}