<commit_before>package Slic3r::Print;
use Moo;

use File::Basename qw(basename fileparse);
use Math::ConvexHull 1.0.4 qw(convex_hull);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 PI scale unscale move_points);
use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUND);
use Time::HiRes qw(gettimeofday tv_interval);

has 'objects'                => (is => 'rw', default => sub {[]});
has 'copies'                 => (is => 'rw', default => sub {[]});  # obj_idx => [copies...]
has 'total_extrusion_length' => (is => 'rw');
has 'processing_time'        => (is => 'rw', required => 0);

# ordered collection of extrusion paths to build skirt loops
has 'skirt' => (
    is      => 'rw',
    #isa     => 'ArrayRef[Slic3r::ExtrusionLoop]',
    default => sub { [] },
);

sub add_object_from_file {
    my $self = shift;
    my ($input_file) = @_;
    
    my $object;
    if ($input_file =~ /\.stl$/i) {
        my $mesh = Slic3r::Format::STL->read_file($input_file);
        $mesh->check_manifoldness;
        $object = $self->add_object_from_mesh($mesh);
    } elsif ($input_file =~ /\.obj$/i) {
        my $mesh = Slic3r::Format::OBJ->read_file($input_file);
        $mesh->check_manifoldness;
        $object = $self->add_object_from_mesh($mesh);
    } elsif ( $input_file =~ /\.amf(\.xml)?$/i) {
        my ($materials, $meshes_by_material) = Slic3r::Format::AMF->read_file($input_file);
        $_->check_manifoldness for values %$meshes_by_material;
        $object = $self->add_object_from_mesh($meshes_by_material->{_} || +(values %$meshes_by_material)[0]);
    } else {
        die "Input file must have .stl, .obj or .amf(.xml) extension\n";
    }
    $object->input_file($input_file);
    return $object;
}

sub add_object_from_mesh {
    my $self = shift;
    my ($mesh) = @_;
    
    $mesh->rotate($Slic3r::rotate);
    $mesh->scale($Slic3r::scale / $Slic3r::scaling_factor);
    $mesh->align_to_origin;
    
    # initialize print object
    my @size = $mesh->size;
    my $object = Slic3r::Print::Object->new(
        mesh     => $mesh,
        x_length => $size[X],
        y_length => $size[Y],
    );
    
    push @{$self->objects}, $object;
    push @{$self->copies}, [[0, 0]];
    return $object;
}

sub validate {
    my $self = shift;
    
    if ($Slic3r::complete_objects) {
        # check horizontal clearance
        {
            my @a = ();
            for my $obj_idx (0 .. $#{$self->objects}) {
                my $clearance;
                {
                    my @points = map [ @$_[X,Y] ], @{$self->objects->[$obj_idx]->mesh->vertices};
                    my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points));
                    $clearance = +($convex_hull->offset(scale $Slic3r::extruder_clearance_radius / 2, 1, JT_ROUND))[0];
                }
                for my $copy (@{$self->copies->[$obj_idx]}) {
                    my $copy_clearance = $clearance->clone;
                    $copy_clearance->translate(@$copy);
                    if (@{ intersection_ex(\@a, [$copy_clearance]) }) {
                        die "Some objects are too close; your extruder will collide with them.\n";
                    }
                    @a = map @$_, @{union_ex([ @a, $copy_clearance ])};
                }
            }
        }
        
        # check vertical clearance
        {
            my @obj_copies = $self->object_copies;
            pop @obj_copies;  # ignore the last copy: its height doesn't matter
            if (grep { +($self->objects->[$_->[0]]->mesh->size)[Z] > scale $Slic3r::extruder_clearance_height } @obj_copies) {
                die "Some objects are too tall and cannot be printed without extruder collisions.\n";
            }
        }
    }
}

sub object_copies {
    my $self = shift;
    my @oc = ();
    for my $obj_idx (0 .. $#{$self->objects}) {
        push @oc, map [ $obj_idx, $_ ], @{$self->copies->[$obj_idx]};
    }
    return @oc;
}

sub cleanup {
    my $self = shift;
    $_->cleanup for @{$self->objects};
    @{$self->skirt} = ();
    $self->total_extrusion_length(0);
    $self->processing_time(0);
}

sub layer_count {
    my $self = shift;
    my $count = 0;
    foreach my $object (@{$self->objects}) {
        $count = @{$object->layers} if @{$object->layers} > $count;
    }
    return $count;
}

