Removed a broken Arc Fitting feature.

Removed the questionable Pressure Advance feature. It is better to use the Pressure Advance implemented into a firmware.
Added a C++ implementation of GCodeReader and SpiralVase, thanks to @alexrj
Added a C++ implementation of GCodeTimeEstimator, thanks to @lordofhyphens
This commit is contained in:
bubnikv 2017-04-26 14:24:31 +02:00
parent e918ea9c65
commit 72ae3585e4
28 changed files with 459 additions and 1001 deletions

View file

@ -12,7 +12,7 @@ use List::Util qw(first max);
our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration
adjust_overhang_flow standby_temperature scale rotate duplicate duplicate_grid
rotate scale duplicate_grid start_perimeters_at_concave_points start_perimeters_at_non_overhang
randomize_start seal_position bed_size print_center g0 vibration_limit);
randomize_start seal_position bed_size print_center g0 vibration_limit gcode_arcs pressure_advance);
# C++ Slic3r::PrintConfigDef exported as a Perl hash of hashes.
# The C++ counterpart is a constant singleton.
@ -328,7 +328,7 @@ sub validate {
my $max_nozzle_diameter = max(@{ $self->nozzle_diameter });
die "Invalid extrusion width (too large)\n"
if defined first { $_ > 10 * $max_nozzle_diameter }
map $self->get_abs_value_over("${_}_extrusion_width", $self->layer_height),
map $self->get_abs_value_over("${_}_extrusion_width", $max_nozzle_diameter),
qw(perimeter infill solid_infill top_infill support_material first_layer);
}

View file

