Multiple objects autoplating (--merge, from command line only)

This commit is contained in:
Alessandro Ranellucci 2012-04-29 12:51:20 +02:00
parent 222e9df7f9
commit ed4a5739f6
10 changed files with 692 additions and 651 deletions

View File

@ -28,11 +28,11 @@ lib/Slic3r/GUI/OptionsGroup.pm
lib/Slic3r/GUI/SkeinPanel.pm lib/Slic3r/GUI/SkeinPanel.pm
lib/Slic3r/Layer.pm lib/Slic3r/Layer.pm
lib/Slic3r/Line.pm lib/Slic3r/Line.pm
lib/Slic3r/Perimeter.pm
lib/Slic3r/Point.pm lib/Slic3r/Point.pm
lib/Slic3r/Polygon.pm lib/Slic3r/Polygon.pm
lib/Slic3r/Polyline.pm lib/Slic3r/Polyline.pm
lib/Slic3r/Print.pm lib/Slic3r/Print.pm
lib/Slic3r/Print/Object.pm
lib/Slic3r/Skein.pm lib/Slic3r/Skein.pm
lib/Slic3r/Surface.pm lib/Slic3r/Surface.pm
lib/Slic3r/SVG.pm lib/Slic3r/SVG.pm

View File

@ -95,6 +95,8 @@ The author is Alessandro Ranellucci (me).
--post-process Generated G-code will be processed with the supplied script; --post-process Generated G-code will be processed with the supplied script;
call this more than once to process through multiple scripts. call this more than once to process through multiple scripts.
--export-svg Export a SVG file containing slices instead of G-code. --export-svg Export a SVG file containing slices instead of G-code.
--merge If multiple files are supplied, they will be composed into a single
print rather than processed individually.
Printer options: Printer options:
--nozzle-diameter Diameter of nozzle in mm (default: 0.5) --nozzle-diameter Diameter of nozzle in mm (default: 0.5)

View File

@ -28,11 +28,11 @@ use Slic3r::Format::STL;
use Slic3r::Geometry qw(PI); use Slic3r::Geometry qw(PI);
use Slic3r::Layer; use Slic3r::Layer;
use Slic3r::Line; use Slic3r::Line;
use Slic3r::Perimeter;
use Slic3r::Point; use Slic3r::Point;
use Slic3r::Polygon; use Slic3r::Polygon;
use Slic3r::Polyline; use Slic3r::Polyline;
use Slic3r::Print; use Slic3r::Print;
use Slic3r::Print::Object;
use Slic3r::Skein; use Slic3r::Skein;
use Slic3r::Surface; use Slic3r::Surface;
use Slic3r::TriangleMesh; use Slic3r::TriangleMesh;

View File

