Manual rebase of the avoid_crossing_perimeters feature
This commit is contained in:
parent
d278998f11
commit
0eadc5adba
12 changed files with 362 additions and 11 deletions
1
MANIFEST
1
MANIFEST
|
@ -24,6 +24,7 @@ lib/Slic3r/Format/AMF/Parser.pm
|
|||
lib/Slic3r/Format/OBJ.pm
|
||||
lib/Slic3r/Format/STL.pm
|
||||
lib/Slic3r/GCode.pm
|
||||
lib/Slic3r/GCode/MotionPlanner.pm
|
||||
lib/Slic3r/Geometry.pm
|
||||
lib/Slic3r/Geometry/Clipper.pm
|
||||
lib/Slic3r/GUI.pm
|
||||
|
|
|
@ -170,6 +170,7 @@ The author of the Silk icon set is Mark James.
|
|||
--layer-gcode Load layer-change G-code from the supplied file (default: nothing).
|
||||
--extra-perimeters Add more perimeters when needed (default: yes)
|
||||
--randomize-start Randomize starting point across layers (default: yes)
|
||||
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
|
||||
|
||||
Support material options:
|
||||
--support-material Generate support material for overhangs
|
||||
|
|
|
@ -38,6 +38,7 @@ use Slic3r::Format::AMF;
|
|||
use Slic3r::Format::OBJ;
|
||||
use Slic3r::Format::STL;
|
||||
use Slic3r::GCode;
|
||||
use Slic3r::GCode::MotionPlanner;
|
||||
use Slic3r::Geometry qw(PI);
|
||||
use Slic3r::Layer;
|
||||
use Slic3r::Line;
|
||||
|
|
|
@ -462,6 +462,13 @@ our $Options = {
|
|||
type => 'bool',
|
||||
default => 1,
|
||||
},
|
||||
'avoid_crossing_perimeters' => {
|
||||
label => 'Avoid crossing perimeters',
|
||||
tooltip => 'Optimize travel moves in order to minimize the crossing of perimeters. This is mostly useful with Bowden extruders which suffer from oozing. This feature slows down the processing times.',
|
||||
cli => 'avoid-crossing-perimeters!',
|
||||
type => 'bool',
|
||||
default => 0,
|
||||
},
|
||||
'support_material' => {
|
||||
label => 'Generate support material',
|
||||
tooltip => 'Enable support material generation.',
|
||||
|
|
|
@ -155,16 +155,19 @@ sub clip_line {
|
|||
sub simplify {
|
||||
my $self = shift;
|
||||
$_->simplify(@_) for @$self;
|
||||
$self;
|
||||
}
|
||||
|
||||
sub translate {
|
||||
my $self = shift;
|
||||
$_->translate(@_) for @$self;
|
||||
$self;
|
||||
}
|
||||
|
||||
sub rotate {
|
||||
my $self = shift;
|
||||
$_->rotate(@_) for @$self;
|
||||
$self;
|
||||
}
|
||||
|
||||
sub area {
|
||||
|
|
|
@ -2,7 +2,8 @@ package Slic3r::GCode;
|
|||
use Moo;
|
||||
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Geometry qw(scale unscale);
|
||||
use Slic3r::Geometry qw(PI X Y scale unscale points_coincide);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex);
|
||||
|
||||
has 'layer' => (is => 'rw');
|
||||
has 'shift_x' => (is => 'rw', default => sub {0} );
|
||||
|
@ -10,6 +11,8 @@ has 'shift_y' => (is => 'rw', default => sub {0} );
|
|||
has 'z' => (is => 'rw', default => sub {0} );
|
||||
has 'speed' => (is => 'rw');
|
||||
|
||||
has 'motionplanner' => (is => 'rw');
|
||||
has 'straight_once' => (is => 'rw');
|
||||
has 'extruder_idx' => (is => 'rw');
|
||||
has 'extrusion_distance' => (is => 'rw', default => sub {0} );
|
||||
has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds
|
||||
|
@ -48,18 +51,35 @@ my %role_speeds = (
|
|||
&EXTR_ROLE_SUPPORTMATERIAL => 'perimeter',
|
||||
);
|
||||
|
||||
use Slic3r::Geometry qw(points_coincide PI X Y);
|
||||
|
||||
sub extruder {
|
||||
my $self = shift;
|
||||
return $Slic3r::extruders->[$self->extruder_idx];
|
||||
}
|
||||
|
||||
sub set_shift {
|
||||
my $self = shift;
|
||||
my @shift = @_;
|
||||
|
||||
# adjust last position
|
||||
$self->last_pos($self->last_pos->clone->translate(
|
||||
scale($self->shift_x - $shift[X]),
|
||||
scale($self->shift_y - $shift[Y]),
|
||||
));
|
||||
|
||||
$self->shift_x($shift[X]);
|
||||
$self->shift_y($shift[Y]);
|
||||
}
|
||||
|
||||
sub change_layer {
|
||||
my $self = shift;
|
||||
my ($layer) = @_;
|
||||
|
||||
$self->layer($layer);
|
||||
if ($Slic3r::Config->avoid_crossing_perimeters) {
|
||||
$self->motionplanner(Slic3r::GCode::MotionPlanner->new(
|
||||
islands => union_ex([ map @{$_->expolygon}, @{$layer->slices} ], undef, 1),
|
||||
));
|
||||
}
|
||||
my $z = $Slic3r::Config->z_offset + $layer->print_z * &Slic3r::SCALING_FACTOR;
|
||||
|
||||
my $gcode = "";
|
||||
|
@ -145,8 +165,7 @@ sub extrude_path {
|
|||
}
|
||||
|
||||
# go to first point of extrusion path
|
||||
$gcode .= $self->G0($path->points->[0], undef, 0, "move to first $description point")
|
||||
if !points_coincide($self->last_pos, $path->points->[0]);
|
||||
$gcode .= $self->travel_to($path->points->[0], "move to first $description point");
|
||||
|
||||
# compensate retraction
|
||||
$gcode .= $self->unretract if $self->extruder->retracted;
|
||||
|
@ -192,6 +211,20 @@ sub extrude_path {
|
|||
return $gcode;
|
||||
}
|
||||
|
||||
sub travel_to {
|
||||
my $self = shift;
|
||||
my ($point, $comment) = @_;
|
||||
|
||||
return "" if points_coincide($self->last_pos, $point);
|
||||
if ($Slic3r::Config->avoid_crossing_perimeters && $self->last_pos->distance_to($point) > scale 5 && !$self->straight_once) {
|
||||
return join '', map $self->G0($_->b, undef, 0, $comment || ""),
|
||||
$self->motionplanner->shortest_path($self->last_pos, $point)->lines;
|
||||
} else {
|
||||
$self->straight_once(0);
|
||||
return $self->G0($point, undef, 0, $comment || "");
|
||||
}
|
||||
}
|
||||
|
||||
sub retract {
|
||||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
|
259
lib/Slic3r/GCode/MotionPlanner.pm
Normal file
259
lib/Slic3r/GCode/MotionPlanner.pm
Normal file
|
@ -0,0 +1,259 @@
|
|||
package Slic3r::GCode::MotionPlanner;
|
||||
use Moo;
|
||||
|
||||
has 'islands' => (is => 'ro', required => 1);
|
||||
has 'no_internal' => (is => 'ro');
|
||||
has 'last_crossings'=> (is => 'rw');
|
||||
has '_inner' => (is => 'rw', default => sub { [] }); # arrayref of arrayrefs of expolygons
|
||||
has '_outer' => (is => 'rw', default => sub { [] }); # arrayref of arrayrefs of polygons
|
||||
has '_contours_ex' => (is => 'rw', default => sub { [] }); # arrayref of arrayrefs of expolygons
|
||||
has '_pointmap' => (is => 'rw', default => sub { {} }); # { id => $point }
|
||||
has '_edges' => (is => 'rw', default => sub { {} }); # node_idx => { node_idx => distance, ... }
|
||||
has '_crossing_edges' => (is => 'rw', default => sub { {} }); # edge_idx => bool
|
||||
|
||||
use List::Util qw(first);
|
||||
use Slic3r::Geometry qw(scale epsilon nearest_point);
|
||||
use Slic3r::Geometry::Clipper qw(diff_ex JT_MITER);
|
||||
|
||||
# clearance (in mm) from the perimeters
|
||||
has '_inner_margin' => (is => 'ro', default => sub { scale 0.5 });
|
||||
has '_outer_margin' => (is => 'ro', default => sub { scale 2 });
|
||||
|
||||
# this factor weigths the crossing of a perimeter
|
||||
# vs. the alternative path. a value of 5 means that
|
||||
# a perimeter will be crossed if the alternative path
|
||||
# is >= 5x the length of the straight line we could
|
||||
# follow if we decided to cross the perimeter.
|
||||
# a nearly-infinite value for this will only permit
|
||||
# perimeter crossing when there's no alternative path.
|
||||
use constant CROSSING_FACTOR => 20;
|
||||
|
||||
use constant INFINITY => 'inf';
|
||||
|
||||
# setup our configuration space
|
||||
sub BUILD {
|
||||
my $self = shift;
|
||||
|
||||
my $edges = $self->_edges;
|
||||
my $crossing_edges = $self->_crossing_edges;
|
||||
my $tolerance = scale epsilon;
|
||||
|
||||
my $add_expolygon = sub {
|
||||
my ($expolygon, $crosses_perimeter) = @_;
|
||||
my @points = map @$_, @$expolygon;
|
||||
for my $i (0 .. $#points) {
|
||||
for my $j (($i+1) .. $#points) {
|
||||
my $line = Slic3r::Line->new($points[$i], $points[$j]);
|
||||
if ($expolygon->encloses_line($line, scale Slic3r::Geometry::epsilon)) {
|
||||
my $dist = $line->length * ($crosses_perimeter ? CROSSING_FACTOR : 1);
|
||||
$edges->{$points[$i]}{$points[$j]} = $dist;
|
||||
$edges->{$points[$j]}{$points[$i]} = $dist;
|
||||
$crossing_edges->{$points[$i]}{$points[$j]} = 1;
|
||||
$crossing_edges->{$points[$j]}{$points[$i]} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for my $i (0 .. $#{$self->islands}) {
|
||||
$self->islands->[$i]->simplify($self->_inner_margin);
|
||||
$self->_inner->[$i] = [ $self->islands->[$i]->offset_ex(-$self->_inner_margin) ]
|
||||
if !$self->no_internal;
|
||||
$self->_outer->[$i] = [ $self->islands->[$i]->contour->offset($self->_outer_margin) ];
|
||||
$_->simplify($self->_inner_margin) for @{$self->_inner->[$i]}, @{$self->_outer->[$i]};
|
||||
|
||||
if (!$self->no_internal) {
|
||||
$self->_contours_ex->[$i] = diff_ex(
|
||||
$self->_outer->[$i],
|
||||
[ map $_->contour, @{$self->_inner->[$i]} ],
|
||||
);
|
||||
|
||||
# lines enclosed in inner expolygons are visible
|
||||
$add_expolygon->($_) for @{ $self->_inner->[$i] };
|
||||
|
||||
# lines enclosed in expolygons covering perimeters are visible
|
||||
# (but discouraged)
|
||||
$add_expolygon->($_, 1) for @{ $self->_contours_ex->[$i] };
|
||||
}
|
||||
}
|
||||
|
||||
my $intersects = sub {
|
||||
my ($polygon, $line) = @_;
|
||||
@{Boost::Geometry::Utils::polygon_linestring_intersection(
|
||||
$polygon->boost_polygon,
|
||||
Boost::Geometry::Utils::linestring($line),
|
||||
)} > 0;
|
||||
};
|
||||
|
||||
# lines connecting outer polygons are visible
|
||||
{
|
||||
my @outer = (map @$_, @{$self->_outer});
|
||||
for my $i (0 .. $#outer) {
|
||||
for my $j (($i+1) .. $#outer) {
|
||||
for my $m (0 .. $#{$outer[$i]}) {
|
||||
for my $n (0 .. $#{$outer[$j]}) {
|
||||
my $line = Slic3r::Line->new($outer[$i][$m], $outer[$j][$n]);
|
||||
if (!first { $intersects->($_, $line) } @outer) {
|
||||
# this line does not cross any polygon
|
||||
my $dist = $line->length;
|
||||
$edges->{$outer[$i][$m]}{$outer[$j][$n]} = $dist;
|
||||
$edges->{$outer[$j][$n]}{$outer[$i][$m]} = $dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# lines connecting inner polygons contours are visible but discouraged
|
||||
if (!$self->no_internal) {
|
||||
my @inner = (map $_->contour, map @$_, @{$self->_inner});
|
||||
for my $i (0 .. $#inner) {
|
||||
for my $j (($i+1) .. $#inner) {
|
||||
for my $m (0 .. $#{$inner[$i]}) {
|
||||
for my $n (0 .. $#{$inner[$j]}) {
|
||||
my $line = Slic3r::Line->new($inner[$i][$m], $inner[$j][$n]);
|
||||
if (!first { $intersects->($_, $line) } @inner) {
|
||||
# this line does not cross any polygon
|
||||
my $dist = $line->length * CROSSING_FACTOR;
|
||||
$edges->{$inner[$i][$m]}{$inner[$j][$n]} = $dist;
|
||||
$edges->{$inner[$j][$n]}{$inner[$i][$m]} = $dist;
|
||||
$crossing_edges->{$inner[$i][$m]}{$inner[$j][$n]} = 1;
|
||||
$crossing_edges->{$inner[$j][$n]}{$inner[$i][$m]} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->_pointmap({
|
||||
map +("$_" => $_),
|
||||
(map @$_, map @$_, map @$_, @{$self->_inner}),
|
||||
(map @$_, map @$_, @{$self->_outer}),
|
||||
(map @$_, map @$_, map @$_, @{$self->_contours_ex}),
|
||||
});
|
||||
|
||||
if (0) {
|
||||
my @lines = ();
|
||||
my %lines = ();
|
||||
for my $i (keys %{$self->_edges}) {
|
||||
for my $j (keys %{$self->_edges->{$i}}) {
|
||||
next if $lines{join '_', sort $i, $j};
|
||||
push @lines, [ map $self->_pointmap->{$_}, $i, $j ];
|
||||
$lines{join '_', sort $i, $j} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(undef, "space.svg",
|
||||
lines => \@lines,
|
||||
no_arrows => 1,
|
||||
polygons => [ map @$_, @{$self->islands} ],
|
||||
red_polygons => [ map $_->holes, map @$_, @{$self->_inner} ],
|
||||
white_polygons => [ map @$_, @{$self->_outer} ],
|
||||
);
|
||||
printf "%d islands\n", scalar @{$self->islands};
|
||||
}
|
||||
}
|
||||
|
||||
sub find_node {
|
||||
my $self = shift;
|
||||
my ($point, $near_to) = @_;
|
||||
|
||||
# for optimal pathing, we should check visibility from $point to all $candidates, and then
|
||||
# choose the one that is nearest to $near_to among the visible ones; however this is probably too slow
|
||||
|
||||
# if we're inside a hole, move to a point on hole;
|
||||
{
|
||||
my $polygon = first { $_->encloses_point($point) } (map $_->holes, map @$_, @{$self->_inner});
|
||||
return nearest_point($point, $polygon) if $polygon;
|
||||
}
|
||||
|
||||
# if we're inside an expolygon move to a point on contour or holes
|
||||
{
|
||||
my $expolygon = first { $_->encloses_point_quick($point) } (map @$_, @{$self->_inner});
|
||||
return nearest_point($point, [ map @$_, @$expolygon ]) if $expolygon;
|
||||
}
|
||||
|
||||
{
|
||||
my $outer_polygon_idx;
|
||||
if (!$self->no_internal) {
|
||||
# look for an outer expolygon whose contour contains our point
|
||||
$outer_polygon_idx = first { first { $_->contour->encloses_point($point) } @{$self->_contours_ex->[$_]} }
|
||||
0 .. $#{ $self->_contours_ex };
|
||||
} else {
|
||||
# # look for an outer expolygon containing our point
|
||||
$outer_polygon_idx = first { first { $_->encloses_point($point) } @{$self->_outer->[$_]} }
|
||||
0 .. $#{ $self->_outer };
|
||||
}
|
||||
my $candidates = defined $outer_polygon_idx
|
||||
? [ map @{$_->contour}, @{$self->_inner->[$outer_polygon_idx]} ]
|
||||
: [ map @$_, map @$_, @{$self->_outer} ];
|
||||
$candidates = [ map @$_, @{$self->_outer->[$outer_polygon_idx]} ]
|
||||
if @$candidates == 0;
|
||||
return nearest_point($point, $candidates);
|
||||
}
|
||||
}
|
||||
|
||||
sub shortest_path {
|
||||
my $self = shift;
|
||||
my ($from, $to) = @_;
|
||||
|
||||
# find nearest nodes
|
||||
my $new_from = $self->find_node($from, $to);
|
||||
my $new_to = $self->find_node($to, $from);
|
||||
|
||||
my $root = "$new_from";
|
||||
my $target = "$new_to";
|
||||
my $edges = $self->_edges;
|
||||
my %dist = map { $_ => INFINITY } keys %$edges;
|
||||
$dist{$root} = 0;
|
||||
my %prev = map { $_ => undef } keys %$edges;
|
||||
my @unsolved = keys %$edges;
|
||||
my %crossings = (); # node_idx => bool
|
||||
|
||||
while (@unsolved) {
|
||||
# sort unsolved by distance from root
|
||||
# using a sorting option that accounts for infinity
|
||||
@unsolved = sort {
|
||||
$dist{$a} eq INFINITY ? +1 :
|
||||
$dist{$b} eq INFINITY ? -1 :
|
||||
$dist{$a} <=> $dist{$b};
|
||||
} @unsolved;
|
||||
|
||||
# we'll solve the closest node
|
||||
last if $dist{$unsolved[0]} eq INFINITY;
|
||||
my $n = shift @unsolved;
|
||||
|
||||
# stop search
|
||||
last if $n eq $target;
|
||||
|
||||
# now, look at all the nodes connected to n
|
||||
foreach my $n2 (keys %{$edges->{$n}}) {
|
||||
# .. and find out if any of their estimated distances
|
||||
# can be improved if we go through n
|
||||
if ( ($dist{$n2} eq INFINITY) || ($dist{$n2} > ($dist{$n} + $edges->{$n}{$n2})) ) {
|
||||
$dist{$n2} = $dist{$n} + $edges->{$n}{$n2};
|
||||
$prev{$n2} = $n;
|
||||
$crossings{$n} = 1 if $self->_crossing_edges->{$n}{$n2};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @points = ();
|
||||
my $crossings = 0;
|
||||
{
|
||||
my $pointmap = $self->_pointmap;
|
||||
my $u = $target;
|
||||
while (defined $prev{$u}) {
|
||||
unshift @points, $pointmap->{$u};
|
||||
$crossings++ if $crossings{$u};
|
||||
$u = $prev{$u};
|
||||
}
|
||||
}
|
||||
$self->last_crossings($crossings);
|
||||
return Slic3r::Polyline->new($from, $new_from, @points, $to); # @points already includes $new_to
|
||||
}
|
||||
|
||||
1;
|
|
@ -392,6 +392,10 @@ sub build {
|
|||
title => 'Horizontal shells',
|
||||
options => [qw(solid_layers)],
|
||||
},
|
||||
{
|
||||
title => 'Advanced',
|
||||
options => [qw(avoid_crossing_perimeters)],
|
||||
},
|
||||
]);
|
||||
|
||||
$self->add_options_page('Infill', 'shading.png', optgroups => [
|
||||
|
|
|
@ -58,12 +58,14 @@ sub rotate {
|
|||
my $self = shift;
|
||||
my ($angle, $center) = @_;
|
||||
@$self = @{ +(Slic3r::Geometry::rotate_points($angle, $center, $self))[0] };
|
||||
$self;
|
||||
}
|
||||
|
||||
sub translate {
|
||||
my $self = shift;
|
||||
my ($x, $y) = @_;
|
||||
@$self = @{ +(Slic3r::Geometry::move_points([$x, $y], $self))[0] };
|
||||
$self;
|
||||
}
|
||||
|
||||
sub x { $_[0]->[0] }
|
||||
|
|
|
@ -14,6 +14,11 @@ sub lines {
|
|||
return polygon_lines($self);
|
||||
}
|
||||
|
||||
sub boost_polygon {
|
||||
my $self = shift;
|
||||
return Boost::Geometry::Utils::polygon($self);
|
||||
}
|
||||
|
||||
sub boost_linestring {
|
||||
my $self = shift;
|
||||
return Boost::Geometry::Utils::linestring([@$self, $self->[0]]);
|
||||
|
|
|
@ -613,9 +613,30 @@ sub write_gcode {
|
|||
$Slic3r::Config->print_center->[Y] - (unscale ($print_bb[Y2] - $print_bb[Y1]) / 2) - unscale $print_bb[Y1],
|
||||
);
|
||||
|
||||
# initialize a motion planner for object-to-object travel moves
|
||||
my $external_motionplanner;
|
||||
if ($Slic3r::Config->avoid_crossing_perimeters) {
|
||||
my $distance_from_objects = 1;
|
||||
# compute the offsetted convex hull for each object and repeat it for each copy.
|
||||
my @islands = ();
|
||||
foreach my $obj_idx (0 .. $#{$self->objects}) {
|
||||
my @island = Slic3r::ExPolygon->new(convex_hull([
|
||||
map @{$_->expolygon->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
|
||||
]))->translate(map -$_, @shift)->offset_ex(scale $distance_from_objects);
|
||||
foreach my $copy (@{$self->copies->[$obj_idx]}) {
|
||||
push @islands, map $_->clone->translate(@$copy), @island;
|
||||
}
|
||||
}
|
||||
$external_motionplanner = Slic3r::GCode::MotionPlanner->new(
|
||||
islands => union_ex([ map @$_, @islands ]),
|
||||
no_internal => 1,
|
||||
);
|
||||
}
|
||||
|
||||
# prepare the logic to print one layer
|
||||
my $skirt_done = 0; # count of skirt layers done
|
||||
my $brim_done = 0;
|
||||
my $last_obj_copy = "";
|
||||
my $extrude_layer = sub {
|
||||
my ($layer_id, $object_copies) = @_;
|
||||
my $gcode = "";
|
||||
|
@ -635,8 +656,7 @@ sub write_gcode {
|
|||
|
||||
# extrude skirt
|
||||
if ($skirt_done < $Slic3r::Config->skirt_height) {
|
||||
$gcodegen->shift_x($shift[X]);
|
||||
$gcodegen->shift_y($shift[Y]);
|
||||
$gcodegen->set_shift(@shift);
|
||||
$gcode .= $gcodegen->set_acceleration($Slic3r::Config->perimeter_acceleration);
|
||||
# skip skirt if we have a large brim
|
||||
if ($layer_id < $Slic3r::Config->skirt_height && ($layer_id != 0 || $Slic3r::Config->skirt_distance + (($Slic3r::Config->skirts - 1) * $Slic3r::flow->spacing) > $Slic3r::Config->brim_width)) {
|
||||
|
@ -647,8 +667,7 @@ sub write_gcode {
|
|||
|
||||
# extrude brim
|
||||
if ($layer_id == 0 && !$brim_done) {
|
||||
$gcodegen->shift_x($shift[X]);
|
||||
$gcodegen->shift_y($shift[Y]);
|
||||
$gcodegen->set_shift(@shift);
|
||||
$gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim};
|
||||
$brim_done = 1;
|
||||
}
|
||||
|
@ -661,8 +680,21 @@ sub write_gcode {
|
|||
# won't always trigger the automatic retraction
|
||||
$gcode .= $gcodegen->retract;
|
||||
|
||||
$gcodegen->shift_x($shift[X] + unscale $copy->[X]);
|
||||
$gcodegen->shift_y($shift[Y] + unscale $copy->[Y]);
|
||||
# travel to the first perimeter point using the external motion planner
|
||||
if ($external_motionplanner && @{ $layer->perimeters } && !$gcodegen->straight_once && $last_obj_copy ne "${obj_idx}_${copy}") {
|
||||
$gcodegen->set_shift(@shift);
|
||||
my $layer_mp = $gcodegen->motionplanner;
|
||||
$gcodegen->motionplanner($external_motionplanner);
|
||||
my $first_perimeter = $layer->perimeters->[0]->unpack;
|
||||
my $target = $first_perimeter->polygon->[0]->clone->translate(@$copy);
|
||||
$gcode .= $gcodegen->travel_to($target, "move to first perimeter point");
|
||||
$gcodegen->motionplanner($layer_mp);
|
||||
}
|
||||
|
||||
$gcodegen->set_shift(
|
||||
$shift[X] + unscale $copy->[X],
|
||||
$shift[Y] + unscale $copy->[Y],
|
||||
);
|
||||
|
||||
# extrude perimeters
|
||||
$gcode .= $gcodegen->set_tool($Slic3r::Config->perimeter_extruder-1);
|
||||
|
@ -686,6 +718,8 @@ sub write_gcode {
|
|||
$gcode .= $gcodegen->extrude_path($_, 'support material')
|
||||
for $layer->support_fills->shortest_path($gcodegen->last_pos);
|
||||
}
|
||||
|
||||
$last_obj_copy = "${obj_idx}_${copy}";
|
||||
}
|
||||
return if !$gcode;
|
||||
|
||||
|
|
|
@ -213,6 +213,7 @@ $j
|
|||
--layer-gcode Load layer-change G-code from the supplied file (default: nothing).
|
||||
--extra-perimeters Add more perimeters when needed (default: yes)
|
||||
--randomize-start Randomize starting point across layers (default: yes)
|
||||
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
|
||||
|
||||
Support material options:
|
||||
--support-material Generate support material for overhangs
|
||||
|
|
Loading…
Reference in a new issue