sub duplicate {
    my $self = shift;
    
    if ($Slic3r::duplicate_grid->[X] > 1 || $Slic3r::duplicate_grid->[Y] > 1) {
        if (@{$self->objects} > 1) {
            die "Grid duplication is not supported with multiple objects\n";
        }
        my $object = $self->objects->[0];
        
        # generate offsets for copies
        my $dist = scale $Slic3r::duplicate_distance;
        @{$self->copies->[0]} = ();
        for my $x_copy (1..$Slic3r::duplicate_grid->[X]) {
            for my $y_copy (1..$Slic3r::duplicate_grid->[Y]) {
                push @{$self->copies->[0]}, [
                    ($object->x_length + $dist) * ($x_copy-1),
                    ($object->y_length + $dist) * ($y_copy-1),
                ];
            }
        }
    } elsif ($Slic3r::duplicate > 1) {
        foreach my $copies (@{$self->copies}) {
            @$copies = map [0,0], 1..$Slic3r::duplicate;
        }
        $self->arrange_objects;
    }
}

sub arrange_objects {
    my $self = shift;

    my $total_parts = scalar map @$_, @{$self->copies};
    my $partx = my $party = 0;
    foreach my $object (@{$self->objects}) {
        $partx = $object->x_length if $object->x_length > $partx;
        $party = $object->y_length if $object->y_length > $party;
    }
    my @positions = Slic3r::Geometry::arrange
        ($total_parts, $partx, $party, (map scale $_, @$Slic3r::bed_size), scale $Slic3r::duplicate_distance);
    
    for my $obj_idx (0..$#{$self->objects}) {
        @{$self->copies->[$obj_idx]} = splice @positions, 0, scalar @{$self->copies->[$obj_idx]};
    }
}

sub bounding_box {
    my $self = shift;
    <commit_msg>Take extruder clearance radius into account when autopositioning<commit_after>package Slic3r::Print;
use Moo;

use File::Basename qw(basename fileparse);
use Math::ConvexHull 1.0.4 qw(convex_hull);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 PI scale unscale move_points);
use Slic3r::Geometry::Clipper qw(diff_ex union_ex intersection_ex offset JT_ROUND);
use Time::HiRes qw(gettimeofday tv_interval);

has 'objects'                => (is => 'rw', default => sub {[]});
has 'copies'                 => (is => 'rw', default => sub {[]});  # obj_idx => [copies...]
has 'total_extrusion_length' => (is => 'rw');
has 'processing_time'        => (is => 'rw', required => 0);

# ordered collection of extrusion paths to build skirt loops
has 'skirt' => (
    is      => 'rw',
    #isa     => 'ArrayRef[Slic3r::ExtrusionLoop]',
    default => sub { [] },
);

sub add_object_from_file {
    my $self = shift;
    my ($input_file) = @_;
    
    my $object;
    if ($input_file =~ /\.stl$/i) {
        my $mesh = Slic3r::Format::STL->read_file($input_file);
        $mesh->check_manifoldness;
        $object = $self->add_object_from_mesh($mesh);
    } elsif ($input_file =~ /\.obj$/i) {
        my $mesh = Slic3r::Format::OBJ->read_file($input_file);
        $mesh->check_manifoldness;
        $object = $self->add_object_from_mesh($mesh);
    } elsif ( $input_file =~ /\.amf(\.xml)?$/i) {
        my ($materials, $meshes_by_material) = Slic3r::Format::AMF->read_file($input_file);
        $_->check_manifoldness for values %$meshes_by_material;
        $object = $self->add_object_from_mesh($meshes_by_material->{_} || +(values %$meshes_by_material)[0]);
    } else {
        die "Input file must have .stl, .obj or .amf(.xml) extension\n";
    }
    $object->input_file($input_file);
    return $object;
}

sub add_object_from_mesh {
    my $self = shift;
    my ($mesh) = @_;
    
    $mesh->rotate($Slic3r::rotate);
    $mesh->scale($Slic3r::scale / $Slic3r::scaling_factor);
    $mesh->align_to_origin;
    
    # initialize print object
    my @size = $mesh->size;
    my $object = Slic3r::Print::Object->new(
        mesh     => $mesh,
        x_length => $size[X],
        y_length => $size[Y],
    );
    
    push @{$self->objects}, $object;
    push @{$self->copies}, [[0, 0]];
    return $object;
}