@ -1,242 +0,0 @@
package Slic3r::GCode::ArcFitting;
use Moo;
use Slic3r::Geometry qw(X Y PI scale unscale epsilon scaled_epsilon deg2rad angle3points);
extends 'Slic3r::GCode::Reader';
has 'config' => (is => 'ro', required => 0);
has 'min_segments' => (is => 'rw', default => sub { 2 });
has 'min_total_angle' => (is => 'rw', default => sub { deg2rad(30) });
has 'max_relative_angle' => (is => 'rw', default => sub { deg2rad(15) });
has 'len_epsilon' => (is => 'rw', default => sub { scale 0.2 });
has 'angle_epsilon' => (is => 'rw', default => sub { abs(deg2rad(10)) });
has '_extrusion_axis' => (is => 'lazy');
has '_path' => (is => 'rw');
has '_cur_F' => (is => 'rw');
has '_cur_E' => (is => 'rw');
has '_cur_E0' => (is => 'rw');
has '_comment' => (is => 'rw');
sub _build__extrusion_axis {
my ($self) = @_;
return $self->config ? $self->config->get_extrusion_axis : 'E';
}
sub process {
my $self = shift;
my ($gcode) = @_;
die "Arc fitting is not available (incomplete feature)\n";
die "Arc fitting doesn't support extrusion axis not being E\n" if $self->_extrusion_axis ne 'E';
my $new_gcode = "";
$self->parse($gcode, sub {
my ($reader, $cmd, $args, $info) = @_;
if ($info->{extruding} && $info->{dist_XY} > 0) {
# this is an extrusion segment
# get segment
my $line = Slic3r::Line->new(
Slic3r::Point->new_scale($self->X, $self->Y),
Slic3r::Point->new_scale($args->{X}, $args->{Y}),
);
# get segment speed
my $F = $args->{F} // $reader->F;
# get extrusion per unscaled distance unit
my $e = $info->{dist_E} / unscale($line->length);
if ($self->_path && $F == $self->_cur_F && abs($e - $self->_cur_E) < epsilon) {
# if speed and extrusion per unit are the same as the previous segments,
# append this segment to path
$self->_path->append($line->b);
} elsif ($self->_path) {
# segment can't be appended to previous path, so we flush the previous one
# and start over
$new_gcode .= $self->path_to_gcode;
$self->_path(undef);
}
if (!$self->_path) {
# if this is the first segment of a path, start it from scratch
$self->_path(Slic3r::Polyline->new(@$line));
$self->_cur_F($F);
$self->_cur_E($e);
$self->_cur_E0($self->E);
$self->_comment($info->{comment});
}
} else {
# if we have a path, we flush it and go on
$new_gcode .= $self->path_to_gcode if $self->_path;
$new_gcode .= $info->{raw} . "\n";
$self->_path(undef);
}
});
$new_gcode .= $self->path_to_gcode if $self->_path;
return $new_gcode;
}
sub path_to_gcode {
my ($self) = @_;
my @chunks = $self->detect_arcs($self->_path);
my $gcode = "";
my $E = $self->_cur_E0;
foreach my $chunk (@chunks) {
if ($chunk->isa('Slic3r::Polyline')) {
my @lines = @{$chunk->lines};
$gcode .= sprintf "G1 F%s\n", $self->_cur_F;
foreach my $line (@lines) {
$E += $self->_cur_E * unscale($line->length);
$gcode .= sprintf "G1 X%.3f Y%.3f %s%.5f",
(map unscale($_), @{$line->b}),
$self->_extrusion_axis, $E;
$gcode .= sprintf " ; %s", $self->_comment if $self->_comment;
$gcode .= "\n";
}
} elsif ($chunk->isa('Slic3r::GCode::ArcFitting::Arc')) {
$gcode .= !$chunk->is_ccw ? "G2" : "G3";
$gcode .= sprintf " X%.3f Y%.3f", map unscale($_), @{$chunk->end}; # destination point
# XY distance of the center from the start position
$gcode .= sprintf " I%.3f", unscale($chunk->center->[X] - $chunk->start->[X]);
$gcode .= sprintf " J%.3f", unscale($chunk->center->[Y] - $chunk->start->[Y]);
$E += $self->_cur_E * unscale($chunk->length);
$gcode .= sprintf " %s%.5f", $self->_extrusion_axis, $E;
$gcode .= sprintf " F%s\n", $self->_cur_F;
}
}
return $gcode;
}
sub detect_arcs {
my ($self, $path) = @_;
my @chunks = ();
my @arc_points = ();
my $polyline = undef;
my $arc_start = undef;
my @points = @$path;
for (my $i = 1; $i <= $#points; ++$i) {
my $end = undef;
# we need at least three points to check whether they form an arc
if ($i < $#points) {
my $len = $points[$i-1]->distance_to($points[$i]);
my $rel_angle = PI - angle3points(@points[$i, $i-1, $i+1]);
if (abs($rel_angle) <= $self->max_relative_angle) {
for (my $j = $i+1; $j <= $#points; ++$j) {
# check whether @points[($i-1)..$j] form an arc
last if abs($points[$j-1]->distance_to($points[$j]) - $len) > $self->len_epsilon;
last if abs(PI - angle3points(@points[$j-1, $j-2, $j]) - $rel_angle) > $self->angle_epsilon;
$end = $j;
}
}
}
if (defined $end && ($end - $i + 1) >= $self->min_segments) {
my $arc = polyline_to_arc(Slic3r::Polyline->new(@points[($i-1)..$end]));
if (1||$arc->angle >= $self->min_total_angle) {
push @chunks, $arc;
# continue scanning after arc points
$i = $end;
next;
}
}
# if last chunk was a polyline, append to it
if (@chunks && $chunks[-1]->isa('Slic3r::Polyline')) {
$chunks[-1]->append($points[$i]);
} else {
push @chunks, Slic3r::Polyline->new(@points[($i-1)..$i]);
}
}
return @chunks;
}
sub polyline_to_arc {
my ($polyline) = @_;
my @points = @$polyline;
my $is_ccw = $points[2]->ccw(@points[0,1]) > 0;
# to find the center, we intersect the perpendicular lines
# passing by first and last vertex;
# a better method would be to draw all the perpendicular lines
# and find the centroid of the enclosed polygon, or to
# intersect multiple lines and find the centroid of the convex hull
# around the intersections
my $arc_center;
{
my $first_ray = Slic3r::Line->new(@points[0,1]);
$first_ray->rotate(PI/2 * ($is_ccw ? 1 : -1), $points[0]);
my $last_ray = Slic3r::Line->new(@points[-2,-1]);
$last_ray->rotate(PI/2 * ($is_ccw ? -1 : 1), $points[-1]);
# require non-parallel rays in order to compute an accurate center
return if abs($first_ray->atan2_ - $last_ray->atan2_) < deg2rad(30);
$arc_center = $first_ray->intersection($last_ray, 0) or return;
}
# angle measured in ccw orientation
my $abs_angle = Slic3r::Geometry::angle3points($arc_center, @points[0,-1]);
my $rel_angle = $is_ccw
? $abs_angle
: (2*PI - $abs_angle);
my $arc = Slic3r::GCode::ArcFitting::Arc->new(
start => $points[0]->clone,
end => $points[-1]->clone,
center => $arc_center,
is_ccw => $is_ccw || 0,
angle => $rel_angle,
);
if (0) {
printf "points = %d, path length = %f, arc angle = %f, arc length = %f\n",
scalar(@points),
unscale(Slic3r::Polyline->new(@points)->length),
Slic3r::Geometry::rad2deg($rel_angle),
unscale($arc->length);
}
return $arc;
}
package Slic3r::GCode::ArcFitting::Arc;
use Moo;
has 'start' => (is => 'ro', required => 1);
has 'end' => (is => 'ro', required => 1);
has 'center' => (is => 'ro', required => 1);
has 'is_ccw' => (is => 'ro', required => 1);
has 'angle' => (is => 'ro', required => 1);
sub radius {
my ($self) = @_;
return $self->start->distance_to($self->center);
}
sub length {
my ($self) = @_;
return $self->radius * $self->angle;
}
1;

