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:
parent
e918ea9c65
commit
72ae3585e4
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
||||
|
||||
|
@ -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;
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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])
|
||||
|
122
t/arcs.t
122
t/arcs.t
@ -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';
|
||||
}
|
||||
|
||||
#==========================================================
|
36
t/pressure.t
36
t/pressure.t
@ -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__
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
95
xs/src/libslic3r/GCode/SpiralVase.cpp
Normal file
95
xs/src/libslic3r/GCode/SpiralVase.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
#include "SpiralVase.hpp"
|
||||
#include <sstream>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
29
xs/src/libslic3r/GCode/SpiralVase.hpp
Normal file
29
xs/src/libslic3r/GCode/SpiralVase.hpp
Normal file
@ -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
|
109
xs/src/libslic3r/GCodeReader.cpp
Normal file
109
xs/src/libslic3r/GCodeReader.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
#include "GCodeReader.hpp"
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
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<std::string> 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;
|
||||
}
|
||||
|
||||
}
|
65
xs/src/libslic3r/GCodeReader.hpp
Normal file
65
xs/src/libslic3r/GCodeReader.hpp
Normal file
@ -0,0 +1,65 @@
|
||||
#ifndef slic3r_GCodeReader_hpp_
|
||||
#define slic3r_GCodeReader_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCodeReader {
|
||||
public:
|
||||
class GCodeLine {
|
||||
public:
|
||||
GCodeReader* reader;
|
||||
std::string raw;
|
||||
std::string cmd;
|
||||
std::string comment;
|
||||
std::map<char,std::string> 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<void(GCodeReader&, const GCodeLine&)> 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_ */
|
78
xs/src/libslic3r/GCodeTimeEstimator.cpp
Normal file
78
xs/src/libslic3r/GCodeTimeEstimator.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
#include "GCodeTimeEstimator.hpp"
|
||||
#include <boost/bind.hpp>
|
||||
#include <cmath>
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
}
|
24
xs/src/libslic3r/GCodeTimeEstimator.hpp
Normal file
24
xs/src/libslic3r/GCodeTimeEstimator.hpp
Normal file
@ -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_ */
|
@ -152,7 +152,6 @@ Print::invalidate_state_by_config_options(const std::vector<t_config_option_key>
|
||||
|| *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<t_config_option_key>
|
||||
|| *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"
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <xsinit.h>
|
||||
#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<PrintConfig*>(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();
|
||||
|
@ -201,6 +201,10 @@ CoolingBuffer* O_OBJECT_SLIC3R
|
||||
Ref<CoolingBuffer> O_OBJECT_SLIC3R_T
|
||||
Clone<CoolingBuffer> O_OBJECT_SLIC3R_T
|
||||
|
||||
SpiralVase* O_OBJECT_SLIC3R
|
||||
Ref<SpiralVase> O_OBJECT_SLIC3R_T
|
||||
Clone<SpiralVase> O_OBJECT_SLIC3R_T
|
||||
|
||||
GCode* O_OBJECT_SLIC3R
|
||||
Ref<GCode> O_OBJECT_SLIC3R_T
|
||||
Clone<GCode> O_OBJECT_SLIC3R_T
|
||||
|
Loading…
Reference in New Issue
Block a user