sub validate {
    my $self = shift;
    
    if ($Slic3r::complete_objects) {
        # check horizontal clearance
        {
            my @a = ();
            for my $obj_idx (0 .. $#{$self->objects}) {
                my $clearance;
                {
                    my @points = map [ @$_[X,Y] ], @{$self->objects->[$obj_idx]->mesh->vertices};
                    my $convex_hull = Slic3r::Polygon->new(convex_hull(\@points));
                    $clearance = +($convex_hull->offset(scale $Slic3r::extruder_clearance_radius / 2, 1, JT_ROUND))[0];
                }
                for my $copy (@{$self->copies->[$obj_idx]}) {
                    my $copy_clearance = $clearance->clone;
                    $copy_clearance->translate(@$copy);
                    if (@{ intersection_ex(\@a, [$copy_clearance]) }) {
                        die "Some objects are too close; your extruder will collide with them.\n";
                    }
                    @a = map @$_, @{union_ex([ @a, $copy_clearance ])};
                }
            }
        }
        
        # check vertical clearance
        {
            my @obj_copies = $self->object_copies;
            pop @obj_copies;  # ignore the last copy: its height doesn't matter
            if (grep { +($self->objects->[$_->[0]]->mesh->size)[Z] > scale $Slic3r::extruder_clearance_height } @obj_copies) {
                die "Some objects are too tall and cannot be printed without extruder collisions.\n";
            }
        }
    }
}

sub object_copies {
    my $self = shift;
    my @oc = ();
    for my $obj_idx (0 .. $#{$self->objects}) {
        push @oc, map [ $obj_idx, $_ ], @{$self->copies->[$obj_idx]};
    }
    return @oc;
}

sub cleanup {
    my $self = shift;
    $_->cleanup for @{$self->objects};
    @{$self->skirt} = ();
    $self->total_extrusion_length(0);
    $self->processing_time(0);
}

sub layer_count {
    my $self = shift;
    my $count = 0;
    foreach my $object (@{$self->objects}) {
        $count = @{$object->layers} if @{$object->layers} > $count;
    }
    return $count;
}

sub duplicate {
    my $self = shift;
    
    if ($Slic3r::duplicate_grid->[X] > 1 || $Slic3r::duplicate_grid->[Y] > 1) {
        if (@{$self->objects} > 1) {
            die "Grid duplication is not supported with multiple objects\n";
        }
        my $object = $self->objects->[0];
        
        # generate offsets for copies
        my $dist = scale $Slic3r::duplicate_distance;
        @{$self->copies->[0]} = ();
        for my $x_copy (1..$Slic3r::duplicate_grid->[X]) {
            for my $y_copy (1..$Slic3r::duplicate_grid->[Y]) {
                push @{$self->copies->[0]}, [
                    ($object->x_length + $dist) * ($x_copy-1),
                    ($object->y_length + $dist) * ($y_copy-1),
                ];
            }
        }
    } elsif ($Slic3r::duplicate > 1) {
        foreach my $copies (@{$self->copies}) {
            @$copies = map [0,0], 1..$Slic3r::duplicate;
        }
        $self->arrange_objects;
    }
}

sub arrange_objects {
    my $self = shift;

    my $total_parts = scalar map @$_, @{$self->copies};
    my $partx = my $party = 0;
    foreach my $object (@{$self->objects}) {
        $partx = $object->x_length if $object->x_length > $partx;
        $party = $object->y_length if $object->y_length > $party;
    }
    
    # object distance is max(duplicate_distance, clearance_radius)
    my $distance = $Slic3r::complete_objects && $Slic3r::extruder_clearance_radius > $Slic3r::duplicate_distance
        ? $Slic3r::extruder_clearance_radius
        : $Slic3r::duplicate_distance;
    
    my @positions = Slic3r::Geometry::arrange
        ($total_parts, $partx, $party, (map scale $_, @$Slic3r::bed_size), scale $distance);
    
    for my $obj_idx (0..$#{$self->objects}) {
        @{$self->copies->[$obj_idx]} = splice @positions, 0, scalar @{$self->copies->[$obj_idx]};
    }
}

sub bounding_box {
    my $self = shift;
    
{'fraction_non_alphanumeric': 0.1717975794005361, 'fraction_numerical': 0.010559662090813094, 'mean_word_length': 2.274468085106383, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 202, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, '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': '3994431', 'n_tokens_mistral': 4351, 'n_tokens_neox': 4200, 'n_words': 887}