View file

@ -1,317 +0,0 @@
package Slic3r::GCode::MotionPlanner;
use Moo;
has 'islands' => (is => 'ro', required => 1); # arrayref of ExPolygons
has 'internal' => (is => 'ro', default => sub { 1 });
has '_space' => (is => 'ro', default => sub { Slic3r::GCode::MotionPlanner::ConfigurationSpace->new });
has '_inner' => (is => 'ro', default => sub { [] }); # arrayref of ExPolygons
use List::Util qw(first max);
use Slic3r::Geometry qw(A B scale epsilon);
use Slic3r::Geometry::Clipper qw(offset offset_ex diff_ex intersection_pl);
# clearance (in mm) from the perimeters
has '_inner_margin' => (is => 'ro', default => sub { scale 1 });
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_PENALTY => 20;
use constant POINT_DISTANCE => 10; # unscaled
# setup our configuration space
sub BUILD {
my $self = shift;
my $point_distance = scale POINT_DISTANCE;
my $nodes = $self->_space->nodes;
my $edges = $self->_space->edges;
# process individual islands
for my $i (0 .. $#{$self->islands}) {
my $expolygon = $self->islands->[$i];
# find external margin
my $outer = offset([ @$expolygon ], +$self->_outer_margin);
my @outer_points = map @{$_->equally_spaced_points($point_distance)}, @$outer;
# add outer points to graph
my $o_outer = $self->_space->add_nodes(@outer_points);
# find pairs of visible outer points and add them to the graph
for my $i (0 .. $#outer_points) {
for my $j (($i+1) .. $#outer_points) {
my ($a, $b) = ($outer_points[$i], $outer_points[$j]);
my $line = Slic3r::Polyline->new($a, $b);
# outer points are visible when their line has empty intersection with islands
my $intersection = intersection_pl(
[ $line ],
[ map @$_, @{$self->islands} ],
);
if (!@$intersection) {
$self->_space->add_edge($i+$o_outer, $j+$o_outer, $line->length);
}
}
}
if ($self->internal) {
# find internal margin
my $inner = offset_ex([ @$expolygon ], -$self->_inner_margin);
push @{ $self->_inner }, @$inner;
my @inner_points = map @{$_->equally_spaced_points($point_distance)}, map @$_, @$inner;
# add points to graph and get their offset
my $o_inner = $self->_space->add_nodes(@inner_points);
# find pairs of visible inner points and add them to the graph
for my $i (0 .. $#inner_points) {
for my $j (($i+1) .. $#inner_points) {
my ($a, $b) = ($inner_points[$i], $inner_points[$j]);
my $line = Slic3r::Line->new($a, $b);
# turn $inner into an ExPolygonCollection and use $inner->contains_line()
if (first { $_->contains_line($line) } @$inner) {
$self->_space->add_edge($i+$o_inner, $j+$o_inner, $line->length);
}
}
}
# generate the stripe around slice contours
my $contour = diff_ex(
$outer,
[ map @$_, @$inner ],
);
# find pairs of visible points in this area and add them to the graph
for my $i (0 .. $#inner_points) {
for my $j (0 .. $#outer_points) {
my ($a, $b) = ($inner_points[$i], $outer_points[$j]);
my $line = Slic3r::Line->new($a, $b);
# turn $contour into an ExPolygonCollection and use $contour->contains_line()
if (first { $_->contains_line($line) } @$contour) {
$self->_space->add_edge($i+$o_inner, $j+$o_outer, $line->length * CROSSING_PENALTY);
}
}
}
}
}
# since Perl has no infinity symbol and we don't want to overcomplicate
# the Dijkstra algorithm with string constants or -1 values
$self->_space->_infinity(10 * (max(map values %$_, values %{$self->_space->edges}) // 0));
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("space.svg",
no_arrows => 1,
expolygons => $self->islands,
lines => $self->_space->get_lines,
points => $self->_space->nodes,
);
printf "%d islands\n", scalar @{$self->islands};
eval "use Devel::Size";
print "MEMORY USAGE:\n";
printf " %-19s = %.1fMb\n", $_, Devel::Size::total_size($self->$_)/1024/1024
for qw(_space islands);
printf " %-19s = %.1fMb\n", $_, Devel::Size::total_size($self->_space->$_)/1024/1024
for qw(nodes edges);
printf " %-19s = %.1fMb\n", 'self', Devel::Size::total_size($self)/1024/1024;
exit if $self->internal;
}
}
sub shortest_path {
my $self = shift;
my ($from, $to) = @_;
return Slic3r::Polyline->new($from, $to)
if !@{$self->_space->nodes};
# create a temporary configuration space
my $space = $self->_space->clone;
# add from/to points to the temporary configuration space
my $node_from = $self->_add_point_to_space($from, $space);
my $node_to = $self->_add_point_to_space($to, $space);
# compute shortest path
my $path = $space->shortest_path($node_from, $node_to);
if (!$path->is_valid) {
Slic3r::debugf "Failed to compute shortest path.\n";
return Slic3r::Polyline->new($from, $to);
}
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("path.svg",
no_arrows => 1,
expolygons => $self->islands,
lines => $space->get_lines,
red_points => [$from, $to],
red_polylines => [$path],
);
exit;
}
return $path;
}
# returns the index of the new node
sub _add_point_to_space {
my ($self, $point, $space) = @_;
my $n = $space->add_nodes($point);
# check whether we are inside an island or outside
my $inside = defined first { $self->islands->[$_]->contains_point($point) } 0..$#{$self->islands};
# find candidates by checking visibility from $from to them
foreach my $idx (0..$#{$space->nodes}) {
my $line = Slic3r::Line->new($point, $space->nodes->[$idx]);
# if $point is inside an island, it is visible from $idx when island contains their line
# if $point is outside an island, it is visible from $idx when their line does not cross any island
if (
($inside && defined first { $_->contains_line($line) } @{$self->_inner})
|| (!$inside && !@{intersection_pl(
[ $line->as_polyline ],
[ map @$_, @{$self->islands} ],
)})
) {
# $n ($point) and $idx are visible
$space->add_edge($n, $idx, $line->length);
}
}
# if we found no visibility, retry with larger margins
if (!exists $space->edges->{$n} && $inside) {
foreach my $idx (0..$#{$space->nodes}) {
my $line = Slic3r::Line->new($point, $space->nodes->[$idx]);
if (defined first { $_->contains_line($line) } @{$self->islands}) {
# $n ($point) and $idx are visible
$space->add_edge($n, $idx, $line->length);
}
}
}
warn "Temporary node is not visible from any other node"
if !exists $space->edges->{$n};
return $n;
}
package Slic3r::GCode::MotionPlanner::ConfigurationSpace;
use Moo;
has 'nodes' => (is => 'rw', default => sub { [] }); # [ Point, ... ]
has 'edges' => (is => 'rw', default => sub { {} }); # node_idx => { node_idx => distance, ... }
has '_infinity' => (is => 'rw');
sub clone {
my $self = shift;
return (ref $self)->new(
nodes => [ map $_->clone, @{$self->nodes} ],
edges => { map { $_ => { %{$self->edges->{$_}} } } keys %{$self->edges} },
_infinity => $self->_infinity,
);
}
sub nodes_count {
my $self = shift;
return scalar(@{ $self->nodes });
}
sub add_nodes {
my ($self, @nodes) = @_;
my $offset = $self->nodes_count;
push @{ $self->nodes }, @nodes;
return $offset;
}
sub add_edge {
my ($self, $a, $b, $dist) = @_;
$self->edges->{$a}{$b} = $self->edges->{$b}{$a} = $dist;
}
sub shortest_path {
my ($self, $node_from, $node_to) = @_;
my $edges = $self->edges;
my (%dist, %visited, %prev);
$dist{$_} = $self->_infinity for keys %$edges;
$dist{$node_from} = 0;
my @queue = ($node_from);
while (@queue) {
my $u = -1;
{
# find node in @queue with smallest distance in %dist and has not been visited
my $d = -1;
foreach my $n (@queue) {
next if $visited{$n};
if ($u == -1 || $dist{$n} < $d) {
$u = $n;
$d = $dist{$n};
}
}
}
last if $u == $node_to;
# remove $u from @queue
@queue = grep $_ != $u, @queue;
$visited{$u} = 1;
# loop through neighbors of $u
foreach my $v (keys %{ $edges->{$u} }) {
my $alt = $dist{$u} + $edges->{$u}{$v};
if ($alt < $dist{$v}) {
$dist{$v} = $alt;
$prev{$v} = $u;
if (!$visited{$v}) {
push @queue, $v;
}
}
}
}
my @points = ();
{
my $u = $node_to;
while (exists $prev{$u}) {
unshift @points, $self->nodes->[$u];
$u = $prev{$u};
}
unshift @points, $self->nodes->[$node_from];
}
return Slic3r::Polyline->new(@points);
}
# for debugging purposes
sub get_lines {
my $self = shift;
my @lines = ();
my %lines = ();
for my $i (keys %{$self->edges}) {
for my $j (keys %{$self->edges->{$i}}) {
my $line_id = join '_', sort $i, $j;
next if $lines{$line_id};
$lines{$line_id} = 1;
push @lines, Slic3r::Line->new(map $self->nodes->[$_], $i, $j);
}
}
return [@lines];
}
1;

