diff --git a/README.md b/README.md index 5264598ee..5ca5703e8 100644 --- a/README.md +++ b/README.md @@ -141,13 +141,7 @@ The author of the Silk icon set is Mark James. --use-relative-e-distances Enable this to get relative E values (default: no) --use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no) --use-volumetric-e Express E in cubic millimeters and prepend M200 (default: no) - --gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported - by all firmwares) --gcode-comments Make G-code verbose by adding comments (default: no) - --vibration-limit Limit the frequency of moves on X and Y axes (Hz, set zero to disable; - default: 0) - --pressure-advance Adjust pressure using the experimental advance algorithm (K constant, - set zero to disable; default: 0) Filament options: --filament-diameter Diameter in mm of your raw filament (default: 3) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 198a52825..7ffeb0e66 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -61,11 +61,7 @@ use Slic3r::ExPolygon; use Slic3r::ExtrusionLoop; use Slic3r::ExtrusionPath; use Slic3r::Flow; -use Slic3r::GCode::ArcFitting; -use Slic3r::GCode::MotionPlanner; -use Slic3r::GCode::PressureRegulator; use Slic3r::GCode::Reader; -use Slic3r::GCode::SpiralVase; use Slic3r::Geometry qw(PI); use Slic3r::Geometry::Clipper; use Slic3r::Layer; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 53c0ceba5..c2781d2f1 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -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); } diff --git a/lib/Slic3r/GCode/ArcFitting.pm b/lib/Slic3r/GCode/ArcFitting.pm deleted file mode 100644 index 8faa399b7..000000000 --- a/lib/Slic3r/GCode/ArcFitting.pm +++ /dev/null @@ -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; diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm deleted file mode 100644 index 823e6641d..000000000 --- a/lib/Slic3r/GCode/MotionPlanner.pm +++ /dev/null @@ -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; diff --git a/lib/Slic3r/GCode/PressureRegulator.pm b/lib/Slic3r/GCode/PressureRegulator.pm deleted file mode 100644 index 19c10a62f..000000000 --- a/lib/Slic3r/GCode/PressureRegulator.pm +++ /dev/null @@ -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; diff --git a/lib/Slic3r/GCode/Reader.pm b/lib/Slic3r/GCode/Reader.pm index 5763b3848..6f95f5eb0 100644 --- a/lib/Slic3r/GCode/Reader.pm +++ b/lib/Slic3r/GCode/Reader.pm @@ -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; diff --git a/lib/Slic3r/GCode/SpiralVase.pm b/lib/Slic3r/GCode/SpiralVase.pm deleted file mode 100644 index 837e33d16..000000000 --- a/lib/Slic3r/GCode/SpiralVase.pm +++ /dev/null @@ -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; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 1044181be..fd6db27ff 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -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'); } } diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index 7d9e33783..96628de36 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -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; } diff --git a/slic3r.pl b/slic3r.pl index 0cd1ab80d..9caffce8a 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -307,8 +307,6 @@ $j --gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported by all firmwares) --gcode-comments Make G-code verbose by adding comments (default: no) - --pressure-advance Adjust pressure using the experimental advance algorithm (K constant, - set zero to disable; default: $config->{pressure_advance}) Filament options: --filament-diameter Diameter in mm of your raw filament (default: $config->{filament_diameter}->[0]) diff --git a/t/arcs.t b/t/arcs.t deleted file mode 100644 index 65b83de43..000000000 --- a/t/arcs.t +++ /dev/null @@ -1,122 +0,0 @@ -use Test::More; -use strict; -use warnings; - -plan tests => 24; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; -} - -use Slic3r; -use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(scaled_epsilon epsilon scale unscale X Y deg2rad); - -{ - my $angle = deg2rad(4); - foreach my $ccw (1, 0) { - my $polyline = Slic3r::Polyline->new_scale([0,0], [0,10]); - { - my $p3 = Slic3r::Point->new_scale(0, 20); - $p3->rotate($angle * ($ccw ? 1 : -1), $polyline->[-1]); - is $ccw, ($p3->[X] < $polyline->[-1][X]) ? 1 : 0, 'third point is rotated correctly'; - $polyline->append($p3); - } - ok abs($polyline->length - scale(20)) < scaled_epsilon, 'curved polyline length'; - is $ccw, ($polyline->[2]->ccw(@$polyline[0,1]) > 0) ? 1 : 0, 'curved polyline has wanted orientation'; - - ok my $arc = Slic3r::GCode::ArcFitting::polyline_to_arc($polyline), 'arc is detected'; - is $ccw, $arc->is_ccw, 'arc orientation is correct'; - - ok abs($arc->angle - $angle) < epsilon, 'arc relative angle is correct'; - - ok $arc->start->coincides_with($polyline->[0]), 'arc start point is correct'; - ok $arc->end->coincides_with($polyline->[-1]), 'arc end point is correct'; - - # since first polyline segment is vertical we expect arc center to have same Y as its first point - is $arc->center->[Y], 0, 'arc center has correct Y'; - - my $s1 = Slic3r::Line->new(@$polyline[0,1]); - my $s2 = Slic3r::Line->new(@$polyline[1,2]); - ok abs($arc->center->distance_to($s1->midpoint) - $arc->center->distance_to($s2->midpoint)) < scaled_epsilon, - 'arc center is equidistant from both segments\' midpoints'; - } -} - -#========================================================== - -{ - my $path = Slic3r::Polyline->new_scale( - [13.532242,2.665496], [18.702911,9.954623], [22.251514,9.238193], [25.800116,9.954623], - [28.697942,11.908391], [30.65171,14.806217], [31.36814,18.35482], - [30.65171,21.903423], [28.697942,24.801249], [25.800116,26.755017], [22.251514,27.471447], - [18.702911,26.755017], [15.805085,24.801249], [13.851317,21.903423], [13.134887,18.35482], - [86948.77,175149.09], [119825.35,100585], - ); - - if (0) { - require "Slic3r::SVG"; - Slic3r::SVG::output( - "arc.svg", - polylines => [$path], - ); - } - - my $af = Slic3r::GCode::ArcFitting->new(max_relative_angle => deg2rad(30)); - my @chunks = $af->detect_arcs($path); - - is scalar(@chunks), 3, 'path collection now contains three paths'; - isa_ok $chunks[0], 'Slic3r::Polyline', 'first one is polyline'; - isa_ok $chunks[1], 'Slic3r::GCode::ArcFitting::Arc', 'second one is arc'; - isa_ok $chunks[2], 'Slic3r::Polyline', 'third one is polyline'; -} - -exit; - -#========================================================== - -{ - my @points = map [ scale $_->[0], scale $_->[1] ], ( - [10,20], [10.7845909572784,19.9691733373313], [11.5643446504023,19.8768834059514], - [12.3344536385591,19.7236992039768], [13.0901699437495,19.5105651629515], - [13.8268343236509,19.2387953251129], [14.5399049973955,18.9100652418837], - [15.2249856471595,18.5264016435409], [15.8778525229247,18.0901699437495], - [16.4944804833018,17.6040596560003] - ); - my $path1 = Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new(@points), - role => EXTR_ROLE_FILL, - mm3_per_mm => 0.5, - ); - my $path2 = Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new(reverse @points), - role => EXTR_ROLE_FILL, - mm3_per_mm => 0.5, - ); - - my @paths1 = $path1->detect_arcs(10, scale 1); - my @paths2 = $path2->detect_arcs(10, scale 1); - - is scalar(@paths1), 1, 'path collection now contains one path'; - is scalar(@paths2), 1, 'path collection now contains one path'; - - isa_ok $paths1[0], 'Slic3r::ExtrusionPath::Arc', 'path'; - isa_ok $paths2[0], 'Slic3r::ExtrusionPath::Arc', 'path'; - - my $expected_length = scale 7.06858347057701; - ok abs($paths1[0]->length - $expected_length) < scaled_epsilon, 'cw oriented arc has correct length'; - ok abs($paths2[0]->length - $expected_length) < scaled_epsilon, 'ccw oriented arc has correct length'; - - is $paths1[0]->orientation, 'cw', 'cw orientation was correctly detected'; - is $paths2[0]->orientation, 'ccw', 'ccw orientation was correctly detected'; - is $paths1[0]->flow_spacing, $path1->flow_spacing, 'flow spacing was correctly preserved'; - - my $center1 = [ map sprintf('%.0f', $_), @{ $paths1[0]->center } ]; - ok abs($center1->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected'; - - my $center2 = [ map sprintf('%.0f', $_), @{ $paths2[0]->center } ]; - ok abs($center2->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected'; -} - -#========================================================== diff --git a/t/pressure.t b/t/pressure.t deleted file mode 100644 index 6bbb81d84..000000000 --- a/t/pressure.t +++ /dev/null @@ -1,36 +0,0 @@ -use Test::More tests => 1; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; -} - -use List::Util qw(); -use Slic3r; -use Slic3r::Geometry qw(epsilon); -use Slic3r::Test; - -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('pressure_advance', 10); - $config->set('retract_length', [1]); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); - my $retracted = $config->retract_length->[0]; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{extruding} && !$info->{dist_XY}) { - $retracted += $info->{dist_E}; - } elsif ($info->{retracting}) { - $retracted += $info->{dist_E}; - } - }); - - ok abs($retracted) < 0.01, 'all retractions are compensated'; -} - - -__END__ diff --git a/xs/MANIFEST b/xs/MANIFEST index 4fbd32a48..52bf72263 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -367,14 +367,20 @@ src/libslic3r/GCode.cpp src/libslic3r/GCode.hpp src/libslic3r/GCode/CoolingBuffer.cpp src/libslic3r/GCode/CoolingBuffer.hpp +src/libslic3r/GCodeReader.cpp +src/libslic3r/GCodeReader.hpp src/libslic3r/GCodeSender.cpp src/libslic3r/GCodeSender.hpp +src/libslic3r/GCodeTimeEstimator.cpp +src/libslic3r/GCodeTimeEstimator.hpp src/libslic3r/GCodeWriter.cpp src/libslic3r/GCodeWriter.hpp src/libslic3r/GCode/Analyzer.cpp src/libslic3r/GCode/Analyzer.hpp src/libslic3r/GCode/PressureEqualizer.cpp src/libslic3r/GCode/PressureEqualizer.hpp +src/libslic3r/GCode/SpiralVase.cpp +src/libslic3r/GCode/SpiralVase.hpp src/libslic3r/Geometry.cpp src/libslic3r/Geometry.hpp src/libslic3r/Layer.cpp diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index 5d58ec404..0c66422d5 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -78,11 +78,6 @@ AvoidCrossingPerimeters::travel_to(GCode &gcodegen, Point point) } } -OozePrevention::OozePrevention() - : enable(false) -{ -} - std::string OozePrevention::pre_toolchange(GCode &gcodegen) { @@ -114,16 +109,11 @@ OozePrevention::pre_toolchange(GCode &gcodegen) return gcode; } -std::string -OozePrevention::post_toolchange(GCode &gcodegen) +std::string OozePrevention::post_toolchange(GCode &gcodegen) { - std::string gcode; - - if (gcodegen.config.standby_temperature_delta.value != 0) { - gcode += gcodegen.writer.set_temperature(this->_get_temp(gcodegen), true); - } - - return gcode; + return (gcodegen.config.standby_temperature_delta.value != 0) ? + gcodegen.writer.set_temperature(this->_get_temp(gcodegen), true) : + std::string(); } int @@ -134,23 +124,6 @@ OozePrevention::_get_temp(GCode &gcodegen) : gcodegen.config.temperature.get_at(gcodegen.writer.extruder()->id); } -Wipe::Wipe() - : enable(false) -{ -} - -bool -Wipe::has_path() -{ - return !this->path.points.empty(); -} - -void -Wipe::reset_path() -{ - this->path = Polyline(); -} - std::string Wipe::wipe(GCode &gcodegen, bool toolchange) { diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index b973bf95d..5e05455c3 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -41,26 +41,26 @@ class AvoidCrossingPerimeters { }; class OozePrevention { - public: +public: bool enable; Points standby_points; - OozePrevention(); + OozePrevention() : enable(false) {} std::string pre_toolchange(GCode &gcodegen); std::string post_toolchange(GCode &gcodegen); - private: +private: int _get_temp(GCode &gcodegen); }; class Wipe { - public: +public: bool enable; Polyline path; - Wipe(); - bool has_path(); - void reset_path(); + Wipe() : enable(false) {} + bool has_path() const { return !this->path.points.empty(); } + void reset_path() { this->path = Polyline(); } std::string wipe(GCode &gcodegen, bool toolchange = false); }; diff --git a/xs/src/libslic3r/GCode/SpiralVase.cpp b/xs/src/libslic3r/GCode/SpiralVase.cpp new file mode 100644 index 000000000..9299da86a --- /dev/null +++ b/xs/src/libslic3r/GCode/SpiralVase.cpp @@ -0,0 +1,95 @@ +#include "SpiralVase.hpp" +#include + +namespace Slic3r { + +std::string +_format_z(float z) +{ + std::ostringstream ss; + ss << std::fixed << std::setprecision(3) + << z; + return ss.str(); +} + +std::string +SpiralVase::process_layer(const std::string &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 (!this->enable) { + this->_reader.parse(gcode, {}); + return gcode; + } + + // Get total XY length for this layer by summing all extrusion moves. + float total_layer_length = 0; + float layer_height = 0; + float z; + bool set_z = false; + + { + GCodeReader r = this->_reader; // clone + r.parse(gcode, [&total_layer_length, &layer_height, &z, &set_z] + (GCodeReader &, const GCodeReader::GCodeLine &line) { + if (line.cmd == "G1") { + if (line.extruding()) { + total_layer_length += line.dist_XY(); + } else if (line.has('Z')) { + layer_height += line.dist_Z(); + if (!set_z) { + z = line.new_Z(); + set_z = true; + } + } + } + }); + } + + // Remove layer height from initial Z. + z -= layer_height; + + std::string new_gcode; + this->_reader.parse(gcode, [&new_gcode, &z, &layer_height, &total_layer_length] + (GCodeReader &, GCodeReader::GCodeLine line) { + if (line.cmd == "G1") { + if (line.has('Z')) { + // If this is the initial Z move of the layer, replace it with a + // (redundant) move to the last Z of previous layer. + line.set('Z', _format_z(z)); + new_gcode += line.raw + '\n'; + return; + } else { + float dist_XY = line.dist_XY(); + if (dist_XY > 0) { + // horizontal move + if (line.extruding()) { + z += dist_XY * layer_height / total_layer_length; + line.set('Z', _format_z(z)); + new_gcode += line.raw + '\n'; + } + return; + + /* 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?). */ + } + } + } + new_gcode += line.raw + '\n'; + }); + + return new_gcode; +} + +} diff --git a/xs/src/libslic3r/GCode/SpiralVase.hpp b/xs/src/libslic3r/GCode/SpiralVase.hpp new file mode 100644 index 000000000..f14d15879 --- /dev/null +++ b/xs/src/libslic3r/GCode/SpiralVase.hpp @@ -0,0 +1,29 @@ +#ifndef slic3r_SpiralVase_hpp_ +#define slic3r_SpiralVase_hpp_ + +#include "libslic3r.h" +#include "GCode.hpp" +#include "GCodeReader.hpp" + +namespace Slic3r { + +class SpiralVase { + public: + bool enable; + + SpiralVase(const PrintConfig &config) + : enable(false), _config(&config) + { + this->_reader.Z = this->_config->z_offset; + this->_reader.apply_config(*this->_config); + }; + std::string process_layer(const std::string &gcode); + + private: + const PrintConfig* _config; + GCodeReader _reader; +}; + +} + +#endif diff --git a/xs/src/libslic3r/GCodeReader.cpp b/xs/src/libslic3r/GCodeReader.cpp new file mode 100644 index 000000000..6ad937bbd --- /dev/null +++ b/xs/src/libslic3r/GCodeReader.cpp @@ -0,0 +1,109 @@ +#include "GCodeReader.hpp" +#include +#include +#include +#include + +namespace Slic3r { + +void +GCodeReader::apply_config(const PrintConfigBase &config) +{ + this->_config.apply(config, true); + this->_extrusion_axis = this->_config.get_extrusion_axis()[0]; +} + +void +GCodeReader::parse(const std::string &gcode, callback_t callback) +{ + std::istringstream ss(gcode); + std::string line; + while (std::getline(ss, line)) + this->parse_line(line, callback); +} + +void +GCodeReader::parse_line(std::string line, callback_t callback) +{ + GCodeLine gline(this); + gline.raw = line; + if (this->verbose) + std::cout << line << std::endl; + + // strip comment + { + size_t pos = line.find(';'); + if (pos != std::string::npos) { + gline.comment = line.substr(pos+1); + line.erase(pos); + } + } + + // command and args + { + std::vector args; + boost::split(args, line, boost::is_any_of(" ")); + + // first one is cmd + gline.cmd = args.front(); + args.erase(args.begin()); + + for (std::string &arg : args) { + if (arg.size() < 2) continue; + gline.args.insert(std::make_pair(arg[0], arg.substr(1))); + } + } + + // convert extrusion axis + if (this->_extrusion_axis != 'E') { + const auto it = gline.args.find(this->_extrusion_axis); + if (it != gline.args.end()) { + std::swap(gline.args['E'], it->second); + gline.args.erase(it); + } + } + + if (gline.has('E') && this->_config.use_relative_e_distances) + this->E = 0; + + if (callback) callback(*this, gline); + + // update coordinates + if (gline.cmd == "G0" || gline.cmd == "G1" || gline.cmd == "G92") { + this->X = gline.new_X(); + this->Y = gline.new_Y(); + this->Z = gline.new_Z(); + this->E = gline.new_E(); + this->F = gline.new_F(); + } +} + +void +GCodeReader::parse_file(const std::string &file, callback_t callback) +{ + std::ifstream f(file); + std::string line; + while (std::getline(f, line)) + this->parse_line(line, callback); +} + +void +GCodeReader::GCodeLine::set(char arg, std::string value) +{ + const std::string space(" "); + if (this->has(arg)) { + size_t pos = this->raw.find(space + arg)+2; + size_t end = this->raw.find(' ', pos+1); + this->raw = this->raw.replace(pos, end-pos, value); + } else { + size_t pos = this->raw.find(' '); + if (pos == std::string::npos) { + this->raw += space + arg + value; + } else { + this->raw = this->raw.replace(pos, 0, space + arg + value); + } + } + this->args[arg] = value; +} + +} diff --git a/xs/src/libslic3r/GCodeReader.hpp b/xs/src/libslic3r/GCodeReader.hpp new file mode 100644 index 000000000..8506a49d2 --- /dev/null +++ b/xs/src/libslic3r/GCodeReader.hpp @@ -0,0 +1,65 @@ +#ifndef slic3r_GCodeReader_hpp_ +#define slic3r_GCodeReader_hpp_ + +#include "libslic3r.h" +#include +#include +#include +#include +#include "PrintConfig.hpp" + +namespace Slic3r { + +class GCodeReader { +public: + class GCodeLine { + public: + GCodeReader* reader; + std::string raw; + std::string cmd; + std::string comment; + std::map args; + + GCodeLine(GCodeReader* _reader) : reader(_reader) {}; + + bool has(char arg) const { return this->args.count(arg) > 0; }; + float get_float(char arg) const { return atof(this->args.at(arg).c_str()); }; + float new_X() const { return this->has('X') ? atof(this->args.at('X').c_str()) : this->reader->X; }; + float new_Y() const { return this->has('Y') ? atof(this->args.at('Y').c_str()) : this->reader->Y; }; + float new_Z() const { return this->has('Z') ? atof(this->args.at('Z').c_str()) : this->reader->Z; }; + float new_E() const { return this->has('E') ? atof(this->args.at('E').c_str()) : this->reader->E; }; + float new_F() const { return this->has('F') ? atof(this->args.at('F').c_str()) : this->reader->F; }; + float dist_X() const { return this->new_X() - this->reader->X; }; + float dist_Y() const { return this->new_Y() - this->reader->Y; }; + float dist_Z() const { return this->new_Z() - this->reader->Z; }; + float dist_E() const { return this->new_E() - this->reader->E; }; + float dist_XY() const { + float x = this->dist_X(); + float y = this->dist_Y(); + return sqrt(x*x + y*y); + }; + bool extruding() const { return this->cmd == "G1" && this->dist_E() > 0; }; + bool retracting() const { return this->cmd == "G1" && this->dist_E() < 0; }; + bool travel() const { return this->cmd == "G1" && !this->has('E'); }; + void set(char arg, std::string value); + }; + typedef std::function callback_t; + + float X, Y, Z, E, F; + bool verbose; + callback_t callback; + + GCodeReader() : X(0), Y(0), Z(0), E(0), F(0), verbose(false), _extrusion_axis('E') {}; + void apply_config(const PrintConfigBase &config); + void parse(const std::string &gcode, callback_t callback); + void parse_line(std::string line, callback_t callback); + void parse_file(const std::string &file, callback_t callback); + +private: + GCodeConfig _config; + char _extrusion_axis; +}; + +} /* namespace Slic3r */ + +#endif /* slic3r_GCodeReader_hpp_ */ diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp new file mode 100644 index 000000000..c6fa353b4 --- /dev/null +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -0,0 +1,78 @@ +#include "GCodeTimeEstimator.hpp" +#include +#include + +namespace Slic3r { + +void +GCodeTimeEstimator::parse(const std::string &gcode) +{ + GCodeReader::parse(gcode, boost::bind(&GCodeTimeEstimator::_parser, this, _1, _2)); +} + +void +GCodeTimeEstimator::parse_file(const std::string &file) +{ + GCodeReader::parse_file(file, boost::bind(&GCodeTimeEstimator::_parser, this, _1, _2)); +} + +void +GCodeTimeEstimator::_parser(GCodeReader&, const GCodeReader::GCodeLine &line) +{ + // std::cout << "[" << this->time << "] " << line.raw << std::endl; + if (line.cmd == "G1") { + const float dist_XY = line.dist_XY(); + const float new_F = line.new_F(); + + if (dist_XY > 0) { + //this->time += dist_XY / new_F * 60; + this->time += _accelerated_move(dist_XY, new_F/60, this->acceleration); + } else { + //this->time += std::abs(line.dist_E()) / new_F * 60; + this->time += _accelerated_move(std::abs(line.dist_E()), new_F/60, this->acceleration); + } + //this->time += std::abs(line.dist_Z()) / new_F * 60; + this->time += _accelerated_move(std::abs(line.dist_Z()), new_F/60, this->acceleration); + } else if (line.cmd == "M204" && line.has('S')) { + this->acceleration = line.get_float('S'); + } else if (line.cmd == "G4") { // swell + if (line.has('S')) { + this->time += line.get_float('S'); + } else if (line.has('P')) { + this->time += line.get_float('P')/1000; + } + } +} + +// Wildly optimistic acceleration "bell" curve modeling. +// Returns an estimate of how long the move with a given accel +// takes in seconds. +// It is assumed that the movement is smooth and uniform. +float +GCodeTimeEstimator::_accelerated_move(double length, double v, double acceleration) +{ + // for half of the move, there are 2 zones, where the speed is increasing/decreasing and + // where the speed is constant. + // Since the slowdown is assumed to be uniform, calculate the average velocity for half of the + // expected displacement. + // final velocity v = a*t => a * (dx / 0.5v) => v^2 = 2*a*dx + // v_avg = 0.5v => 2*v_avg = v + // d_x = v_avg*t => t = d_x / v_avg + acceleration = (acceleration == 0.0 ? 4000.0 : acceleration); // Set a default accel to use for print time in case it's 0 somehow. + auto half_length = length / 2.0; + auto t_init = v / acceleration; // time to final velocity + auto dx_init = (0.5*v*t_init); // Initial displacement for the time to get to final velocity + auto t = 0.0; + if (half_length >= dx_init) { + half_length -= (0.5*v*t_init); + t += t_init; + t += (half_length / v); // rest of time is at constant speed. + } else { + // If too much displacement for the expected final velocity, we don't hit the max, so reduce + // the average velocity to fit the displacement we actually are looking for. + t += std::sqrt(std::abs(length) * 2.0 * acceleration) / acceleration; + } + return 2.0*t; // cut in half before, so double to get full time spent. +} + +} diff --git a/xs/src/libslic3r/GCodeTimeEstimator.hpp b/xs/src/libslic3r/GCodeTimeEstimator.hpp new file mode 100644 index 000000000..dd301c929 --- /dev/null +++ b/xs/src/libslic3r/GCodeTimeEstimator.hpp @@ -0,0 +1,24 @@ +#ifndef slic3r_GCodeTimeEstimator_hpp_ +#define slic3r_GCodeTimeEstimator_hpp_ + +#include "libslic3r.h" +#include "GCodeReader.hpp" + +namespace Slic3r { + +class GCodeTimeEstimator : public GCodeReader { + public: + float time = 0; // in seconds + + void parse(const std::string &gcode); + void parse_file(const std::string &file); + + protected: + float acceleration = 9000; + void _parser(GCodeReader&, const GCodeReader::GCodeLine &line); + static float _accelerated_move(double length, double v, double acceleration); +}; + +} /* namespace Slic3r */ + +#endif /* slic3r_GCodeTimeEstimator_hpp_ */ diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 3b2d64177..cafbd8037 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -152,7 +152,6 @@ Print::invalidate_state_by_config_options(const std::vector || *opt_key == "first_layer_bed_temperature" || *opt_key == "first_layer_speed" || *opt_key == "first_layer_temperature" - || *opt_key == "gcode_arcs" || *opt_key == "gcode_comments" || *opt_key == "gcode_flavor" || *opt_key == "infill_acceleration" @@ -166,7 +165,6 @@ Print::invalidate_state_by_config_options(const std::vector || *opt_key == "output_filename_format" || *opt_key == "perimeter_acceleration" || *opt_key == "post_process" - || *opt_key == "pressure_advance" || *opt_key == "retract_before_travel" || *opt_key == "retract_layer_change" || *opt_key == "retract_length" diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index cae0479d6..545cc3e44 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -514,12 +514,6 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloat(20); - def = this->add("gcode_arcs", coBool); - def->label = "Use native G-code arcs"; - def->tooltip = "This experimental feature tries to detect arcs from segments and generates G2/G3 arc commands instead of multiple straight G1 commands."; - def->cli = "gcode-arcs!"; - def->default_value = new ConfigOptionBool(0); - def = this->add("gcode_comments", coBool); def->label = "Verbose G-code"; def->tooltip = "Enable this to get a commented G-code file, with each line explained by a descriptive text. If you print from SD card, the additional weight of the file could make your firmware slow down."; @@ -853,13 +847,6 @@ PrintConfigDef::PrintConfigDef() def = this->add("printer_settings_id", coString); def->default_value = new ConfigOptionString(""); - def = this->add("pressure_advance", coFloat); - def->label = "Pressure advance"; - def->tooltip = "When set to a non-zero value, this experimental option enables pressure regulation. It's the K constant for the advance algorithm that pushes more or less filament upon speed changes. It's useful for Bowden-tube extruders. Reasonable values are in range 0-10."; - def->cli = "pressure-advance=f"; - def->min = 0; - def->default_value = new ConfigOptionFloat(0); - def = this->add("raft_layers", coInt); def->label = "Raft layers"; def->category = "Support material"; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 4ed822511..101cb2c52 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -313,7 +313,6 @@ class GCodeConfig : public virtual StaticPrintConfig ConfigOptionFloat max_volumetric_speed; ConfigOptionFloat max_volumetric_extrusion_rate_slope_positive; ConfigOptionFloat max_volumetric_extrusion_rate_slope_negative; - ConfigOptionFloat pressure_advance; ConfigOptionFloats retract_length; ConfigOptionFloats retract_length_toolchange; ConfigOptionFloats retract_lift; @@ -351,7 +350,6 @@ class GCodeConfig : public virtual StaticPrintConfig OPT_PTR(max_volumetric_speed); OPT_PTR(max_volumetric_extrusion_rate_slope_positive); OPT_PTR(max_volumetric_extrusion_rate_slope_negative); - OPT_PTR(pressure_advance); OPT_PTR(retract_length); OPT_PTR(retract_length_toolchange); OPT_PTR(retract_lift); @@ -409,7 +407,6 @@ class PrintConfig : public GCodeConfig ConfigOptionFloatOrPercent first_layer_extrusion_width; ConfigOptionFloatOrPercent first_layer_speed; ConfigOptionInts first_layer_temperature; - ConfigOptionBool gcode_arcs; ConfigOptionFloat infill_acceleration; ConfigOptionBool infill_first; ConfigOptionInt max_fan_speed; @@ -468,7 +465,6 @@ class PrintConfig : public GCodeConfig OPT_PTR(first_layer_extrusion_width); OPT_PTR(first_layer_speed); OPT_PTR(first_layer_temperature); - OPT_PTR(gcode_arcs); OPT_PTR(infill_acceleration); OPT_PTR(infill_first); OPT_PTR(max_fan_speed); diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index d76671833..571f4664b 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -17,6 +17,7 @@ REGISTER_CLASS(Flow, "Flow"); REGISTER_CLASS(AvoidCrossingPerimeters, "GCode::AvoidCrossingPerimeters"); REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(OozePrevention, "GCode::OozePrevention"); +REGISTER_CLASS(SpiralVase, "GCode::SpiralVase"); REGISTER_CLASS(Wipe, "GCode::Wipe"); REGISTER_CLASS(GCode, "GCode"); REGISTER_CLASS(GCodeSender, "GCode::Sender"); diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 58c12170e..8d2856b13 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -4,6 +4,7 @@ #include #include "libslic3r/GCode.hpp" #include "libslic3r/GCode/CoolingBuffer.hpp" +#include "libslic3r/GCode/SpiralVase.hpp" %} %name{Slic3r::GCode::AvoidCrossingPerimeters} class AvoidCrossingPerimeters { @@ -81,6 +82,18 @@ std::string flush(); }; +%name{Slic3r::GCode::SpiralVase} class SpiralVase { + SpiralVase(StaticPrintConfig* config) + %code{% RETVAL = new SpiralVase(*dynamic_cast(config)); %}; + ~SpiralVase(); + + bool enable() + %code{% RETVAL = THIS->enable; %}; + void set_enable(bool enable) + %code{% THIS->enable = enable; %}; + std::string process_layer(std::string gcode); +}; + %name{Slic3r::GCode} class GCode { GCode(); ~GCode(); diff --git a/xs/xsp/my.map b/xs/xsp/my.map index e48c0eec7..0d28d1ac2 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -201,6 +201,10 @@ CoolingBuffer* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +SpiralVase* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + GCode* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T