@ -2,7 +2,7 @@ package Slic3r::Layer;
use Moo; use Moo;
use Math::Clipper ':all'; use Math::Clipper ':all';
use Slic3r::Geometry qw(scale collinear X Y A B PI rad2deg_dir bounding_box_center); use Slic3r::Geometry qw(scale unscale collinear X Y A B PI rad2deg_dir bounding_box_center shortest_path);
use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex xor_ex is_counter_clockwise); use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex xor_ex is_counter_clockwise);
# a sequential number of layer, starting at 0 # a sequential number of layer, starting at 0
@ -58,13 +58,6 @@ has 'perimeters' => (
default => sub { [] }, default => sub { [] },
); );
# ordered collection of extrusion paths to build skirt loops
has 'skirts' => (
is => 'rw',
#isa => 'ArrayRef[Slic3r::ExtrusionLoop]',
default => sub { [] },
);
# ordered collection of extrusion paths to fill surfaces for support material # ordered collection of extrusion paths to fill surfaces for support material
has 'support_fills' => ( has 'support_fills' => (
is => 'rw', is => 'rw',
@ -167,6 +160,143 @@ sub make_surfaces {
} }
} }
sub make_perimeters {
my $self = shift;
Slic3r::debugf "Making perimeters for layer %d\n", $self->id;
# this array will hold one arrayref per original surface (island);
# each item of this arrayref is an arrayref representing a depth (from outer
# perimeters to inner); each item of this arrayref is an ExPolygon:
# @perimeters = (
# [ # first island
# [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 0: outer loop
# [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 1: inner loop
# ],
# [ # second island
# ...
# ]
# )
my @perimeters = (); # one item per depth; each item
# organize islands using a shortest path search
my @surfaces = @{shortest_path([
map [ $_->contour->[0], $_ ], @{$self->slices},
])};
# for each island:
foreach my $surface (@surfaces) {
my @last_offsets = ($surface->expolygon);
my $distance = 0;
# experimental hole compensation (see ArcCompensation in the RepRap wiki)
foreach my $hole ($last_offsets[0]->holes) {
my $area = abs($hole->area);last;
next unless $area <= $Slic3r::small_perimeter_area;
my $radius = sqrt($area / PI);
my $new_radius = (scale($Slic3r::flow_width) + sqrt((scale($Slic3r::flow_width)**2) + (4*($radius**2)))) / 2;
@$hole = map Slic3r::Point->new($_), @{ +($hole->offset(+ ($new_radius - $radius)))[0] };
}
# create other offsets
push @perimeters, [];
for (my $loop = 0; $loop < $Slic3r::perimeters; $loop++) {
# offsetting a polygon can result in one or many offset polygons
@last_offsets = map $_->offset_ex(-$distance), @last_offsets if $distance;
last if !@last_offsets;
push @{ $perimeters[-1] }, [@last_offsets];
# offset distance for inner loops
$distance = scale $Slic3r::flow_spacing;
}
# create one more offset to be used as boundary for fill
{
my @fill_boundaries = map $_->offset_ex(-$distance), @last_offsets;
push @{ $self->fill_boundaries }, @fill_boundaries;
# detect the small gaps that we need to treat like thin polygons,
# thus generating the skeleton and using it to fill them
my $small_gaps = diff_ex(
[ map @$_, map $_->offset_ex(-$distance/2), map @$_, @{$perimeters[-1]} ],
[ map @$_, map $_->offset_ex(+$distance/2), @fill_boundaries ],
);
push @{ $self->thin_fills },
grep $_,
map $_->medial_axis(scale $Slic3r::flow_width),
@$small_gaps if 0;
}
}
# process one island (original surface) at time
foreach my $island (@perimeters) {
# do holes starting from innermost one
my @holes = ();
my @hole_depths = map [ map $_->holes, @$_ ], @$island;
# organize the outermost hole loops using a shortest path search
@{$hole_depths[0]} = @{shortest_path([
map [ $_->[0], $_ ], @{$hole_depths[0]},
])};
CYCLE: while (map @$_, @hole_depths) {
shift @hole_depths while !@{$hole_depths[0]};
# take first available hole
push @holes, shift @{$hole_depths[0]};
my $current_depth = 0;
while (1) {
$current_depth++;
# look for the hole containing this one if any
next CYCLE if !$hole_depths[$current_depth];
my $parent_hole;
for (@{$hole_depths[$current_depth]}) {
if ($_->encloses_point($holes[-1]->[0])) {
$parent_hole = $_;
last;
}
}
next CYCLE if !$parent_hole;
# look for other holes contained in such parent
for (@{$hole_depths[$current_depth-1]}) {
if ($parent_hole->encloses_point($_->[0])) {
# we have a sibling, so let's move onto next iteration
next CYCLE;
}
}
push @holes, $parent_hole;
@{$hole_depths[$current_depth]} = grep $_ ne $parent_hole, @{$hole_depths[$current_depth]};
}
}
foreach my $hole (@holes) {
push @{ $self->perimeters }, Slic3r::ExtrusionLoop->new(polygon => $hole, role => 'perimeter');
}
# do contours starting from innermost one
foreach my $contour (map $_->contour, map @$_, reverse @$island) {
push @{ $self->perimeters }, Slic3r::ExtrusionLoop->new(polygon => $contour, role => 'perimeter');
}
}
# detect small perimeters by checking their area
for (@{ $self->perimeters }) {
$_->role('small-perimeter') if abs($_->polygon->area) < $Slic3r::small_perimeter_area;
}
# add thin walls as perimeters
for (@{ $self->thin_walls }) {
if ($_->isa('Slic3r::Polygon')) {
push @{ $self->perimeters }, Slic3r::ExtrusionLoop->new(polygon => $_, role => 'perimeter');
} else {
push @{ $self->perimeters }, Slic3r::ExtrusionPath->new(polyline => $_, role => 'perimeter');
}
}
}
sub prepare_fill_surfaces { sub prepare_fill_surfaces {
my $self = shift; my $self = shift;

View File

@ -1,146 +0,0 @@
package Slic3r::Perimeter;
use Moo;
use Math::Clipper ':all';
use Slic3r::Geometry qw(X Y PI shortest_path scale unscale);
use Slic3r::Geometry::Clipper qw(diff_ex);
sub make_perimeter {
my $self = shift;
my ($layer) = @_;
Slic3r::debugf "Making perimeters for layer %d\n", $layer->id;
# this array will hold one arrayref per original surface (island);
# each item of this arrayref is an arrayref representing a depth (from outer
# perimeters to inner); each item of this arrayref is an ExPolygon:
# @perimeters = (
# [ # first island
# [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 0: outer loop
# [ Slic3r::ExPolygon, Slic3r::ExPolygon... ], #depth 1: inner loop
# ],
# [ # second island
# ...
# ]
# )
my @perimeters = (); # one item per depth; each item
# organize islands using a shortest path search
my @surfaces = @{shortest_path([
map [ $_->contour->[0], $_ ], @{$layer->slices},
])};
# for each island:
foreach my $surface (@surfaces) {
my @last_offsets = ($surface->expolygon);
my $distance = 0;
# experimental hole compensation (see ArcCompensation in the RepRap wiki)
foreach my $hole ($last_offsets[0]->holes) {
my $area = abs($hole->area);last;
next unless $area <= $Slic3r::small_perimeter_area;
my $radius = sqrt($area / PI);
my $new_radius = (scale($Slic3r::flow_width) + sqrt((scale($Slic3r::flow_width)**2) + (4*($radius**2)))) / 2;
@$hole = map Slic3r::Point->new($_), @{ +($hole->offset(+ ($new_radius - $radius)))[0] };
}
# create other offsets
push @perimeters, [];
for (my $loop = 0; $loop < $Slic3r::perimeters; $loop++) {
# offsetting a polygon can result in one or many offset polygons
@last_offsets = map $_->offset_ex(-$distance), @last_offsets if $distance;
last if !@last_offsets;
push @{ $perimeters[-1] }, [@last_offsets];
# offset distance for inner loops
$distance = scale $Slic3r::flow_spacing;
}
# create one more offset to be used as boundary for fill
{
my @fill_boundaries = map $_->offset_ex(-$distance), @last_offsets;
push @{ $layer->fill_boundaries }, @fill_boundaries;
# detect the small gaps that we need to treat like thin polygons,
# thus generating the skeleton and using it to fill them
my $small_gaps = diff_ex(
[ map @$_, map $_->offset_ex(-$distance/2), map @$_, @{$perimeters[-1]} ],
[ map @$_, map $_->offset_ex(+$distance/2), @fill_boundaries ],
);
push @{ $layer->thin_fills },
grep $_,
map $_->medial_axis(scale $Slic3r::flow_width),
@$small_gaps if 0;
}
}
# process one island (original surface) at time
foreach my $island (@perimeters) {
# do holes starting from innermost one
my @holes = ();
my @hole_depths = map [ map $_->holes, @$_ ], @$island;
# organize the outermost hole loops using a shortest path search
@{$hole_depths[0]} = @{shortest_path([
map [ $_->[0], $_ ], @{$hole_depths[0]},
])};
CYCLE: while (map @$_, @hole_depths) {
shift @hole_depths while !@{$hole_depths[0]};
# take first available hole
push @holes, shift @{$hole_depths[0]};
my $current_depth = 0;
while (1) {
$current_depth++;
# look for the hole containing this one if any
next CYCLE if !$hole_depths[$current_depth];
my $parent_hole;
for (@{$hole_depths[$current_depth]}) {
if ($_->encloses_point($holes[-1]->[0])) {
$parent_hole = $_;
last;
}
}
next CYCLE if !$parent_hole;
# look for other holes contained in such parent
for (@{$hole_depths[$current_depth-1]}) {
if ($parent_hole->encloses_point($_->[0])) {
# we have a sibling, so let's move onto next iteration
next CYCLE;
}
}
push @holes, $parent_hole;
@{$hole_depths[$current_depth]} = grep $_ ne $parent_hole, @{$hole_depths[$current_depth]};
}
}
foreach my $hole (@holes) {
push @{ $layer->perimeters }, Slic3r::ExtrusionLoop->new(polygon => $hole, role => 'perimeter');
}
# do contours starting from innermost one
foreach my $contour (map $_->contour, map @$_, reverse @$island) {
push @{ $layer->perimeters }, Slic3r::ExtrusionLoop->new(polygon => $contour, role => 'perimeter');
}
}
# detect small perimeters by checking their area
for (@{ $layer->perimeters }) {
$_->role('small-perimeter') if abs($_->polygon->area) < $Slic3r::small_perimeter_area;
}
# add thin walls as perimeters
for (@{ $layer->thin_walls }) {
if ($_->isa('Slic3r::Polygon')) {
push @{ $layer->perimeters }, Slic3r::ExtrusionLoop->new(polygon => $_, role => 'perimeter');
} else {
push @{ $layer->perimeters }, Slic3r::ExtrusionPath->new(polyline => $_, role => 'perimeter');
}
}
}
1;

View File

@ -1,38 +1,34 @@
package Slic3r::Print; package Slic3r::Print;
use Moo; use Moo;
use Config;
use Math::ConvexHull 1.0.4 qw(convex_hull); use Math::ConvexHull 1.0.4 qw(convex_hull);
use Slic3r::Geometry qw(X Y Z PI MIN MAX scale unscale move_points); use Slic3r::Geometry qw(X Y Z PI scale unscale move_points);
use Slic3r::Geometry::Clipper qw(explode_expolygons safety_offset diff_ex intersection_ex use Slic3r::Geometry::Clipper qw(diff_ex union_ex offset JT_ROUND);
union_ex offset JT_ROUND JT_MITER);
has 'x_length' => (is => 'ro', required => 1); has 'objects' => (is => 'rw', default => sub {[]});
has 'y_length' => (is => 'ro', required => 1); has 'copies' => (is => 'rw', default => sub {[]}); # obj_idx => [copies...]
has 'total_x_length' => (is => 'rw'); # including duplicates has 'total_x_length' => (is => 'rw'); # including duplicates
has 'total_y_length' => (is => 'rw'); # including duplicates has 'total_y_length' => (is => 'rw'); # including duplicates
has 'copies' => (is => 'rw', default => sub {[]}); has 'total_extrusion_length' => (is => 'rw');
has 'layers' => ( # ordered collection of extrusion paths to build skirt loops
traits => ['Array'], has 'skirt' => (
is => 'rw', is => 'rw',
#isa => 'ArrayRef[Slic3r::Layer]', #isa => 'ArrayRef[Slic3r::ExtrusionLoop]',
default => sub { [] }, default => sub { [] },
); );
has 'total_extrusion_length' => (is => 'rw'); sub add_object_from_mesh {
my $self = shift;
sub new_from_mesh {
my $class = shift;
my ($mesh) = @_; my ($mesh) = @_;
$mesh->rotate($Slic3r::rotate); $mesh->rotate($Slic3r::rotate);
$mesh->scale($Slic3r::scale / $Slic3r::scaling_factor); $mesh->scale($Slic3r::scale / $Slic3r::scaling_factor);
$mesh->align_to_origin; $mesh->align_to_origin;
# initialize print job # initialize print object
my @size = $mesh->size; my @size = $mesh->size;
my $print = $class->new( my $object = Slic3r::Print::Object->new(
x_length => $size[X], x_length => $size[X],
y_length => $size[Y], y_length => $size[Y],
); );
@ -42,7 +38,7 @@ sub new_from_mesh {
my $apply_lines = sub { my $apply_lines = sub {
my $lines = shift; my $lines = shift;
foreach my $layer_id (keys %$lines) { foreach my $layer_id (keys %$lines) {
my $layer = $print->layer($layer_id); my $layer = $object->layer($layer_id);
$layer->add_line($_) for @{ $lines->{$layer_id} }; $layer->add_line($_) for @{ $lines->{$layer_id} };
} }
}; };
@ -53,7 +49,7 @@ sub new_from_mesh {
my $q = shift; my $q = shift;
my $result_lines = {}; my $result_lines = {};
while (defined (my $facet_id = $q->dequeue)) { while (defined (my $facet_id = $q->dequeue)) {
my $lines = $mesh->slice_facet($print, $facet_id); my $lines = $mesh->slice_facet($object, $facet_id);
foreach my $layer_id (keys %$lines) { foreach my $layer_id (keys %$lines) {
$result_lines->{$layer_id} ||= []; $result_lines->{$layer_id} ||= [];
push @{ $result_lines->{$layer_id} }, @{ $lines->{$layer_id} }; push @{ $result_lines->{$layer_id} }, @{ $lines->{$layer_id} };
@ -66,19 +62,19 @@ sub new_from_mesh {
}, },
no_threads_cb => sub { no_threads_cb => sub {
for (0..$#{$mesh->facets}) { for (0..$#{$mesh->facets}) {
my $lines = $mesh->slice_facet($print, $_); my $lines = $mesh->slice_facet($object, $_);
$apply_lines->($lines); $apply_lines->($lines);
} }
}, },
); );
} }
die "Invalid input file\n" if !@{$print->layers}; die "Invalid input file\n" if !@{$object->layers};
# remove last layer if empty # remove last layer if empty
# (we might have created it because of the $max_layer = ... + 1 code below) # (we might have created it because of the $max_layer = ... + 1 code below)
pop @{$print->layers} if !@{$print->layers->[-1]->surfaces} && !@{$print->layers->[-1]->lines}; pop @{$object->layers} if !@{$object->layers->[-1]->surfaces} && !@{$object->layers->[-1]->lines};
foreach my $layer (@{ $print->layers }) { foreach my $layer (@{ $object->layers }) {
Slic3r::debugf "Making surfaces for layer %d (slice z = %f):\n", Slic3r::debugf "Making surfaces for layer %d (slice z = %f):\n",
$layer->id, unscale $layer->slice_z if $Slic3r::debug; $layer->id, unscale $layer->slice_z if $Slic3r::debug;
@ -98,8 +94,8 @@ sub new_from_mesh {
# detect slicing errors # detect slicing errors
my $warning_thrown = 0; my $warning_thrown = 0;
for (my $i = 0; $i <= $#{$print->layers}; $i++) { for my $i (0 .. $#{$object->layers}) {
my $layer = $print->layers->[$i]; my $layer = $object->layers->[$i];
next unless $layer->slicing_errors; next unless $layer->slicing_errors;
if (!$warning_thrown) { if (!$warning_thrown) {
warn "The model has overlapping or self-intersecting facets. I tried to repair it, " warn "The model has overlapping or self-intersecting facets. I tried to repair it, "
@ -112,15 +108,15 @@ sub new_from_mesh {
Slic3r::debugf "Attempting to repair layer %d\n", $i; Slic3r::debugf "Attempting to repair layer %d\n", $i;
my (@upper_surfaces, @lower_surfaces); my (@upper_surfaces, @lower_surfaces);
for (my $j = $i+1; $j <= $#{$print->layers}; $j++) { for (my $j = $i+1; $j <= $#{$object->layers}; $j++) {
if (!$print->layers->[$j]->slicing_errors) { if (!$object->layers->[$j]->slicing_errors) {
@upper_surfaces = @{$print->layers->[$j]->slices}; @upper_surfaces = @{$object->layers->[$j]->slices};
last; last;
} }
} }
for (my $j = $i-1; $j >= 0; $j--) { for (my $j = $i-1; $j >= 0; $j--) {
if (!$print->layers->[$j]->slicing_errors) { if (!$object->layers->[$j]->slicing_errors) {
@lower_surfaces = @{$print->layers->[$j]->slices}; @lower_surfaces = @{$object->layers->[$j]->slices};
last; last;
} }
} }
@ -139,46 +135,67 @@ sub new_from_mesh {
} }
# remove empty layers from bottom # remove empty layers from bottom
while (@{$print->layers} && !@{$print->layers->[0]->slices} && !@{$print->layers->[0]->thin_walls}) { while (@{$object->layers} && !@{$object->layers->[0]->slices} && !@{$object->layers->[0]->thin_walls}) {
shift @{$print->layers}; shift @{$object->layers};
for (my $i = 0; $i <= $#{$print->layers}; $i++) { for (my $i = 0; $i <= $#{$object->layers}; $i++) {
$print->layers->[$i]->id($i); $object->layers->[$i]->id($i);
} }
} }
warn "No layers were detected. You might want to repair your STL file and retry.\n" warn "No layers were detected. You might want to repair your STL file and retry.\n"
if !@{$print->layers}; if !@{$object->layers};
return $print; push @{$self->objects}, $object;
return $object;
} }
sub BUILD { sub layer_count {
my $self = shift;
my $count = 0;
foreach my $object (@{$self->objects}) {
$count = @{$object->layers} if @{$object->layers} > $count;
}
return $count;
}
sub arrange_objects {
my $self = shift; my $self = shift;
my $dist = scale $Slic3r::duplicate_distance; my $dist = scale $Slic3r::duplicate_distance;
if ($Slic3r::duplicate_grid->[X] > 1 || $Slic3r::duplicate_grid->[Y] > 1) { if ($Slic3r::duplicate_grid->[X] > 1 || $Slic3r::duplicate_grid->[Y] > 1) {
$self->total_x_length($self->x_length * $Slic3r::duplicate_grid->[X] + $dist * ($Slic3r::duplicate_grid->[X] - 1)); if (@{$self->objects} > 1) {
$self->total_y_length($self->y_length * $Slic3r::duplicate_grid->[Y] + $dist * ($Slic3r::duplicate_grid->[Y] - 1)); die "Grid duplication is not supported with multiple objects\n";
}
my $object = $self->objects->[0];
$self->total_x_length($object->x_length * $Slic3r::duplicate_grid->[X] + $dist * ($Slic3r::duplicate_grid->[X] - 1));
$self->total_y_length($object->y_length * $Slic3r::duplicate_grid->[Y] + $dist * ($Slic3r::duplicate_grid->[Y] - 1));
# generate offsets for copies # generate offsets for copies
push @{$self->copies}, [];
for my $x_copy (1..$Slic3r::duplicate_grid->[X]) { for my $x_copy (1..$Slic3r::duplicate_grid->[X]) {
for my $y_copy (1..$Slic3r::duplicate_grid->[Y]) { for my $y_copy (1..$Slic3r::duplicate_grid->[Y]) {
push @{$self->copies}, [ push @{$self->copies->[0]}, [
($self->x_length + $dist) * ($x_copy-1), ($self->x_length + $dist) * ($x_copy-1),
($self->y_length + $dist) * ($y_copy-1), ($self->y_length + $dist) * ($y_copy-1),
]; ];
} }
} }
} elsif ($Slic3r::duplicate > 1) { } elsif ($Slic3r::duplicate > 1 || @{$self->objects} > 1) {
my $total_parts = @{$self->objects} * $Slic3r::duplicate;
my $linint = sub { my $linint = sub {
my ($value, $oldmin, $oldmax, $newmin, $newmax) = @_; my ($value, $oldmin, $oldmax, $newmin, $newmax) = @_;
return ($value - $oldmin) * ($newmax - $newmin) / ($oldmax - $oldmin) + $newmin; return ($value - $oldmin) * ($newmax - $newmin) / ($oldmax - $oldmin) + $newmin;
}; };
# use actual part size plus separation distance (half on each side) in spacing algorithm # use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm
my $partx = unscale($self->x_length) + $Slic3r::duplicate_distance; my $partx = my $party = 0;
my $party = unscale($self->y_length) + $Slic3r::duplicate_distance; foreach my $object (@{$self->objects}) {
$partx = $object->x_length if $object->x_length > $partx;
$party = $object->y_length if $object->y_length > $party;
}
$partx = unscale($partx) + $Slic3r::duplicate_distance;
$party = unscale($party) + $Slic3r::duplicate_distance;
# margin needed for the skirt # margin needed for the skirt
my $skirt_margin; my $skirt_margin;
@ -192,7 +209,7 @@ sub BUILD {
my $cellw = int(($Slic3r::bed_size->[X] - $skirt_margin + $Slic3r::duplicate_distance) / $partx); my $cellw = int(($Slic3r::bed_size->[X] - $skirt_margin + $Slic3r::duplicate_distance) / $partx);
my $cellh = int(($Slic3r::bed_size->[Y] - $skirt_margin + $Slic3r::duplicate_distance) / $party); my $cellh = int(($Slic3r::bed_size->[Y] - $skirt_margin + $Slic3r::duplicate_distance) / $party);
die "$Slic3r::duplicate parts won't fit in your print area!\n" if $Slic3r::duplicate > ($cellw * $cellh); die "$total_parts parts won't fit in your print area!\n" if $total_parts > ($cellw * $cellh);
# width and height of space used by cells # width and height of space used by cells
my $w = $cellw * $partx; my $w = $cellw * $partx;
@ -250,7 +267,7 @@ sub BUILD {
my ($lx, $ty, $rx, $by) = (0, 0, 0, 0); my ($lx, $ty, $rx, $by) = (0, 0, 0, 0);
# now find cells actually used by objects, map out the extents so we can position correctly # now find cells actually used by objects, map out the extents so we can position correctly
for my $i (1..$Slic3r::duplicate) { for my $i (1..$total_parts) {
my $c = $cellsorder[$i - 1]; my $c = $cellsorder[$i - 1];
my $cx = $c->[1]->{index}->[0]; my $cx = $c->[1]->{index}->[0];
my $cy = $c->[1]->{index}->[1]; my $cy = $c->[1]->{index}->[1];
@ -265,446 +282,66 @@ sub BUILD {
} }
} }
# now we actually place objects into cells, positioned such that the left and bottom borders are at 0 # now we actually place objects into cells, positioned such that the left and bottom borders are at 0
for my $i (1..$Slic3r::duplicate) { for (0..$#{$self->objects}) {
my @copies = ();
for (1..$Slic3r::duplicate) {
my $c = shift @cellsorder; my $c = shift @cellsorder;
my $cx = $c->[1]->{index}->[0] - $lx; my $cx = $c->[1]->{index}->[0] - $lx;
my $cy = $c->[1]->{index}->[1] - $ty; my $cy = $c->[1]->{index}->[1] - $ty;
push @{$self->copies}, [scale($cx * $partx), scale($cy * $party)]; push @copies, [scale($cx * $partx), scale($cy * $party)];
}
push @{$self->copies}, [@copies];
} }
# save size of area used # save size of area used
$self->total_x_length(scale(($rx - $lx + 1) * $partx - $Slic3r::duplicate_distance)); $self->total_x_length(scale(($rx - $lx + 1) * $partx - $Slic3r::duplicate_distance));
$self->total_y_length(scale(($by - $ty + 1) * $party - $Slic3r::duplicate_distance)); $self->total_y_length(scale(($by - $ty + 1) * $party - $Slic3r::duplicate_distance));
} else { } else {
$self->total_x_length($self->x_length); $self->total_x_length($self->objects->[0]->x_length);
$self->total_y_length($self->y_length); $self->total_y_length($self->objects->[0]->y_length);
push @{$self->copies}, [0, 0]; push @{$self->copies}, [[0, 0]];
} }
} }
sub layer_count {
my $self = shift;
return scalar @{ $self->layers };
}
sub max_length { sub max_length {
my $self = shift; my $self = shift;
return ($self->x_length > $self->y_length) ? $self->x_length : $self->y_length; return ($self->total_x_length > $self->total_y_length) ? $self->total_x_length : $self->total_y_length;
} }
sub layer { sub make_skirt {
my $self = shift;
my ($layer_id) = @_;
# extend our print by creating all necessary layers
if ($self->layer_count < $layer_id + 1) {
for (my $i = $self->layer_count; $i <= $layer_id; $i++) {
push @{ $self->layers }, Slic3r::Layer->new(id => $i);
}
}
return $self->layers->[$layer_id];
}
sub detect_surfaces_type {
my $self = shift;
Slic3r::debugf "Detecting solid surfaces...\n";
# prepare a reusable subroutine to make surface differences
my $surface_difference = sub {
my ($subject_surfaces, $clip_surfaces, $result_type) = @_;
my $expolygons = diff_ex(
[ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$subject_surfaces ],
[ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$clip_surfaces ],
1,
);
return grep $_->contour->is_printable,
map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type),
@$expolygons;
};
for (my $i = 0; $i < $self->layer_count; $i++) {
my $layer = $self->layers->[$i];
my $upper_layer = $self->layers->[$i+1];
my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef;
my (@bottom, @top, @internal) = ();
# find top surfaces (difference between current surfaces
# of current layer and upper one)
if ($upper_layer) {
@top = $surface_difference->($layer->slices, $upper_layer->slices, 'top');
} else {
# if no upper layer, all surfaces of this one are solid
@top = @{$layer->slices};
$_->surface_type('top') for @top;
}
# find bottom surfaces (difference between current surfaces
# of current layer and lower one)
if ($lower_layer) {
@bottom = $surface_difference->($layer->slices, $lower_layer->slices, 'bottom');
} else {
# if no lower layer, all surfaces of this one are solid
@bottom = @{$layer->slices};
$_->surface_type('bottom') for @bottom;
}
# now, if the object contained a thin membrane, we could have overlapping bottom
# and top surfaces; let's do an intersection to discover them and consider them
# as bottom surfaces (to allow for bridge detection)
if (@top && @bottom) {
my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]);
Slic3r::debugf " layer %d contains %d membrane(s)\n", $layer->id, scalar(@$overlapping);
@top = $surface_difference->([@top], $overlapping, 'top');
}
# find internal surfaces (difference between top/bottom surfaces and others)
@internal = $surface_difference->($layer->slices, [@top, @bottom], 'internal');
# save surfaces to layer
@{$layer->slices} = (@bottom, @top, @internal);
Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n",
$layer->id, scalar(@bottom), scalar(@top), scalar(@internal);
}
# clip surfaces to the fill boundaries
foreach my $layer (@{$self->layers}) {
@{$layer->surfaces} = ();
foreach my $surface (@{$layer->slices}) {
my $intersection = intersection_ex(
[ $surface->p ],
[ map @$_, @{$layer->fill_boundaries} ],
);
push @{$layer->surfaces}, map Slic3r::Surface->new
(expolygon => $_, surface_type => $surface->surface_type),
@$intersection;
}
# free memory
@{$layer->fill_boundaries} = ();
}
}
sub discover_horizontal_shells {
my $self = shift;
Slic3r::debugf "==> DISCOVERING HORIZONTAL SHELLS\n";
for (my $i = 0; $i < $self->layer_count; $i++) {
my $layer = $self->layers->[$i];
foreach my $type (qw(top bottom)) {
# find surfaces of current type for current layer
# and offset them to take perimeters into account
my @surfaces = map $_->offset($Slic3r::perimeters * scale $Slic3r::flow_width),
grep $_->surface_type eq $type, @{$layer->fill_surfaces} or next;
my $surfaces_p = [ map $_->p, @surfaces ];
Slic3r::debugf "Layer %d has %d surfaces of type '%s'\n",
$i, scalar(@surfaces), $type;
for (my $n = $type eq 'top' ? $i-1 : $i+1;
abs($n - $i) <= $Slic3r::solid_layers-1;
$type eq 'top' ? $n-- : $n++) {
next if $n < 0 || $n >= $self->layer_count;
Slic3r::debugf " looking for neighbors on layer %d...\n", $n;
my @neighbor_surfaces = @{$self->layers->[$n]->surfaces};
my @neighbor_fill_surfaces = @{$self->layers->[$n]->fill_surfaces};
# find intersection between neighbor and current layer's surfaces
# intersections have contours and holes
my $new_internal_solid = intersection_ex(
$surfaces_p,
[ map $_->p, grep $_->surface_type =~ /internal/, @neighbor_surfaces ],
undef, 1,
);
next if !@$new_internal_solid;
# internal-solid are the union of the existing internal-solid surfaces
# and new ones
my $internal_solid = union_ex([
( map $_->p, grep $_->surface_type eq 'internal-solid', @neighbor_fill_surfaces ),
( map @$_, @$new_internal_solid ),
]);
# subtract intersections from layer surfaces to get resulting inner surfaces
my $internal = diff_ex(
[ map $_->p, grep $_->surface_type eq 'internal', @neighbor_fill_surfaces ],
[ map @$_, @$internal_solid ],
);
Slic3r::debugf " %d internal-solid and %d internal surfaces found\n",
scalar(@$internal_solid), scalar(@$internal);
# Note: due to floating point math we're going to get some very small
# polygons as $internal; they will be removed by removed_small_features()
# assign resulting inner surfaces to layer
my $neighbor_fill_surfaces = $self->layers->[$n]->fill_surfaces;
@$neighbor_fill_surfaces = ();
push @$neighbor_fill_surfaces, Slic3r::Surface->new
(expolygon => $_, surface_type => 'internal')
for @$internal;
# assign new internal-solid surfaces to layer
push @$neighbor_fill_surfaces, Slic3r::Surface->new
(expolygon => $_, surface_type => 'internal-solid')
for @$internal_solid;
# assign top and bottom surfaces to layer
foreach my $s (Slic3r::Surface->group(grep $_->surface_type =~ /top|bottom/, @neighbor_fill_surfaces)) {
my $solid_surfaces = diff_ex(
[ map $_->p, @$s ],
[ map @$_, @$internal_solid, @$internal ],
);
push @$neighbor_fill_surfaces, Slic3r::Surface->new
(expolygon => $_, surface_type => $s->[0]->surface_type, bridge_angle => $s->[0]->bridge_angle)
for @$solid_surfaces;
}
}
}
}
}
sub extrude_skirt {
my $self = shift; my $self = shift;
return unless $Slic3r::skirts > 0; return unless $Slic3r::skirts > 0;
# collect points from all layers contained in skirt height # collect points from all layers contained in skirt height
my $skirt_height = $Slic3r::skirt_height; my $skirt_height = $Slic3r::skirt_height;
$skirt_height = $self->layer_count if $skirt_height > $self->layer_count; $skirt_height = $self->layer_count if $skirt_height > $self->layer_count;
my @layers = map $self->layer($_), 0..($skirt_height-1); my @points = ();
my @points = ( foreach my $obj_idx (0 .. $#{$self->objects}) {
my @layers = map $self->objects->[$obj_idx]->layer($_), 0..($skirt_height-1);
my @layer_points = (
(map @$_, map @{$_->expolygon}, map @{$_->slices}, @layers), (map @$_, map @{$_->expolygon}, map @{$_->slices}, @layers),
(map @$_, map @{$_->thin_walls}, @layers), (map @$_, map @{$_->thin_walls}, @layers),
(map @{$_->polyline}, map @{$_->support_fills->paths}, grep $_->support_fills, @layers), (map @{$_->polyline}, map @{$_->support_fills->paths}, grep $_->support_fills, @layers),
); );
push @points, map move_points($_, @layer_points), @{$self->copies->[$obj_idx]};
}
return if @points < 3; # at least three points required for a convex hull return if @points < 3; # at least three points required for a convex hull
# duplicate points to take copies into account
my @all_points = map move_points($_, @points), @{$self->copies};
# find out convex hull # find out convex hull
my $convex_hull = convex_hull(\@all_points); my $convex_hull = convex_hull(\@points);
# draw outlines from outside to inside # draw outlines from outside to inside
my @skirts = (); my @skirt = ();
for (my $i = $Slic3r::skirts - 1; $i >= 0; $i--) { for (my $i = $Slic3r::skirts - 1; $i >= 0; $i--) {
my $distance = scale ($Slic3r::skirt_distance + ($Slic3r::flow_spacing * $i)); my $distance = scale ($Slic3r::skirt_distance + ($Slic3r::flow_spacing * $i));
my $outline = offset([$convex_hull], $distance, $Slic3r::scaling_factor * 100, JT_ROUND); my $outline = offset([$convex_hull], $distance, $Slic3r::scaling_factor * 100, JT_ROUND);
push @skirts, Slic3r::ExtrusionLoop->new( push @skirt, Slic3r::ExtrusionLoop->new(
polygon => Slic3r::Polygon->new(@{$outline->[0]}), polygon => Slic3r::Polygon->new(@{$outline->[0]}),
role => 'skirt', role => 'skirt',
); );
} }
push @{$self->skirt}, @skirt;
# apply skirts to all layers
push @{$_->skirts}, @skirts for @layers;
}
# combine fill surfaces across layers
sub infill_every_layers {
my $self = shift;
return unless $Slic3r::infill_every_layers > 1 && $Slic3r::fill_density > 0;
# start from bottom, skip first layer
for (my $i = 1; $i < $self->layer_count; $i++) {
my $layer = $self->layer($i);
# skip layer if no internal fill surfaces
next if !grep $_->surface_type eq 'internal', @{$layer->fill_surfaces};
# for each possible depth, look for intersections with the lower layer
# we do this from the greater depth to the smaller
for (my $d = $Slic3r::infill_every_layers - 1; $d >= 1; $d--) {
next if ($i - $d) < 0;
my $lower_layer = $self->layer($i - 1);
# select surfaces of the lower layer having the depth we're looking for
my @lower_surfaces = grep $_->depth_layers == $d && $_->surface_type eq 'internal',
@{$lower_layer->fill_surfaces};
next if !@lower_surfaces;
# calculate intersection between our surfaces and theirs
my $intersection = intersection_ex(
[ map $_->p, grep $_->depth_layers <= $d, @lower_surfaces ],
[ map $_->p, grep $_->surface_type eq 'internal', @{$layer->fill_surfaces} ],
);
next if !@$intersection;
# new fill surfaces of the current layer are:
# - any non-internal surface
# - intersections found (with a $d + 1 depth)
# - any internal surface not belonging to the intersection (with its original depth)
{
my @new_surfaces = ();
push @new_surfaces, grep $_->surface_type ne 'internal', @{$layer->fill_surfaces};
push @new_surfaces, map Slic3r::Surface->new
(expolygon => $_, surface_type => 'internal', depth_layers => $d + 1), @$intersection;
foreach my $depth (reverse $d..$Slic3r::infill_every_layers) {
push @new_surfaces, map Slic3r::Surface->new
(expolygon => $_, surface_type => 'internal', depth_layers => $depth),
# difference between our internal layers with depth == $depth
# and the intersection found
@{diff_ex(
[
map $_->p, grep $_->surface_type eq 'internal' && $_->depth_layers == $depth,
@{$layer->fill_surfaces},
],
[ map @$_, @$intersection ],
1,
)};
}
@{$layer->fill_surfaces} = @new_surfaces;
}
# now we remove the intersections from lower layer
{
my @new_surfaces = ();
push @new_surfaces, grep $_->surface_type ne 'internal', @{$lower_layer->fill_surfaces};
foreach my $depth (1..$Slic3r::infill_every_layers) {
push @new_surfaces, map Slic3r::Surface->new
(expolygon => $_, surface_type => 'internal', depth_layers => $depth),
# difference between internal layers with depth == $depth
# and the intersection found
@{diff_ex(
[
map $_->p, grep $_->surface_type eq 'internal' && $_->depth_layers == $depth,
@{$lower_layer->fill_surfaces},
],
[ map @$_, @$intersection ],
1,
)};
}
@{$lower_layer->fill_surfaces} = @new_surfaces;
}
}
}
}
sub generate_support_material {
my $self = shift;
# determine unsupported surfaces
my %layers = ();
my @unsupported_expolygons = ();
{
my (@a, @b) = ();
for my $i (reverse 0 .. $#{$self->layers}) {
my $layer = $self->layers->[$i];
my @c = ();
if (@b) {
@c = @{diff_ex(
[ map @$_, @b ],
[ map @$_, map $_->expolygon->offset_ex(scale $Slic3r::flow_width), @{$layer->slices} ],
)};
$layers{$i} = [@c];
}
@b = @{union_ex([ map @$_, @c, @a ])};
# get unsupported surfaces for current layer as all bottom slices
# minus the bridges offsetted to cover their perimeters.
# actually, we are marking as bridges more than we should be, so
# better build support material for bridges too rather than ignoring
# those parts. a visibility check algorithm is needed.
# @a = @{diff_ex(
# [ map $_->p, grep $_->surface_type eq 'bottom', @{$layer->slices} ],
# [ map @$_, map $_->expolygon->offset_ex(scale $Slic3r::flow_spacing * $Slic3r::perimeters),
# grep $_->surface_type eq 'bottom' && defined $_->bridge_angle,
# @{$layer->fill_surfaces} ],
# )};
@a = map $_->expolygon->clone, grep $_->surface_type eq 'bottom', @{$layer->slices};
$_->simplify(scale $Slic3r::flow_spacing * 3) for @a;
push @unsupported_expolygons, @a;
}
}
return if !@unsupported_expolygons;
# generate paths for the pattern that we're going to use
my $support_patterns = [];
{
my @support_material_areas = map $_->offset_ex(scale 5),
@{union_ex([ map @$_, @unsupported_expolygons ])};
my $fill = Slic3r::Fill->new(print => $self);
foreach my $angle (0, 90) {
my @patterns = ();
foreach my $expolygon (@support_material_areas) {
my @paths = $fill->fillers->{rectilinear}->fill_surface(
Slic3r::Surface->new(
expolygon => $expolygon,
bridge_angle => $Slic3r::fill_angle + 45 + $angle,
),
density => 0.20,
flow_spacing => $Slic3r::flow_spacing,
);
my $params = shift @paths;
push @patterns,
map Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(@$_),
role => 'support-material',
depth_layers => 1,
flow_spacing => $params->{flow_spacing},
), @paths;
}
push @$support_patterns, [@patterns];
}
}
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "support.svg",
polylines => [ map $_->polyline, map @$_, @$support_patterns ],
);
}
# apply the pattern to layers
{
my $clip_pattern = sub {
my ($layer_id, $expolygons) = @_;
my @paths = ();
foreach my $expolygon (@$expolygons) {
push @paths, map $_->clip_with_expolygon($expolygon),
map $_->clip_with_polygon($expolygon->bounding_box_polygon),
@{$support_patterns->[ $layer_id % 2 ]};
};
return @paths;
};
my %layer_paths = ();
Slic3r::parallelize(
items => [ keys %layers ],
thread_cb => sub {
my $q = shift;
my $paths = {};
while (defined (my $layer_id = $q->dequeue)) {
$paths->{$layer_id} = [ $clip_pattern->($layer_id, $layers{$layer_id}) ];
}
return $paths;
},
collect_cb => sub {
my $paths = shift;
$layer_paths{$_} = $paths->{$_} for keys %$paths;
},
no_threads_cb => sub {
$layer_paths{$_} = [ $clip_pattern->($_, $layers{$_}) ] for keys %layers;
},
);
foreach my $layer_id (keys %layer_paths) {
my $layer = $self->layers->[$layer_id];
$layer->support_fills(Slic3r::ExtrusionPath::Collection->new);
push @{$layer->support_fills->paths}, @{$layer_paths{$layer_id}};
}
}
} }
sub export_gcode { sub export_gcode {
@ -772,7 +409,12 @@ sub export_gcode {
print $fh $extruder->set_fan(0, 1) if $Slic3r::cooling && $Slic3r::disable_fan_first_layers; print $fh $extruder->set_fan(0, 1) if $Slic3r::cooling && $Slic3r::disable_fan_first_layers;
# write gcode commands layer by layer # write gcode commands layer by layer
foreach my $layer (@{ $self->layers }) { my @layers = (); # [ $obj_idx, $layer ]
for my $layer_id (0..$self->layer_count) {
push @layers, map [ $_, $self->objects->[$_]->layers->[$layer_id] ], 0..$#{$self->objects};
}
foreach my $obj_layer (grep $_->[1], @layers) {
my ($obj_idx, $layer) = @$obj_layer;
if ($layer->id == 1) { if ($layer->id == 1) {
printf $fh "M104 %s%d ; set temperature\n", printf $fh "M104 %s%d ; set temperature\n",
($Slic3r::gcode_flavor eq 'mach3' ? 'P' : 'S'), $Slic3r::temperature ($Slic3r::gcode_flavor eq 'mach3' ? 'P' : 'S'), $Slic3r::temperature
@ -786,15 +428,15 @@ sub export_gcode {
my $layer_gcode = $extruder->change_layer($layer); my $layer_gcode = $extruder->change_layer($layer);
$extruder->elapsed_time(0); $extruder->elapsed_time(0);
# extrude skirts # extrude skirt
$extruder->shift_x($shift[X]); $extruder->shift_x($shift[X]);
$extruder->shift_y($shift[Y]); $extruder->shift_y($shift[Y]);
$layer_gcode .= $extruder->set_acceleration($Slic3r::perimeter_acceleration); $layer_gcode .= $extruder->set_acceleration($Slic3r::perimeter_acceleration);
$layer_gcode .= $extruder->extrude_loop($_, 'skirt') for @{ $layer->skirts }; if ($layer->id < $Slic3r::skirt_height) {
$layer_gcode .= $extruder->extrude_loop($_, 'skirt') for @{$self->skirt};
for (my $i = 0; $i <= $#{$self->copies}; $i++) { }
my $copy = $self->copies->[$i];
for my $copy (@{ $self->copies->[$obj_idx] }) {
# retract explicitely because changing the shift_[xy] properties below # retract explicitely because changing the shift_[xy] properties below
# won't always trigger the automatic retraction # won't always trigger the automatic retraction
$layer_gcode .= $extruder->retract; $layer_gcode .= $extruder->retract;

399
lib/Slic3r/Print/Object.pm Normal file
View File

@ -0,0 +1,399 @@
package Slic3r::Print::Object;
use Moo;
use Slic3r::Geometry qw(scale);
use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex);
has 'x_length' => (is => 'ro', required => 1);
has 'y_length' => (is => 'ro', required => 1);
has 'layers' => (
traits => ['Array'],
is => 'rw',
#isa => 'ArrayRef[Slic3r::Layer]',
default => sub { [] },
);
sub layer_count {
my $self = shift;
return scalar @{ $self->layers };
}
sub layer {
my $self = shift;
my ($layer_id) = @_;
# extend our print by creating all necessary layers
if ($self->layer_count < $layer_id + 1) {
for (my $i = $self->layer_count; $i <= $layer_id; $i++) {
push @{ $self->layers }, Slic3r::Layer->new(id => $i);
}
}
return $self->layers->[$layer_id];
}
sub detect_surfaces_type {
my $self = shift;
Slic3r::debugf "Detecting solid surfaces...\n";
# prepare a reusable subroutine to make surface differences
my $surface_difference = sub {
my ($subject_surfaces, $clip_surfaces, $result_type) = @_;
my $expolygons = diff_ex(
[ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$subject_surfaces ],
[ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$clip_surfaces ],
1,
);
return grep $_->contour->is_printable,
map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type),
@$expolygons;
};
for (my $i = 0; $i < $self->layer_count; $i++) {
my $layer = $self->layers->[$i];
my $upper_layer = $self->layers->[$i+1];
my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef;
my (@bottom, @top, @internal) = ();
# find top surfaces (difference between current surfaces
# of current layer and upper one)
if ($upper_layer) {
@top = $surface_difference->($layer->slices, $upper_layer->slices, 'top');
} else {
# if no upper layer, all surfaces of this one are solid
@top = @{$layer->slices};
$_->surface_type('top') for @top;
}
# find bottom surfaces (difference between current surfaces
# of current layer and lower one)
if ($lower_layer) {
@bottom = $surface_difference->($layer->slices, $lower_layer->slices, 'bottom');
} else {
# if no lower layer, all surfaces of this one are solid
@bottom = @{$layer->slices};
$_->surface_type('bottom') for @bottom;
}
# now, if the object contained a thin membrane, we could have overlapping bottom
# and top surfaces; let's do an intersection to discover them and consider them
# as bottom surfaces (to allow for bridge detection)
if (@top && @bottom) {
my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]);
Slic3r::debugf " layer %d contains %d membrane(s)\n", $layer->id, scalar(@$overlapping);
@top = $surface_difference->([@top], $overlapping, 'top');
}
# find internal surfaces (difference between top/bottom surfaces and others)
@internal = $surface_difference->($layer->slices, [@top, @bottom], 'internal');
# save surfaces to layer
@{$layer->slices} = (@bottom, @top, @internal);
Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n",
$layer->id, scalar(@bottom), scalar(@top), scalar(@internal);
}
# clip surfaces to the fill boundaries
foreach my $layer (@{$self->layers}) {
@{$layer->surfaces} = ();
foreach my $surface (@{$layer->slices}) {
my $intersection = intersection_ex(
[ $surface->p ],
[ map @$_, @{$layer->fill_boundaries} ],
);
push @{$layer->surfaces}, map Slic3r::Surface->new
(expolygon => $_, surface_type => $surface->surface_type),
@$intersection;
}
# free memory
@{$layer->fill_boundaries} = ();
}
}
sub discover_horizontal_shells {
my $self = shift;
Slic3r::debugf "==> DISCOVERING HORIZONTAL SHELLS\n";
for (my $i = 0; $i < $self->layer_count; $i++) {
my $layer = $self->layers->[$i];
foreach my $type (qw(top bottom)) {
# find surfaces of current type for current layer
# and offset them to take perimeters into account
my @surfaces = map $_->offset($Slic3r::perimeters * scale $Slic3r::flow_width),
grep $_->surface_type eq $type, @{$layer->fill_surfaces} or next;
my $surfaces_p = [ map $_->p, @surfaces ];
Slic3r::debugf "Layer %d has %d surfaces of type '%s'\n",
$i, scalar(@surfaces), $type;
for (my $n = $type eq 'top' ? $i-1 : $i+1;
abs($n - $i) <= $Slic3r::solid_layers-1;
$type eq 'top' ? $n-- : $n++) {
next if $n < 0 || $n >= $self->layer_count;
Slic3r::debugf " looking for neighbors on layer %d...\n", $n;
my @neighbor_surfaces = @{$self->layers->[$n]->surfaces};
my @neighbor_fill_surfaces = @{$self->layers->[$n]->fill_surfaces};
# find intersection between neighbor and current layer's surfaces
# intersections have contours and holes
my $new_internal_solid = intersection_ex(
$surfaces_p,
[ map $_->p, grep $_->surface_type =~ /internal/, @neighbor_surfaces ],
undef, 1,
);
next if !@$new_internal_solid;
# internal-solid are the union of the existing internal-solid surfaces
# and new ones
my $internal_solid = union_ex([
( map $_->p, grep $_->surface_type eq 'internal-solid', @neighbor_fill_surfaces ),
( map @$_, @$new_internal_solid ),
]);
# subtract intersections from layer surfaces to get resulting inner surfaces
my $internal = diff_ex(
[ map $_->p, grep $_->surface_type eq 'internal', @neighbor_fill_surfaces ],
[ map @$_, @$internal_solid ],
);
Slic3r::debugf " %d internal-solid and %d internal surfaces found\n",
scalar(@$internal_solid), scalar(@$internal);
# Note: due to floating point math we're going to get some very small
# polygons as $internal; they will be removed by removed_small_features()
# assign resulting inner surfaces to layer
my $neighbor_fill_surfaces = $self->layers->[$n]->fill_surfaces;
@$neighbor_fill_surfaces = ();
push @$neighbor_fill_surfaces, Slic3r::Surface->new
(expolygon => $_, surface_type => 'internal')
for @$internal;
# assign new internal-solid surfaces to layer
push @$neighbor_fill_surfaces, Slic3r::Surface->new
(expolygon => $_, surface_type => 'internal-solid')
for @$internal_solid;
# assign top and bottom surfaces to layer
foreach my $s (Slic3r::Surface->group(grep $_->surface_type =~ /top|bottom/, @neighbor_fill_surfaces)) {
my $solid_surfaces = diff_ex(
[ map $_->p, @$s ],
[ map @$_, @$internal_solid, @$internal ],
);
push @$neighbor_fill_surfaces, Slic3r::Surface->new
(expolygon => $_, surface_type => $s->[0]->surface_type, bridge_angle => $s->[0]->bridge_angle)
for @$solid_surfaces;
}
}
}
}
}
# combine fill surfaces across layers
sub infill_every_layers {
my $self = shift;
return unless $Slic3r::infill_every_layers > 1 && $Slic3r::fill_density > 0;
# start from bottom, skip first layer
for (my $i = 1; $i < $self->layer_count; $i++) {
my $layer = $self->layer($i);
# skip layer if no internal fill surfaces
next if !grep $_->surface_type eq 'internal', @{$layer->fill_surfaces};
# for each possible depth, look for intersections with the lower layer
# we do this from the greater depth to the smaller
for (my $d = $Slic3r::infill_every_layers - 1; $d >= 1; $d--) {
next if ($i - $d) < 0;
my $lower_layer = $self->layer($i - 1);
# select surfaces of the lower layer having the depth we're looking for
my @lower_surfaces = grep $_->depth_layers == $d && $_->surface_type eq 'internal',
@{$lower_layer->fill_surfaces};
next if !@lower_surfaces;
# calculate intersection between our surfaces and theirs
my $intersection = intersection_ex(
[ map $_->p, grep $_->depth_layers <= $d, @lower_surfaces ],
[ map $_->p, grep $_->surface_type eq 'internal', @{$layer->fill_surfaces} ],
);
next if !@$intersection;
# new fill surfaces of the current layer are:
# - any non-internal surface
# - intersections found (with a $d + 1 depth)
# - any internal surface not belonging to the intersection (with its original depth)
{
my @new_surfaces = ();
push @new_surfaces, grep $_->surface_type ne 'internal', @{$layer->fill_surfaces};
push @new_surfaces, map Slic3r::Surface->new
(expolygon => $_, surface_type => 'internal', depth_layers => $d + 1), @$intersection;
foreach my $depth (reverse $d..$Slic3r::infill_every_layers) {
push @new_surfaces, map Slic3r::Surface->new
(expolygon => $_, surface_type => 'internal', depth_layers => $depth),
# difference between our internal layers with depth == $depth
# and the intersection found
@{diff_ex(
[
map $_->p, grep $_->surface_type eq 'internal' && $_->depth_layers == $depth,
@{$layer->fill_surfaces},
],
[ map @$_, @$intersection ],
1,
)};
}
@{$layer->fill_surfaces} = @new_surfaces;
}
# now we remove the intersections from lower layer
{
my @new_surfaces = ();
push @new_surfaces, grep $_->surface_type ne 'internal', @{$lower_layer->fill_surfaces};
foreach my $depth (1..$Slic3r::infill_every_layers) {
push @new_surfaces, map Slic3r::Surface->new
(expolygon => $_, surface_type => 'internal', depth_layers => $depth),
# difference between internal layers with depth == $depth
# and the intersection found
@{diff_ex(
[
map $_->p, grep $_->surface_type eq 'internal' && $_->depth_layers == $depth,
@{$lower_layer->fill_surfaces},
],
[ map @$_, @$intersection ],
1,
)};
}
@{$lower_layer->fill_surfaces} = @new_surfaces;
}
}
}
}
sub generate_support_material {
my $self = shift;
# determine unsupported surfaces
my %layers = ();
my @unsupported_expolygons = ();
{
my (@a, @b) = ();
for my $i (reverse 0 .. $#{$self->layers}) {
my $layer = $self->layers->[$i];
my @c = ();
if (@b) {
@c = @{diff_ex(
[ map @$_, @b ],
[ map @$_, map $_->expolygon->offset_ex(scale $Slic3r::flow_width), @{$layer->slices} ],
)};
$layers{$i} = [@c];
}
@b = @{union_ex([ map @$_, @c, @a ])};
# get unsupported surfaces for current layer as all bottom slices
# minus the bridges offsetted to cover their perimeters.
# actually, we are marking as bridges more than we should be, so
# better build support material for bridges too rather than ignoring
# those parts. a visibility check algorithm is needed.
# @a = @{diff_ex(
# [ map $_->p, grep $_->surface_type eq 'bottom', @{$layer->slices} ],
# [ map @$_, map $_->expolygon->offset_ex(scale $Slic3r::flow_spacing * $Slic3r::perimeters),
# grep $_->surface_type eq 'bottom' && defined $_->bridge_angle,
# @{$layer->fill_surfaces} ],
# )};
@a = map $_->expolygon->clone, grep $_->surface_type eq 'bottom', @{$layer->slices};
$_->simplify(scale $Slic3r::flow_spacing * 3) for @a;
push @unsupported_expolygons, @a;
}
}
return if !@unsupported_expolygons;
# generate paths for the pattern that we're going to use
my $support_patterns = [];
{
my @support_material_areas = map $_->offset_ex(scale 5),
@{union_ex([ map @$_, @unsupported_expolygons ])};
my $fill = Slic3r::Fill->new(print => $self);
foreach my $angle (0, 90) {
my @patterns = ();
foreach my $expolygon (@support_material_areas) {
my @paths = $fill->fillers->{rectilinear}->fill_surface(
Slic3r::Surface->new(
expolygon => $expolygon,
bridge_angle => $Slic3r::fill_angle + 45 + $angle,
),
density => 0.20,
flow_spacing => $Slic3r::flow_spacing,
);
my $params = shift @paths;
push @patterns,
map Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(@$_),
role => 'support-material',
depth_layers => 1,
flow_spacing => $params->{flow_spacing},
), @paths;
}
push @$support_patterns, [@patterns];
}
}
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(undef, "support.svg",
polylines => [ map $_->polyline, map @$_, @$support_patterns ],
);
}
# apply the pattern to layers
{
my $clip_pattern = sub {
my ($layer_id, $expolygons) = @_;
my @paths = ();
foreach my $expolygon (@$expolygons) {
push @paths, map $_->clip_with_expolygon($expolygon),
map $_->clip_with_polygon($expolygon->bounding_box_polygon),
@{$support_patterns->[ $layer_id % 2 ]};
};
return @paths;
};
my %layer_paths = ();
Slic3r::parallelize(
items => [ keys %layers ],
thread_cb => sub {
my $q = shift;
my $paths = {};
while (defined (my $layer_id = $q->dequeue)) {
$paths->{$layer_id} = [ $clip_pattern->($layer_id, $layers{$layer_id}) ];
}
return $paths;
},
collect_cb => sub {
my $paths = shift;
$layer_paths{$_} = $paths->{$_} for keys %$paths;
},
no_threads_cb => sub {
$layer_paths{$_} = [ $clip_pattern->($_, $layers{$_}) ] for keys %layers;
},
);
foreach my $layer_id (keys %layer_paths) {
my $layer = $self->layers->[$layer_id];
$layer->support_fills(Slic3r::ExtrusionPath::Collection->new);
push @{$layer->support_fills->paths}, @{$layer_paths{$layer_id}};
}
}
}
1;

View File

@ -8,6 +8,7 @@ use Time::HiRes qw(gettimeofday tv_interval);
# full path (relative or absolute) to the input file # full path (relative or absolute) to the input file
has 'input_file' => (is => 'ro', required => 1); has 'input_file' => (is => 'ro', required => 1);
has 'additional_input_files' => (is => 'ro', required => 0, default => sub {[]});
# full path (relative or absolute) to the output file; it may contain # full path (relative or absolute) to the output file; it may contain
# formatting variables like [layer_height] etc. # formatting variables like [layer_height] etc.
@ -19,18 +20,21 @@ has 'processing_time' => (is => 'rw', required => 0);
sub slice_input { sub slice_input {
my $self = shift; my $self = shift;
my $print; my $print = Slic3r::Print->new;
if ($self->input_file =~ /\.stl$/i) { foreach my $input_file ($self->input_file, @{$self->additional_input_files}) {
my $mesh = Slic3r::Format::STL->read_file($self->input_file); if ($input_file =~ /\.stl$/i) {
my $mesh = Slic3r::Format::STL->read_file($input_file);
$mesh->check_manifoldness; $mesh->check_manifoldness;
$print = Slic3r::Print->new_from_mesh($mesh); $print->add_object_from_mesh($mesh);
} elsif ( $self->input_file =~ /\.amf(\.xml)?$/i) { } elsif ( $input_file =~ /\.amf(\.xml)?$/i) {
my ($materials, $meshes_by_material) = Slic3r::Format::AMF->read_file($self->input_file); my ($materials, $meshes_by_material) = Slic3r::Format::AMF->read_file($input_file);
$_->check_manifoldness for values %$meshes_by_material; $_->check_manifoldness for values %$meshes_by_material;
$print = Slic3r::Print->new_from_mesh($meshes_by_material->{_} || +(values %$meshes_by_material)[0]); $print->add_object_from_mesh($meshes_by_material->{_} || +(values %$meshes_by_material)[0]);
} else { } else {
die "Input file must have .stl or .amf(.xml) extension\n"; die "Input file must have .stl or .amf(.xml) extension\n";
} }
}
return $print;
} }
sub go { sub go {
@ -42,76 +46,82 @@ sub go {
$self->status_cb->(5, "Processing input file " . $self->input_file); $self->status_cb->(5, "Processing input file " . $self->input_file);
$self->status_cb->(10, "Processing triangulated mesh"); $self->status_cb->(10, "Processing triangulated mesh");
my $print = $self->slice_input; my $print = $self->slice_input;
$print->arrange_objects;
# make perimeters # make perimeters
# this will add a set of extrusion loops to each layer # this will add a set of extrusion loops to each layer
# as well as generate infill boundaries # as well as generate infill boundaries
$self->status_cb->(20, "Generating perimeters"); $self->status_cb->(20, "Generating perimeters");
{ $_->make_perimeters for map @{$_->layers}, @{$print->objects};
my $perimeter_maker = Slic3r::Perimeter->new;
$perimeter_maker->make_perimeter($_) for @{$print->layers};
}
# this will clip $layer->surfaces to the infill boundaries # this will clip $layer->surfaces to the infill boundaries
# and split them in top/bottom/internal surfaces; # and split them in top/bottom/internal surfaces;
$self->status_cb->(30, "Detecting solid surfaces"); $self->status_cb->(30, "Detecting solid surfaces");
$print->detect_surfaces_type; $_->detect_surfaces_type for @{$print->objects};
# decide what surfaces are to be filled # decide what surfaces are to be filled
$self->status_cb->(35, "Preparing infill surfaces"); $self->status_cb->(35, "Preparing infill surfaces");
$_->prepare_fill_surfaces for @{$print->layers}; $_->prepare_fill_surfaces for map @{$_->layers}, @{$print->objects};
# this will remove unprintable surfaces # this will remove unprintable surfaces
# (those that are too tight for extrusion) # (those that are too tight for extrusion)
$self->status_cb->(40, "Cleaning up"); $self->status_cb->(40, "Cleaning up");
$_->remove_small_surfaces for @{$print->layers}; $_->remove_small_surfaces for map @{$_->layers}, @{$print->objects};
# this will detect bridges and reverse bridges # this will detect bridges and reverse bridges
# and rearrange top/bottom/internal surfaces # and rearrange top/bottom/internal surfaces
$self->status_cb->(45, "Detect bridges"); $self->status_cb->(45, "Detect bridges");
$_->process_bridges for @{$print->layers}; $_->process_bridges for map @{$_->layers}, @{$print->objects};
# this will remove unprintable perimeter loops # this will remove unprintable perimeter loops
# (those that are too tight for extrusion) # (those that are too tight for extrusion)
$self->status_cb->(50, "Cleaning up the perimeters"); $self->status_cb->(50, "Cleaning up the perimeters");
$_->remove_small_perimeters for @{$print->layers}; $_->remove_small_perimeters for map @{$_->layers}, @{$print->objects};
# detect which fill surfaces are near external layers # detect which fill surfaces are near external layers
# they will be split in internal and internal-solid surfaces # they will be split in internal and internal-solid surfaces
$self->status_cb->(60, "Generating horizontal shells"); $self->status_cb->(60, "Generating horizontal shells");
$print->discover_horizontal_shells; $_->discover_horizontal_shells for @{$print->objects};
# free memory # free memory
@{$_->surfaces} = () for @{$print->layers}; @{$_->surfaces} = () for map @{$_->layers}, @{$print->objects};
# combine fill surfaces to honor the "infill every N layers" option # combine fill surfaces to honor the "infill every N layers" option
$self->status_cb->(70, "Combining infill"); $self->status_cb->(70, "Combining infill");
$print->infill_every_layers; $_->infill_every_layers for @{$print->objects};
# this will generate extrusion paths for each layer # this will generate extrusion paths for each layer
$self->status_cb->(80, "Infilling layers"); $self->status_cb->(80, "Infilling layers");
{ {
my $fill_maker = Slic3r::Fill->new('print' => $print); my $fill_maker = Slic3r::Fill->new('print' => $print);
my @items = (); # [obj_idx, layer_id]
foreach my $obj_idx (0 .. $#{$print->objects}) {
push @items, map [$obj_idx, $_], 0..$#{$print->objects->[$obj_idx]->layers};
}
Slic3r::parallelize( Slic3r::parallelize(
items => [ 0..($print->layer_count-1) ], items => [@items],
thread_cb => sub { thread_cb => sub {
my $q = shift; my $q = shift;
$Slic3r::Geometry::Clipper::clipper = Math::Clipper->new; $Slic3r::Geometry::Clipper::clipper = Math::Clipper->new;
my $fills = {}; my $fills = {};
while (defined (my $layer_id = $q->dequeue)) { while (defined (my $obj_layer = $q->dequeue)) {
$fills->{$layer_id} = [ $fill_maker->make_fill($print->layers->[$layer_id]) ]; my ($obj_idx, $layer_id) = @$obj_layer;
$fills->{$obj_idx} ||= {};
$fills->{$obj_idx}{$layer_id} = [ $fill_maker->make_fill($print->objects->[$obj_idx]->layers->[$layer_id]) ];
} }
return $fills; return $fills;
}, },
collect_cb => sub { collect_cb => sub {
my $fills = shift; my $fills = shift;
foreach my $layer_id (keys %$fills) { foreach my $obj_idx (keys %$fills) {
@{$print->layers->[$layer_id]->fills} = @{$fills->{$layer_id}}; foreach my $layer_id (keys %{$fills->{$obj_idx}}) {
@{$print->objects->[$obj_idx]->layers->[$layer_id]->fills} = @{$fills->{$obj_idx}{$layer_id}};
}
} }
}, },
no_threads_cb => sub { no_threads_cb => sub {
foreach my $layer (@{$print->layers}) { foreach my $layer (map @{$_->layers}, @{$print->objects}) {
@{$layer->fills} = $fill_maker->make_fill($layer); @{$layer->fills} = $fill_maker->make_fill($layer);
} }
}, },
@ -121,15 +131,15 @@ sub go {
# generate support material # generate support material
if ($Slic3r::support_material) { if ($Slic3r::support_material) {
$self->status_cb->(85, "Generating support material"); $self->status_cb->(85, "Generating support material");
$print->generate_support_material; $_->generate_support_material for @{$print->objects};
} }
# free memory (note that support material needs fill_surfaces) # free memory (note that support material needs fill_surfaces)
@{$_->fill_surfaces} = () for @{$print->layers}; @{$_->fill_surfaces} = () for map @{$_->layers}, @{$print->objects};
# make skirt # make skirt
$self->status_cb->(88, "Generating skirt"); $self->status_cb->(88, "Generating skirt");
$print->extrude_skirt; $print->make_skirt;
# output everything to a G-code file # output everything to a G-code file
my $output_file = $self->expanded_output_filepath; my $output_file = $self->expanded_output_filepath;

View File

@ -338,7 +338,7 @@ sub size {
sub slice_facet { sub slice_facet {
my $self = shift; my $self = shift;
my ($print, $facet_id) = @_; my ($print_object, $facet_id) = @_;
my ($normal, @vertices) = @{$self->facets->[$facet_id]}; my ($normal, @vertices) = @{$self->facets->[$facet_id]};
Slic3r::debugf "\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", Slic3r::debugf "\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n",
$facet_id, map @{$self->vertices->[$_]}, @vertices $facet_id, map @{$self->vertices->[$_]}, @vertices
@ -367,7 +367,7 @@ sub slice_facet {
my $lines = {}; # layer_id => [ lines ] my $lines = {}; # layer_id => [ lines ]
for (my $layer_id = $min_layer; $layer_id <= $max_layer; $layer_id++) { for (my $layer_id = $min_layer; $layer_id <= $max_layer; $layer_id++) {
my $layer = $print->layer($layer_id); my $layer = $print_object->layer($layer_id);
$lines->{$layer_id} ||= []; $lines->{$layer_id} ||= [];
push @{ $lines->{$layer_id} }, $self->intersect_facet($facet_id, $layer->slice_z); push @{ $lines->{$layer_id} }, $self->intersect_facet($facet_id, $layer->slice_z);
} }

View File

@ -26,6 +26,7 @@ my %cli_options = ();
'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config}, 'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config},
'threads|j=i' => \$Slic3r::threads, 'threads|j=i' => \$Slic3r::threads,
'export-svg' => \$opt{export_svg}, 'export-svg' => \$opt{export_svg},
'merge' => \$opt{merge},
); );
foreach my $opt_key (keys %$Slic3r::Config::Options) { foreach my $opt_key (keys %$Slic3r::Config::Options) {
my $opt = $Slic3r::Config::Options->{$opt_key}; my $opt = $Slic3r::Config::Options->{$opt_key};
@ -72,9 +73,10 @@ if (!@ARGV && !$opt{save} && eval "require Slic3r::GUI; 1") {
} }
if (@ARGV) { if (@ARGV) {
foreach my $input_file ( @ARGV ) { while (my $input_file = shift @ARGV) {
my $skein = Slic3r::Skein->new( my $skein = Slic3r::Skein->new(
input_file => $input_file, input_file => $input_file,
additional_input_files => $opt{merge} ? [ splice @ARGV, 0 ] : [],
output_file => $opt{output}, output_file => $opt{output},
status_cb => sub { status_cb => sub {
my ($percent, $message) = @_; my ($percent, $message) = @_;
@ -123,6 +125,8 @@ $j
--post-process Generated G-code will be processed with the supplied script; --post-process Generated G-code will be processed with the supplied script;
call this more than once to process through multiple scripts. call this more than once to process through multiple scripts.
--export-svg Export a SVG file containing slices instead of G-code. --export-svg Export a SVG file containing slices instead of G-code.
--merge If multiple files are supplied, they will be composed into a single
print rather than processed individually.
Printer options: Printer options:
--nozzle-diameter Diameter of nozzle in mm (default: $Slic3r::nozzle_diameter) --nozzle-diameter Diameter of nozzle in mm (default: $Slic3r::nozzle_diameter)