View file

@ -1,100 +0,0 @@
# A pure perl (no C++ implementation) G-code filter, to control the pressure inside the nozzle.
package Slic3r::GCode::PressureRegulator;
use Moo;
has 'config' => (is => 'ro', required => 1);
has 'enable' => (is => 'rw', default => sub { 0 });
has 'reader' => (is => 'ro', default => sub { Slic3r::GCode::Reader->new });
has '_extrusion_axis' => (is => 'rw', default => sub { "E" });
has '_tool' => (is => 'rw', default => sub { 0 });
has '_last_print_F' => (is => 'rw', default => sub { 0 });
has '_advance' => (is => 'rw', default => sub { 0 }); # extra E injected
use Slic3r::Geometry qw(epsilon);
# Acknowledgements:
# The advance algorithm was proposed by Matthew Roberts.
# The initial work on this Slic3r feature was done by Luís Andrade (lluis)
sub BUILD {
my ($self) = @_;
$self->reader->apply_print_config($self->config);
$self->_extrusion_axis($self->config->get_extrusion_axis);
}
sub process {
my $self = shift;
my ($gcode, $flush) = @_;
my $new_gcode = "";
$self->reader->parse($gcode, sub {
my ($reader, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
$self->_tool($1);
} elsif ($info->{extruding} && $info->{dist_XY} > 0) {
# This is a print move.
my $F = $args->{F} // $reader->F;
if ($F != $self->_last_print_F || ($F == $self->_last_print_F && $self->_advance == 0)) {
# We are setting a (potentially) new speed or a discharge event happend since the last speed change, so we calculate the new advance amount.
# First calculate relative flow rate (mm of filament over mm of travel)
my $rel_flow_rate = $info->{dist_E} / $info->{dist_XY};
# Then calculate absolute flow rate (mm/sec of feedstock)
my $flow_rate = $rel_flow_rate * $F / 60;
# And finally calculate advance by using the user-configured K factor.
my $new_advance = $self->config->pressure_advance * ($flow_rate**2);
if (abs($new_advance - $self->_advance) > 1E-5) {
my $new_E = ($self->config->use_relative_e_distances ? 0 : $reader->E) + ($new_advance - $self->_advance);
$new_gcode .= sprintf "G1 %s%.5f F%.3f ; pressure advance\n",
$self->_extrusion_axis, $new_E, $self->_unretract_speed;
$new_gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $reader->E
if !$self->config->use_relative_e_distances;
$new_gcode .= sprintf "G1 F%.3f ; restore F\n", $F;
$self->_advance($new_advance);
}
$self->_last_print_F($F);
}
} elsif (($info->{retracting} || $cmd eq 'G10') && $self->_advance != 0) {
# We need to bring pressure to zero when retracting.
$new_gcode .= $self->_discharge($args->{F}, $args->{F} // $reader->F);
}
$new_gcode .= "$info->{raw}\n";
});
if ($flush) {
$new_gcode .= $self->_discharge;
}
return $new_gcode;
}
sub _discharge {
my ($self, $F, $oldSpeed) = @_;
my $new_E = ($self->config->use_relative_e_distances ? 0 : $self->reader->E) - $self->_advance;
my $gcode = sprintf "G1 %s%.5f F%.3f ; pressure discharge\n",
$self->_extrusion_axis, $new_E, $F // $self->_unretract_speed;
$gcode .= sprintf "G92 %s%.5f ; restore E\n", $self->_extrusion_axis, $self->reader->E
if !$self->config->use_relative_e_distances;
$gcode .= sprintf "G1 F%.3f ; restore F\n", $oldSpeed
if $oldSpeed;
$self->_advance(0);
return $gcode;
}
sub _unretract_speed {
my ($self) = @_;
return $self->config->get_at('retract_speed', $self->_tool) * 60;
}
1;

View file

@ -1,3 +1,6 @@
# Helper module to parse and interpret a G-code file,
# invoking a callback for each move extracted from the G-code.
# Currently used by the automatic tests only.
package Slic3r::GCode::Reader;
use Moo;

View file

@ -1,86 +0,0 @@
package Slic3r::GCode::SpiralVase;
use Moo;
has 'config' => (is => 'ro', required => 1);
has 'enable' => (is => 'rw', default => sub { 0 });
has 'reader' => (is => 'ro', default => sub { Slic3r::GCode::Reader->new });
use Slic3r::Geometry qw(unscale);
sub BUILD {
my ($self) = @_;
$self->reader->apply_print_config($self->config);
}
sub process_layer {
my $self = shift;
my ($gcode) = @_;
# This post-processor relies on several assumptions:
# - all layers are processed through it, including those that are not supposed
# to be transformed, in order to update the reader with the XY positions
# - each call to this method includes a full layer, with a single Z move
# at the beginning
# - each layer is composed by suitable geometry (i.e. a single complete loop)
# - loops were not clipped before calling this method
# if we're not going to modify G-code, just feed it to the reader
# in order to update positions
if (!$self->enable) {
$self->reader->parse($gcode, sub {});
return $gcode;
}
# get total XY length for this layer by summing all extrusion moves
my $total_layer_length = 0;
my $layer_height = 0;
my $z = undef;
$self->reader->clone->parse($gcode, sub {
my ($reader, $cmd, $args, $info) = @_;
if ($cmd eq 'G1') {
if ($info->{extruding}) {
$total_layer_length += $info->{dist_XY};
} elsif (exists $args->{Z}) {
$layer_height += $info->{dist_Z};
$z //= $args->{Z};
}
}
});
#use XXX; XXX [ $gcode, $layer_height, $z, $total_layer_length ];
# remove layer height from initial Z
$z -= $layer_height;
my $new_gcode = "";
$self->reader->parse($gcode, sub {
my ($reader, $cmd, $args, $info) = @_;
if ($cmd eq 'G1' && exists $args->{Z}) {
# if this is the initial Z move of the layer, replace it with a
# (redundant) move to the last Z of previous layer
my $line = $info->{raw};
$line =~ s/ Z[.0-9]+/ Z$z/;
$new_gcode .= "$line\n";
} elsif ($cmd eq 'G1' && !exists($args->{Z}) && $info->{dist_XY}) {
# horizontal move
my $line = $info->{raw};
if ($info->{extruding}) {
$z += $info->{dist_XY} * $layer_height / $total_layer_length;
$line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e;
$new_gcode .= "$line\n";
}
# skip travel moves: the move to first perimeter point will
# cause a visible seam when loops are not aligned in XY; by skipping
# it we blend the first loop move in the XY plane (although the smoothness
# of such blend depend on how long the first segment is; maybe we should
# enforce some minimum length?)
} else {
$new_gcode .= "$info->{raw}\n";
}
});
return $new_gcode;
}
1;

View file

@ -1135,7 +1135,7 @@ sub build {
gcode_flavor use_relative_e_distances
serial_port serial_speed
octoprint_host octoprint_apikey
use_firmware_retraction pressure_advance
use_firmware_retraction
use_volumetric_e variable_layer_height
start_gcode end_gcode before_layer_gcode layer_gcode toolchange_gcode
nozzle_diameter extruder_offset
@ -1340,7 +1340,6 @@ sub build {
$optgroup->append_single_option_line('use_relative_e_distances');
$optgroup->append_single_option_line('use_firmware_retraction');
$optgroup->append_single_option_line('use_volumetric_e');
$optgroup->append_single_option_line('pressure_advance');
$optgroup->append_single_option_line('variable_layer_height');
}
}

View file

@ -7,11 +7,10 @@ has 'fh' => (is => 'ro', required => 1);
has '_gcodegen' => (is => 'rw');
has '_cooling_buffer' => (is => 'rw');
has '_spiral_vase' => (is => 'rw');
has '_arc_fitting' => (is => 'rw');
has '_pressure_regulator' => (is => 'rw');
has '_pressure_equalizer' => (is => 'rw');
has '_skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1
has '_brim_done' => (is => 'rw');
# Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
has '_second_layer_things_done' => (is => 'rw');
has '_last_obj_copy' => (is => 'rw');
@ -101,15 +100,9 @@ sub BUILD {
$self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new($self->_gcodegen));
$self->_spiral_vase(Slic3r::GCode::SpiralVase->new(config => $self->config))
$self->_spiral_vase(Slic3r::GCode::SpiralVase->new($self->config))
if $self->config->spiral_vase;
$self->_arc_fitting(Slic3r::GCode::ArcFitting->new(config => $self->config))
if $self->config->gcode_arcs;
$self->_pressure_regulator(Slic3r::GCode::PressureRegulator->new(config => $self->config))
if $self->config->pressure_advance > 0;
$self->_pressure_equalizer(Slic3r::GCode::PressureEqualizer->new($self->config))
if ($self->config->max_volumetric_extrusion_rate_slope_positive > 0 ||
$self->config->max_volumetric_extrusion_rate_slope_negative > 0);
@ -168,9 +161,9 @@ sub export {
}
# set extruder(s) temperature before and after start G-code
$self->_print_first_layer_temperature(0);
$self->_print_first_layer_extruder_temperatures(0);
printf $fh "%s\n", $gcodegen->placeholder_parser->process($self->config->start_gcode);
$self->_print_first_layer_temperature(1);
$self->_print_first_layer_extruder_temperatures(1);
# set other general things
print $fh $gcodegen->preamble;
@ -269,12 +262,15 @@ sub export {
if ($layer->id == 0 && $finished_objects > 0) {
printf $fh $gcodegen->writer->set_bed_temperature($self->config->first_layer_bed_temperature),
if $self->config->first_layer_bed_temperature;
$self->_print_first_layer_temperature(0);
# Set first layer extruder
$self->_print_first_layer_extruder_temperatures(0);
}
$self->process_layer($layer, [$copy]);
}
$self->flush_filters;
$finished_objects++;
# Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
# Reset it when starting another object from 1st layer.
$self->_second_layer_things_done(0);
}
}
@ -355,9 +351,13 @@ sub export {
}
}
sub _print_first_layer_temperature {
# Write 1st layer extruder temperatures into the G-code.
# Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
# FIXME this does not work correctly for multi-extruder, single heater configuration as it emits multiple preheat commands for the same heater.
# M104 - Set Extruder Temperature
# M109 - Set Extruder Temperature and Wait
sub _print_first_layer_extruder_temperatures {
my ($self, $wait) = @_;
return if $self->config->start_gcode =~ /M(?:109|104)/i;
for my $t (@{$self->print->extruders}) {
my $temp = $self->config->get_at('first_layer_temperature', $t);
@ -381,7 +381,7 @@ sub process_layer {
# check whether we're going to apply spiralvase logic
if (defined $self->_spiral_vase) {
$self->_spiral_vase->enable(
$self->_spiral_vase->set_enable(
($layer->id > 0 || $self->print->config->brim_width == 0)
&& ($layer->id >= $self->print->config->skirt_height && !$self->print->has_infinite_skirt)
&& !defined(first { $_->region->config->bottom_solid_layers > $layer->id } @{$layer->regions})
@ -394,6 +394,8 @@ sub process_layer {
$self->_gcodegen->set_enable_loop_clipping(!defined $self->_spiral_vase || !$self->_spiral_vase->enable);
if (!$self->_second_layer_things_done && $layer->id == 1) {
# Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent
# first_layer_temperature vs. temperature settings.
for my $extruder (@{$self->_gcodegen->writer->extruders}) {
my $temperature = $self->config->get_at('temperature', $extruder->id);
$gcode .= $self->_gcodegen->writer->set_temperature($temperature, 0, $extruder->id)
@ -401,6 +403,7 @@ sub process_layer {
}
$gcode .= $self->_gcodegen->writer->set_bed_temperature($self->print->config->bed_temperature)
if $self->print->config->bed_temperature && $self->print->config->bed_temperature != $self->print->config->first_layer_bed_temperature;
# Mark the temperature transition from 1st to 2nd layer to be finished.
$self->_second_layer_things_done(1);
}
@ -697,22 +700,12 @@ sub filter {
my ($self, $gcode, $flush) = @_;
$flush //= 0;
# apply pressure regulation if enabled;
# this depends on actual speeds
$gcode = $self->_pressure_regulator->process($gcode, $flush)
if defined $self->_pressure_regulator;
# apply pressure equalization if enabled;
# print "G-code before filter:\n", $gcode;
$gcode = $self->_pressure_equalizer->process($gcode, $flush)
if defined $self->_pressure_equalizer;
# print "G-code after filter:\n", $gcode;
# apply arc fitting if enabled;
# this does not depend on speeds but changes G1 XY commands into G2/G2 IJ
$gcode = $self->_arc_fitting->process($gcode)
if defined $self->_arc_fitting;
return $gcode;
}