diff --git a/MANIFEST b/MANIFEST index 8dacceb30..bc338a5ce 100644 --- a/MANIFEST +++ b/MANIFEST @@ -27,6 +27,7 @@ lib/Slic3r/GCode.pm lib/Slic3r/GCode/CoolingBuffer.pm lib/Slic3r/GCode/MotionPlanner.pm lib/Slic3r/GCode/Reader.pm +lib/Slic3r/GCode/SpiralVase.pm lib/Slic3r/Geometry.pm lib/Slic3r/Geometry/Clipper.pm lib/Slic3r/GUI.pm diff --git a/README.markdown b/README.markdown index dabd97a88..01c43d22d 100644 --- a/README.markdown +++ b/README.markdown @@ -198,6 +198,8 @@ The author of the Silk icon set is Mark James. --randomize-start Randomize starting point across layers (default: yes) --avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no) --external-perimeters-first Reverse perimeter order. (default: no) + --spiral-vase Experimental option to raise Z gradually when printing single-walled vases + (default: no) --only-retract-when-crossing-perimeters Disable retraction when travelling between infill paths inside the same island. (default: yes) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 48adf8e72..9214022f8 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -48,6 +48,7 @@ use Slic3r::GCode; use Slic3r::GCode::CoolingBuffer; use Slic3r::GCode::MotionPlanner; use Slic3r::GCode::Reader; +use Slic3r::GCode::SpiralVase; use Slic3r::Geometry qw(PI); use Slic3r::Layer; use Slic3r::Layer::Region; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 4705552b3..3fc8c2835 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -586,6 +586,13 @@ our $Options = { type => 'bool', default => 0, }, + 'spiral_vase' => { + label => 'Spiral vase', + tooltip => 'This experimental feature will raise Z gradually while printing a single-walled object in order to remove any visible seam. By enabling this option other settings will be overridden to enforce a single perimeter, no infill, no top solid layers, no support material. You can still set any number of bottom solid layers as well as skirt/brim loops. It won\'t work when printing more than an object.', + cli => 'spiral-vase!', + type => 'bool', + default => 0, + }, 'only_retract_when_crossing_perimeters' => { label => 'Only retract when crossing perimeters', tooltip => 'Disables retraction when travelling between infill paths inside the same island.', diff --git a/lib/Slic3r/GCode/SpiralVase.pm b/lib/Slic3r/GCode/SpiralVase.pm new file mode 100644 index 000000000..5c0a50fd3 --- /dev/null +++ b/lib/Slic3r/GCode/SpiralVase.pm @@ -0,0 +1,40 @@ +package Slic3r::GCode::SpiralVase; +use Moo; + +use Slic3r::Geometry qw(unscale); + +sub process_layer { + my $self = shift; + my ($gcode, $layer) = @_; + + my $total_layer_length = 0; + Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub { + my ($reader, $cmd, $args, $info) = @_; + $total_layer_length += $info->{dist_XY} + if $cmd eq 'G1' && $info->{extruding}; + }); + + my $new_gcode = ""; + my $layer_height = $layer->height; + my $z = unscale($layer->print_z) - $layer_height; + Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub { + my ($reader, $cmd, $args, $info) = @_; + + if ($cmd eq 'G1' && exists $args->{Z}) { + my $line = $info->{raw}; + $line =~ s/Z([^ ]+)/Z$z/; + $new_gcode .= "$line\n"; + } elsif ($cmd eq 'G1' && !exists $args->{Z} && $info->{extruding} && $info->{dist_XY}) { + $z += $info->{dist_XY} * $layer_height / $total_layer_length; + my $line = $info->{raw}; + $line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e; + $new_gcode .= "$line\n"; + } 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 1dac68ca0..c985e93c9 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -412,7 +412,7 @@ sub build { }, { title => 'Advanced', - options => [qw(avoid_crossing_perimeters external_perimeters_first)], + options => [qw(avoid_crossing_perimeters external_perimeters_first spiral_vase)], }, ]); diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 9841e4fd7..1d84ad640 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -63,6 +63,14 @@ sub _trigger_config { # G-code flavors $self->config->set('extrusion_axis', 'A') if $self->config->gcode_flavor eq 'mach3'; $self->config->set('extrusion_axis', '') if $self->config->gcode_flavor eq 'no-extrusion'; + + # enforce some settings when spiral_vase is set + if ($self->config->spiral_vase) { + $self->config->set('perimeters', 1); + $self->config->set('fill_density', 0); + $self->config->set('top_solid_layers', 0); + $self->config->set('support_material', 0); + } } sub _build_has_support_material { @@ -184,6 +192,12 @@ sub validate { } } } + + if ($Slic3r::Config->spiral_vase) { + if ((map @{$_->copies}, @{$self->objects}) > 1) { + die "The Spiral Vase option can only be used when printing a single object.\n"; + } + } } sub init_extruders { @@ -794,6 +808,11 @@ sub write_gcode { )); } + # prepare the SpiralVase processor if it's possible + my $spiralvase = $Slic3r::Config->spiral_vase + ? Slic3r::GCode::SpiralVase->new + : undef; + # prepare the logic to print one layer my $skirt_done = 0; # count of skirt layers done my $brim_done = 0; @@ -956,6 +975,14 @@ sub write_gcode { } } } + + # apply spiral vase post-processing if this layer contains suitable geometry + $gcode = $spiralvase->process_layer($gcode, $layer) + if defined $spiralvase + && ($layer->id > 0 || $Slic3r::Config->brim_width == 0) + && ($layer->id >= $Slic3r::Config->skirt_height) + && ($layer->id >= $Slic3r::Config->bottom_solid_layers); + return $gcode; }; diff --git a/slic3r.pl b/slic3r.pl index 9850a9916..b3dec4969 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -251,6 +251,8 @@ $j --randomize-start Randomize starting point across layers (default: yes) --avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no) --external-perimeters-first Reverse perimeter order. (default: no) + --spiral-vase Experimental option to raise Z gradually when printing single-walled vases + (default: no) --only-retract-when-crossing-perimeters Disable retraction when travelling between infill paths inside the same island. (default: no)