New semantics for ExtrusionLoop objects. Early processing of perimeter overhangs for paralellizing such work and making G-code export lighter. Lots of refactoring. This should fix a number of minor bugs, including reversals of perimeter overhangs.

This commit is contained in:
Alessandro Ranellucci 2014-05-08 11:07:37 +02:00
parent d2d885fc53
commit c37ef2f18b
27 changed files with 618 additions and 423 deletions

View file

@ -2,16 +2,4 @@ package Slic3r::ExtrusionLoop;
use strict;
use warnings;
sub split_at {
my $self = shift;
return Slic3r::ExtrusionPath->new(
polyline => $self->polygon->split_at(@_),
role => $self->role,
mm3_per_mm => $self->mm3_per_mm,
width => $self->width,
height => $self->height,
);
}
1;

View file

@ -19,7 +19,6 @@ has 'layer' => (is => 'rw');
has 'region' => (is => 'rw');
has '_layer_islands' => (is => 'rw');
has '_upper_layer_islands' => (is => 'rw');
has '_lower_layer_slices' => (is => 'ro', default => sub { Slic3r::ExPolygon::Collection->new });
has 'shift_x' => (is => 'rw', default => sub {0} );
has 'shift_y' => (is => 'rw', default => sub {0} );
has 'z' => (is => 'rw');
@ -111,17 +110,6 @@ sub change_layer {
# avoid computing islands and overhangs if they're not needed
$self->_layer_islands($layer->islands);
$self->_upper_layer_islands($layer->upper_layer ? $layer->upper_layer->islands : []);
$self->_lower_layer_slices->clear;
if ($layer->lower_layer && ($self->print_config->overhangs || $self->print_config->start_perimeters_at_non_overhang)) {
# We consider overhang any part where the entire nozzle diameter is not supported by the
# lower layer, so we take lower slices and offset them by half the nozzle diameter used
# in the current layer
my $max_nozzle_diameter = max(map $layer->print->config->get_at('nozzle_diameter', $_->region->config->perimeter_extruder-1), @{$layer->regions});
$self->_lower_layer_slices->append(
# clone ExPolygons because they come from Surface objects but will be used outside here
@{offset_ex([ map @$_, @{$layer->lower_layer->slices} ], +scale($max_nozzle_diameter/2))},
);
}
if ($self->print_config->avoid_crossing_perimeters) {
$self->layer_mp(Slic3r::GCode::MotionPlanner->new(
islands => union_ex([ map @$_, @{$layer->slices} ], 1),
@ -199,17 +187,17 @@ sub extrude_loop {
# extrude all loops ccw
my $was_clockwise = $loop->make_counter_clockwise;
my $polygon = $loop->polygon;
# find candidate starting points
# start looking for concave vertices not being overhangs
my $polygon = $loop->polygon;
my @concave = ();
if ($self->print_config->start_perimeters_at_concave_points) {
@concave = $polygon->concave_points;
}
my @candidates = ();
if ($self->print_config->start_perimeters_at_non_overhang) {
@candidates = grep $self->_lower_layer_slices->contains_point($_), @concave;
@candidates = grep !$loop->has_overhang_point($_), @concave;
}
if (!@candidates) {
# if none, look for any concave vertex
@ -217,11 +205,11 @@ sub extrude_loop {
if (!@candidates) {
# if none, look for any non-overhang vertex
if ($self->print_config->start_perimeters_at_non_overhang) {
@candidates = grep $self->_lower_layer_slices->contains_point($_), @$polygon;
@candidates = grep !$loop->has_overhang_point($_), @$polygon;
}
if (!@candidates) {
# if none, all points are valid candidates
@candidates = @{$polygon};
@candidates = @$polygon;
}
}
}
@ -234,71 +222,49 @@ sub extrude_loop {
$last_pos->rotate(rand(2*PI), $self->print_config->print_center);
}
# split the loop at the starting point and make a path
my $start_at = $last_pos->nearest_point(\@candidates);
my $extrusion_path = $loop->split_at($start_at);
# split the loop at the starting point
$loop->split_at($last_pos->nearest_point(\@candidates));
# clip the path to avoid the extruder to get exactly on the first point of the loop;
# if polyline was shorter than the clipping distance we'd get a null polyline, so
# we discard it in that case
$extrusion_path->clip_end(scale($self->extruder->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER)
if $self->enable_loop_clipping;
return '' if !@{$extrusion_path->polyline};
my $clip_length = $self->enable_loop_clipping
? scale($self->extruder->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER
: 0;
my @paths = ();
# detect overhanging/bridging perimeters
if ($self->layer->print->config->overhangs && $extrusion_path->is_perimeter && $self->_lower_layer_slices->count > 0) {
# get non-overhang paths by intersecting this loop with the grown lower slices
push @paths,
map $_->clone,
@{$extrusion_path->intersect_expolygons($self->_lower_layer_slices)};
# get overhang paths by checking what parts of this loop fall
# outside the grown lower slices (thus where the distance between
# the loop centerline and original lower slices is >= half nozzle diameter
foreach my $path (@{$extrusion_path->subtract_expolygons($self->_lower_layer_slices)}) {
$path = $path->clone;
$path->role(EXTR_ROLE_OVERHANG_PERIMETER);
$path->mm3_per_mm($self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, undef, $self->layer->object)->mm3_per_mm(-1));
push @paths, $path;
}
# reapply the nearest point search for starting point
# (clone because the collection gets DESTROY'ed)
my $collection = Slic3r::ExtrusionPath::Collection->new(@paths);
@paths = map $_->clone, @{$collection->chained_path_from($start_at, 1)};
} else {
push @paths, $extrusion_path;
}
# get paths
my @paths = @{$loop->clip_end($clip_length)};
return '' if !@paths;
# apply the small perimeter speed
my %params = ();
if ($extrusion_path->is_perimeter && abs($extrusion_path->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) {
if ($paths[0]->is_perimeter && abs($loop->length) <= &Slic3r::SMALL_PERIMETER_LENGTH) {
$params{speed} = 'small_perimeter';
}
# extrude along the path
my $gcode = join '', map $self->extrude_path($_, $description, %params), @paths;
$self->wipe_path($extrusion_path->polyline->clone) if $self->enable_wipe;
$self->wipe_path($paths[-1]->polyline->clone) if $self->enable_wipe; # TODO: don't limit wipe to last path
# make a little move inwards before leaving loop
if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->region->config->perimeters > 1) {
if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->region->config->perimeters > 1) {
my $last_path_polyline = $paths[-1]->polyline;
# detect angle between last and first segment
# the side depends on the original winding order of the polygon (left for contours, right for holes)
my @points = $was_clockwise ? (-2, 1) : (1, -2);
my $angle = Slic3r::Geometry::angle3points(@{$extrusion_path->polyline}[0, @points]) / 3;
my $angle = Slic3r::Geometry::angle3points(@$last_path_polyline[0, @points]) / 3;
$angle *= -1 if $was_clockwise;
# create the destination point along the first segment and rotate it
# we make sure we don't exceed the segment length because we don't know
# the rotation of the second segment so we might cross the object boundary
my $first_segment = Slic3r::Line->new(@{$extrusion_path->polyline}[0,1]);
my $first_segment = Slic3r::Line->new(@$last_path_polyline[0,1]);
my $distance = min(scale($self->extruder->nozzle_diameter), $first_segment->length);
my $point = $first_segment->point_at($distance);
$point->rotate($angle, $extrusion_path->first_point);
$point->rotate($angle, $last_path_polyline->first_point);
# generate the travel move
$gcode .= $self->travel_to($point, $loop->role, "move inwards before travel");
$gcode .= $self->travel_to($point, $paths[-1]->role, "move inwards before travel");
}
return $gcode;

View file

@ -8,6 +8,6 @@ our @EXPORT_OK = qw(offset offset_ex
diff_ex diff union_ex intersection_ex xor_ex JT_ROUND JT_MITER
JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex
intersection intersection_pl diff_pl union CLIPPER_OFFSET_SCALE
union_pt_chained);
union_pt_chained diff_ppl intersection_ppl);
1;

View file

@ -3,7 +3,8 @@ use Moo;
use List::Util qw(first sum max min);
use Slic3r::Geometry qw(PI unscale scaled_epsilon rad2deg epsilon directions_parallel_within);
use Slic3r::Geometry::Clipper qw(intersection_pl intersection_ex union offset diff_pl union_ex);
use Slic3r::Geometry::Clipper qw(intersection_pl intersection_ex union offset diff_pl union_ex
intersection_ppl);
has 'expolygon' => (is => 'ro', required => 1);
has 'lower_slices' => (is => 'rw', required => 1); # ExPolygons or ExPolygonCollection
@ -26,7 +27,7 @@ sub BUILD {
foreach my $lower (@{$self->lower_slices}) {
# turn bridge contour and holes into polylines and then clip them
# with each lower slice's contour
push @$edges, map @{$_->clip_as_polyline([$lower->contour])}, @$grown;
push @$edges, @{intersection_ppl($grown, [ $lower->contour ])};
}
Slic3r::debugf " bridge has %d support(s)\n", scalar(@$edges);

View file

@ -7,7 +7,7 @@ use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(PI A B scale unscale chained_path points_coincide);
use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex
offset offset_ex offset2 offset2_ex union_pt diff intersection
union diff);
union diff intersection_ppl diff_ppl);
use Slic3r::Surface ':types';
has 'layer' => (
@ -63,6 +63,8 @@ sub make_perimeters {
my $perimeter_flow = $self->flow(FLOW_ROLE_PERIMETER);
my $mm3_per_mm = $perimeter_flow->mm3_per_mm($self->height);
my $overhang_flow = $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, undef, $self->layer->object);
my $mm3_per_mm_overhang = $overhang_flow->mm3_per_mm(-1);
my $pwidth = $perimeter_flow->scaled_width;
my $pspacing = $perimeter_flow->scaled_spacing;
my $solid_infill_flow = $self->flow(FLOW_ROLE_SOLID_INFILL);
@ -229,6 +231,19 @@ sub make_perimeters {
my $contours_pt = union_pt(\@contours);
my $holes_pt = union_pt(\@holes);
# prepare grown lower layer slices for overhang detection
my $lower_slices = Slic3r::ExPolygon::Collection->new;
if ($self->layer->lower_layer && $self->layer->print->config->overhangs) {
# We consider overhang any part where the entire nozzle diameter is not supported by the
# lower layer, so we take lower slices and offset them by half the nozzle diameter used
# in the current layer
my $nozzle_diameter = $self->layer->print->config->get_at('nozzle_diameter', $self->region->config->perimeter_extruder-1);
$lower_slices->append(
@{offset_ex([ map @$_, @{$self->layer->lower_layer->slices} ], scale +$nozzle_diameter/2)},
);
}
my $lower_slices_p = $lower_slices->polygons;
# prepare a coderef for traversing the PolyTree object
# external contours are root items of $contours_pt
# internal contours are the ones next to external
@ -242,15 +257,6 @@ sub make_perimeters {
foreach my $polynode (@$polynodes) {
my $polygon = ($polynode->{outer} // $polynode->{hole})->clone;
# return ccw contours and cw holes
# GCode.pm will convert all of them to ccw, but it needs to know
# what the holes are in order to compute the correct inwards move
if ($is_contour) {
$polygon->make_counter_clockwise;
} else {
$polygon->make_clockwise;
}
my $role = EXTR_ROLE_PERIMETER;
if ($is_contour ? $depth == 0 : !@{ $polynode->{children} }) {
# external perimeters are root level in case of contours
@ -260,13 +266,62 @@ sub make_perimeters {
$role = EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER;
}
$collection->append(Slic3r::ExtrusionLoop->new(
polygon => $polygon,
role => $role,
mm3_per_mm => $mm3_per_mm,
width => $perimeter_flow->width,
height => $self->height,
));
# detect overhanging/bridging perimeters
my @paths = ();
if ($self->layer->print->config->overhangs && $lower_slices->count > 0) {
# get non-overhang paths by intersecting this loop with the grown lower slices
foreach my $polyline (@{ intersection_ppl([ $polygon ], $lower_slices_p) }) {
push @paths, Slic3r::ExtrusionPath->new(
polyline => $polyline,
role => $role,
mm3_per_mm => $mm3_per_mm,
width => $perimeter_flow->width,
height => $self->height,
);
}
# get overhang paths by checking what parts of this loop fall
# outside the grown lower slices (thus where the distance between
# the loop centerline and original lower slices is >= half nozzle diameter
foreach my $polyline (@{ diff_ppl([ $polygon ], $lower_slices_p) }) {
push @paths, Slic3r::ExtrusionPath->new(
polyline => $polyline,
role => EXTR_ROLE_OVERHANG_PERIMETER,
mm3_per_mm => $mm3_per_mm_overhang,
width => $overhang_flow->width,
height => $self->height,
);
}
# reapply the nearest point search for starting point
# (clone because the collection gets DESTROY'ed)
# We allow polyline reversal because Clipper may have randomly
# reversed polylines during clipping.
my $collection = Slic3r::ExtrusionPath::Collection->new(@paths);
@paths = map $_->clone, @{$collection->chained_path(0)};
} else {
push @paths, Slic3r::ExtrusionPath->new(
polyline => $polygon->split_at_first_point,
role => $role,
mm3_per_mm => $mm3_per_mm,
width => $perimeter_flow->width,
height => $self->height,
);
}
my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths);
# return ccw contours and cw holes
# GCode.pm will convert all of them to ccw, but it needs to know
# what the holes are in order to compute the correct inwards move
# We do this on the final Loop object instead of the polygon because
# overhang clipping might have reversed its order since Clipper does
# not preserve polyline orientation.
if ($is_contour) {
$loop->make_counter_clockwise;
} else {
$loop->make_clockwise;
}
$collection->append($loop);
# save the children
push @children, $polynode->{children};
@ -367,11 +422,16 @@ sub _fill_gaps {
Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @$this, $w
if @$this;
return map {
$_->isa('Slic3r::Polygon')
? Slic3r::ExtrusionLoop->new(polygon => $_, %path_args)->split_at_first_point # should we keep these as loops?
: Slic3r::ExtrusionPath->new(polyline => $_, %path_args),
} @polylines;
for my $i (0..$#polylines) {
if ($polylines[$i]->isa('Slic3r::Polygon')) {
my $loop = Slic3r::ExtrusionLoop->new;
$loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args));
$polylines[$i] = $loop;
} else {
$polylines[$i] = Slic3r::ExtrusionPath->new(polyline => $polylines[$i], %path_args);
}
}
return @polylines;
}
sub prepare_fill_surfaces {

View file

@ -51,41 +51,4 @@ sub concave_points {
-1 .. ($#points-1);
}
sub clip_as_polyline {
my ($self, $polygons) = @_;
my $self_pl = $self->split_at_first_point;
# Clipper will remove a polyline segment if first point coincides with last one.
# Until that bug is not fixed upstream, we move one of those points slightly.
$self_pl->[0]->translate(1, 0);
my @polylines = @{intersection_pl([$self_pl], $polygons)};
if (@polylines == 1) {
if ($polylines[0][0]->coincides_with($self_pl->[0])) {
# compensate the above workaround for Clipper bug
$polylines[0][0]->translate(-1, 0);
}
} elsif (@polylines == 2) {
# If the split_at_first_point() call above happens to split the polygon inside the clipping area
# we would get two consecutive polylines instead of a single one, so we use this ugly hack to
# recombine them back into a single one in order to trigger the @edges == 2 logic below.
# This needs to be replaced with something way better.
if ($polylines[0][-1]->coincides_with($self_pl->[-1]) && $polylines[-1][0]->coincides_with($self_pl->[0])) {
my $p = $polylines[0]->clone;
$p->pop_back;
$p->append(@{$polylines[-1]});
return [$p];
}
if ($polylines[0][0]->coincides_with($self_pl->[0]) && $polylines[-1][-1]->coincides_with($self_pl->[-1])) {
my $p = $polylines[-1]->clone;
$p->pop_back;
$p->append(@{$polylines[0]});
return [$p];
}
}
return [ @polylines ];
}
1;

View file

@ -705,12 +705,14 @@ sub make_skirt {
for (my $i = $self->config->skirts; $i > 0; $i--) {
$distance += scale $spacing;
my $loop = offset([$convex_hull], $distance, 1, JT_ROUND, scale(0.1))->[0];
$self->skirt->append(Slic3r::ExtrusionLoop->new(
polygon => Slic3r::Polygon->new(@$loop),
role => EXTR_ROLE_SKIRT,
mm3_per_mm => $mm3_per_mm,
width => $flow->width,
height => $first_layer_height,
$self->skirt->append(Slic3r::ExtrusionLoop->new_from_paths(
Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polygon->new(@$loop)->split_at_first_point,
role => EXTR_ROLE_SKIRT,
mm3_per_mm => $mm3_per_mm,
width => $flow->width,
height => $first_layer_height,
),
));
if ($self->config->min_skirt_length > 0) {
@ -788,12 +790,14 @@ sub make_brim {
push @loops, @{offset2(\@islands, ($i + 0.5) * $flow->scaled_spacing, -1.0 * $flow->scaled_spacing, 100000, JT_SQUARE)};
}
$self->brim->append(map Slic3r::ExtrusionLoop->new(
polygon => Slic3r::Polygon->new(@$_),
role => EXTR_ROLE_SKIRT,
mm3_per_mm => $mm3_per_mm,
width => $flow->width,
height => $first_layer_height,
$self->brim->append(map Slic3r::ExtrusionLoop->new_from_paths(
Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polygon->new(@$_)->split_at_first_point,
role => EXTR_ROLE_SKIRT,
mm3_per_mm => $mm3_per_mm,
width => $flow->width,
height => $first_layer_height,
),
), reverse @{union_pt_chained(\@loops)});
}
@ -913,7 +917,7 @@ sub write_gcode {
# calculate wiping points if needed
if ($self->config->ooze_prevention) {
my @skirt_points = map @$_, @{$self->skirt};
my @skirt_points = map @$_, map @$_, @{$self->skirt};
if (@skirt_points) {
my $outer_skirt = convex_hull(\@skirt_points);
my @skirts = ();

View file

@ -70,14 +70,20 @@ sub generate {
$self->clip_with_shape($base, $shape) if @$shape;
# Install support layers into object.
push @{$object->support_layers}, map Slic3r::Layer::Support->new(
object => $object,
id => $_,
height => ($_ == 0) ? $support_z->[$_] : ($support_z->[$_] - $support_z->[$_-1]),
print_z => $support_z->[$_],
slice_z => -1,
slices => [],
), 0 .. $#$support_z;
for my $i (0 .. $#$support_z) {
push @{$object->support_layers}, Slic3r::Layer::Support->new(
object => $object,
id => $i,
height => ($i == 0) ? $support_z->[$i] : ($support_z->[$i] - $support_z->[$i-1]),
print_z => $support_z->[$i],
slice_z => -1,
slices => [],
);
if ($i >= 1) {
$object->support_layers->[-2]->upper_layer($object->support_layers->[-1]);
$object->support_layers->[-1]->lower_layer($object->support_layers->[-2]);
}
}
# Generate the actual toolpaths and save them into each layer.
$self->generate_toolpaths($object, $overhang, $contact, $interface, $base);