Merge branch 'master' into simple-mode

This commit is contained in:
Alessandro Ranellucci 2013-03-08 22:28:13 +01:00
commit b901e1f6c8
48 changed files with 1711 additions and 711 deletions

View file

@ -7,11 +7,12 @@ my $build = Module::Build->new(
dist_version => '0.1',
license => 'perl',
requires => {
'Boost::Geometry::Utils' => '0',
'Boost::Geometry::Utils' => '0.06',
'Encode::Locale' => '0',
'File::Basename' => '0',
'File::Spec' => '0',
'Getopt::Long' => '0',
'Math::Clipper' => '1.15',
'Math::Clipper' => '1.17',
'Math::ConvexHull::MonotoneChain' => '0.01',
'Math::Geometry::Voronoi' => '1.3',
'Math::PlanePath' => '53',

View file

@ -24,6 +24,7 @@ lib/Slic3r/Format/AMF/Parser.pm
lib/Slic3r/Format/OBJ.pm
lib/Slic3r/Format/STL.pm
lib/Slic3r/GCode.pm
lib/Slic3r/GCode/MotionPlanner.pm
lib/Slic3r/Geometry.pm
lib/Slic3r/Geometry/Clipper.pm
lib/Slic3r/GUI.pm
@ -56,9 +57,11 @@ t/arcs.t
t/clean_polylines.t
t/clipper.t
t/collinear.t
t/combineinfill.t
t/custom_gcode.t
t/dynamic.t
t/fill.t
t/gcode.t
t/geometry.t
t/layers.t
t/loops.t

View file

@ -108,7 +108,7 @@ The author of the Silk icon set is Mark James.
(default: 100,100)
--z-offset Additional height in mm to add to vertical coordinates
(+/-, default: 0)
--gcode-flavor The type of G-code to generate (reprap/teacup/makerbot/mach3/no-extrusion,
--gcode-flavor The type of G-code to generate (reprap/teacup/makerbot/sailfish/mach3/no-extrusion,
default: reprap)
--use-relative-e-distances Enable this to get relative E values
--gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported
@ -140,7 +140,7 @@ The author of the Silk icon set is Mark James.
(default: 30)
--external-perimeter-speed
Speed of print moves for the external perimeter in mm/s or % over perimeter speed
(default: 100%)
(default: 70%)
--infill-speed Speed of print moves in mm/s (default: 60)
--solid-infill-speed Speed of print moves for solid surfaces in mm/s or % over infill speed
(default: 60)
@ -153,6 +153,17 @@ The author of the Silk icon set is Mark James.
--first-layer-speed Speed of print moves for bottom layer, expressed either as an absolute
value or as a percentage over normal speeds (default: 30%)
Acceleration options:
--perimeter-acceleration
Overrides firmware's default acceleration for perimeters. (mm/s^2, set zero
to disable; default: 0)
--infill-acceleration
Overrides firmware's default acceleration for infill. (mm/s^2, set zero
to disable; default: 0)
--default-acceleration
Acceleration will be reset to this value after the specific settings above
have been applied. (mm/s^2, set zero to disable; default: 130)
Accuracy options:
--layer-height Layer height in mm (default: 0.4)
--first-layer-height Layer height for first layer (mm or %, default: 100%)
@ -179,12 +190,15 @@ The author of the Silk icon set is Mark James.
--toolchange-gcode Load tool-change G-code from the supplied file (default: nothing).
--extra-perimeters Add more perimeters when needed (default: yes)
--randomize-start Randomize starting point across layers (default: yes)
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
--only-retract-when-crossing-perimeters
Disable retraction when travelling between infill paths inside the same island.
(default: no)
(default: yes)
--solid-infill-below-area
Force solid infill when a region has a smaller area than this threshold
(mm^2, default: 70)
--infill-only-where-needed
Only infill under ceilings (default: no)
Support material options:
--support-material Generate support material for overhangs
@ -197,6 +211,14 @@ The author of the Silk icon set is Mark James.
Spacing between pattern lines (mm, default: 2.5)
--support-material-angle
Support material angle in degrees (range: 0-90, default: 0)
--support-material-interface-layers
Number of perpendicular layers between support material and object (0+, default: 0)
--support-material-interface-spacing
Spacing between interface pattern lines (mm, set 0 to get a solid layer, default: 0)
--raft-layers Number of layers to raise the printed objects by (range: 0+, default: 0)
--support-material-enforce-layers
Enforce support material on the specified number of layers from bottom,
regardless of --support-material and threshold (0+, default: 0)
Retraction options:
--retract-length Length of retraction in mm when pausing extrusion (default: 1)
@ -223,7 +245,7 @@ The author of the Silk icon set is Mark James.
--fan-below-layer-time Enable fan if layer print time is below this approximate number
of seconds (default: 60)
--slowdown-below-layer-time Slow down if layer print time is below this approximate number
of seconds (default: 15)
of seconds (default: 30)
--min-print-speed Minimum print speed (mm/s, default: 10)
--disable-fan-first-layers Disable fan for the first N layers (default: 1)
--fan-always-on Keep fan always on at min fan speed, even for layers that don't need
@ -263,10 +285,12 @@ The author of the Silk icon set is Mark James.
(like 0.65) or a percentage over layer height (like 200%)
--first-layer-extrusion-width
Set a different extrusion width for first layer
--perimeters-extrusion-width
--perimeter-extrusion-width
Set a different extrusion width for perimeters
--infill-extrusion-width
Set a different extrusion width for infill
--top-infill-extrusion-width
Set a different extrusion width for top infill
--support-material-extrusion-width
Set a different extrusion width for support material
--bridge-flow-ratio Multiplier for extrusion when bridging (> 0, default: 1)
@ -274,7 +298,7 @@ The author of the Silk icon set is Mark James.
Multiple extruder options:
--extruder-offset Offset of each extruder, if firmware doesn't handle the displacement
(can be specified multiple times, default: 0x0)
--perimeters-extruder
--perimeter-extruder
Extruder to use for perimeters (1+, default: 1)
--infill-extruder Extruder to use for infill (1+, default: 1)
--support-material-extruder

View file

@ -7,7 +7,7 @@ use strict;
use warnings;
require v5.10;
our $VERSION = "0.9.8-dev";
our $VERSION = "0.9.9-dev";
our $debug = 0;
sub debugf {
@ -18,7 +18,7 @@ sub debugf {
our $have_threads;
BEGIN {
use Config;
$have_threads = $Config{useithreads} && eval "use threads; use Thread::Queue; 1";
$have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1";
}
warn "Running Slic3r under Perl >= 5.16 is not supported nor recommended\n"
@ -27,7 +27,11 @@ warn "Running Slic3r under Perl >= 5.16 is not supported nor recommended\n"
use FindBin;
our $var = "$FindBin::Bin/var";
use Encode;
use Encode::Locale;
use Boost::Geometry::Utils 0.06;
use Moo 0.091009;
use Slic3r::Config;
use Slic3r::ExPolygon;
use Slic3r::Extruder;
@ -41,6 +45,7 @@ use Slic3r::Format::AMF;
use Slic3r::Format::OBJ;
use Slic3r::Format::STL;
use Slic3r::GCode;
use Slic3r::GCode::MotionPlanner;
use Slic3r::Geometry qw(PI);
use Slic3r::Layer;
use Slic3r::Layer::Region;
@ -64,12 +69,7 @@ use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI;
use constant LOOP_CLIPPING_LENGTH_OVER_SPACING => 0.15;
use constant PERIMETER_INFILL_OVERLAP_OVER_SPACING => 0.45;
# The following variables hold the objects used throughout the slicing
# process. They should belong to the Print object, but we are keeping
# them here because it makes accessing them slightly faster.
our $Config;
our $flow;
our $first_layer_flow;
sub parallelize {
my %params = @_;
@ -88,4 +88,14 @@ sub parallelize {
}
}
sub encode_path {
my ($filename) = @_;
return encode('locale_fs', $filename);
}
sub open {
my ($fh, $mode, $filename) = @_;
return CORE::open $$fh, $mode, encode_path($filename);
}
1;

View file

@ -6,7 +6,7 @@ use utf8;
use List::Util qw(first);
# cemetery of old config settings
our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool);
our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration);
my $serialize_comma = sub { join ',', @{$_[0]} };
my $deserialize_comma = sub { [ split /,/, $_[0] ] };
@ -64,8 +64,8 @@ our $Options = {
tooltip => 'Some G/M-code commands, including temperature control and others, are not universal. Set this option to your printer\'s firmware to get a compatible output. The "No extrusion" flavor prevents Slic3r from exporting any extrusion value at all.',
cli => 'gcode-flavor=s',
type => 'select',
values => [qw(reprap teacup makerbot mach3 no-extrusion)],
labels => ['RepRap (Marlin/Sprinter)', 'Teacup', 'MakerBot', 'Mach3/EMC', 'No extrusion'],
values => [qw(reprap teacup makerbot sailfish mach3 no-extrusion)],
labels => ['RepRap (Marlin/Sprinter)', 'Teacup', 'MakerBot', 'Sailfish', 'Mach3/EMC', 'No extrusion'],
default => 'reprap',
},
'use_relative_e_distances' => {
@ -158,7 +158,7 @@ our $Options = {
sidetext => '°C',
cli => 'temperature=i@',
type => 'i',
max => 300,
max => 400,
serialize => $serialize_comma,
deserialize => sub { $_[0] ? [ split /,/, $_[0] ] : [0] },
default => [200],
@ -171,7 +171,7 @@ our $Options = {
type => 'i',
serialize => $serialize_comma,
deserialize => sub { $_[0] ? [ split /,/, $_[0] ] : [0] },
max => 300,
max => 400,
default => [200],
},
@ -254,7 +254,7 @@ our $Options = {
cli => 'external-perimeter-speed=s',
type => 'f',
ratio_over => 'perimeter_speed',
default => '100%',
default => '70%',
},
'infill_speed' => {
label => 'Infill',
@ -319,25 +319,29 @@ our $Options = {
},
# acceleration options
'acceleration' => {
label => 'Enable acceleration control',
cli => 'acceleration!',
type => 'bool',
'default_acceleration' => {
label => 'Default',
tooltip => 'This is the acceleration your printer will be reset to after the role-specific acceleration values are used (perimeter/infill). Set zero to prevent resetting acceleration at all.',
sidetext => 'mm/s²',
cli => 'default-acceleration',
type => 'f',
default => 0,
},
'perimeter_acceleration' => {
label => 'Perimeters',
tooltip => 'This is the acceleration your printer will use for perimeters. A high value like 9000 usually gives good results if your hardware is up to the job. Set zero to disable acceleration control for perimeters.',
sidetext => 'mm/s²',
cli => 'perimeter-acceleration',
type => 'f',
default => 25,
default => 0,
},
'infill_acceleration' => {
label => 'Infill',
tooltip => 'This is the acceleration your printer will use for infill. Set zero to disable acceleration control for infill.',
sidetext => 'mm/s²',
cli => 'infill-acceleration',
type => 'f',
default => 50,
default => 0,
},
# accuracy options
@ -376,6 +380,13 @@ our $Options = {
min => 0,
default => 0,
},
'infill_only_where_needed' => {
label => 'Only infill where needed',
tooltip => 'This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material).',
cli => 'infill-only-where-needed!',
type => 'bool',
default => 0,
},
# flow options
'extrusion_width' => {
@ -411,6 +422,14 @@ our $Options = {
type => 'f',
default => 0,
},
'top_infill_extrusion_width' => {
label => 'Top infill',
tooltip => 'Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. If expressed as percentage (for example 90%) if will be computed over layer height.',
sidetext => 'mm or % (leave 0 for default)',
cli => 'top-infill-extrusion-width=s',
type => 'f',
default => 0,
},
'support_material_extrusion_width' => {
label => 'Support material',
tooltip => 'Set this to a non-zero value to set a manual extrusion width for support material. If expressed as percentage (for example 90%) if will be computed over layer height.',
@ -518,14 +537,21 @@ our $Options = {
tooltip => 'Start each layer from a different vertex to prevent plastic build-up on the same corner.',
cli => 'randomize-start!',
type => 'bool',
default => 1,
default => 0,
},
'avoid_crossing_perimeters' => {
label => 'Avoid crossing perimeters',
tooltip => 'Optimize travel moves in order to minimize the crossing of perimeters. This is mostly useful with Bowden extruders which suffer from oozing. This feature slows down both the print and the G-code generation.',
cli => 'avoid-crossing-perimeters!',
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.',
cli => 'only-retract-when-crossing-perimeters!',
type => 'bool',
default => 0,
default => 1,
},
'support_material' => {
label => 'Generate support material',
@ -547,8 +573,8 @@ our $Options = {
tooltip => 'Pattern used to generate support material.',
cli => 'support-material-pattern=s',
type => 'select',
values => [qw(rectilinear honeycomb)],
labels => [qw(rectilinear honeycomb)],
values => [qw(rectilinear rectilinear-grid honeycomb)],
labels => ['rectilinear', 'rectilinear grid', 'honeycomb'],
default => 'rectilinear',
},
'support_material_spacing' => {
@ -567,6 +593,38 @@ our $Options = {
type => 'i',
default => 0,
},
'support_material_interface_layers' => {
label => 'Interface layers',
tooltip => 'Number of interface layers to insert between the object(s) and support material.',
sidetext => 'layers',
cli => 'support-material-interface-layers=i',
type => 'i',
default => 0,
},
'support_material_interface_spacing' => {
label => 'Interface pattern spacing',
tooltip => 'Spacing between interface lines. Set zero to get a solid interface.',
sidetext => 'mm',
cli => 'support-material-interface-spacing=f',
type => 'f',
default => 0,
},
'support_material_enforce_layers' => {
label => 'Enforce support for the first',
tooltip => 'Generate support material for the specified number of layers counting from bottom, regardless of whether normal support material is enabled or not and regardless of any angle threshold. This is useful for getting more adhesion of objects having a very thin or poor footprint on the build plate.',
sidetext => 'layers',
cli => 'support-material-enforce-layers=f',
type => 'i',
default => 0,
},
'raft_layers' => {
label => 'Raft layers',
tooltip => 'Number of total raft layers to insert below the object(s).',
sidetext => 'layers',
cli => 'raft-layers=i',
type => 'i',
default => 0,
},
'start_gcode' => {
label => 'Start G-code',
tooltip => 'This start procedure is inserted at the beginning of the output file, right after the temperature control commands for extruder and bed. If Slic3r detects M104 or M190 in your custom codes, such commands will not be prepended automatically. Note that you can use placeholder variables for all Slic3r settings, so you can put a "M104 S[first_layer_temperature]" command wherever you want.',
@ -692,7 +750,7 @@ END
type => 'f',
serialize => $serialize_comma,
deserialize => $deserialize_comma,
default => [3],
default => [10],
},
'retract_restart_extra_toolchange' => {
label => 'Extra length on restart',
@ -711,7 +769,7 @@ END
tooltip => 'This flag enables all the cooling features.',
cli => 'cooling!',
type => 'bool',
default => 0,
default => 1,
},
'min_fan_speed' => {
label => 'Min',
@ -758,7 +816,7 @@ END
type => 'i',
max => 1000,
width => 60,
default => 15,
default => 30,
},
'min_print_speed' => {
label => 'Min print speed',
@ -942,7 +1000,7 @@ sub new_from_cli {
if ($args{$opt_key}) {
die "Invalid value for --${_}-gcode: file does not exist\n"
if !-e $args{$opt_key};
open my $fh, "<", $args{$opt_key}
Slic3r::open(\my $fh, "<", $args{$opt_key})
or die "Failed to open $args{$opt_key}\n";
binmode $fh, ':utf8';
$args{$opt_key} = do { local $/; <$fh> };
@ -1020,6 +1078,17 @@ sub set {
$value = 1;
}
# For historical reasons, the world's full of configs having these very low values;
# to avoid unexpected behavior we need to ignore them. Banning these two hard-coded
# values is a dirty hack and will need to be removed sometime in the future, but it
# will avoid lots of complaints for now.
if ($opt_key eq 'perimeter_acceleration' && $value == '25') {
$value = 0;
}
if ($opt_key eq 'infill_acceleration' && $value == '50') {
$value = 0;
}
if (!exists $Options->{$opt_key}) {
my @keys = grep { $Options->{$_}{aliases} && grep $_ eq $opt_key, @{$Options->{$_}{aliases}} } keys %$Options;
if (!@keys) {
@ -1152,9 +1221,6 @@ sub validate {
# --infill-every-layers
die "Invalid value for --infill-every-layers\n"
if $self->infill_every_layers !~ /^\d+$/ || $self->infill_every_layers < 1;
# TODO: this check should be limited to the extruder used for infill
die "Maximum infill thickness can't exceed nozzle diameter\n"
if grep $self->infill_every_layers * $self->layer_height > $_, @{$self->nozzle_diameter};
# --scale
die "Invalid value for --scale\n"
@ -1247,7 +1313,7 @@ sub write_ini {
my $class = shift;
my ($file, $ini) = @_;
open my $fh, '>', $file;
Slic3r::open(\my $fh, '>', $file);
binmode $fh, ':utf8';
my $localtime = localtime;
printf $fh "# generated by Slic3r $Slic3r::VERSION on %s\n", "$localtime";
@ -1265,7 +1331,7 @@ sub read_ini {
my ($file) = @_;
local $/ = "\n";
open my $fh, '<', $file;
Slic3r::open(\my $fh, '<', $file);
binmode $fh, ':utf8';
my $ini = { _ => {} };

View file

@ -6,7 +6,7 @@ use warnings;
use Boost::Geometry::Utils;
use Math::Geometry::Voronoi;
use Slic3r::Geometry qw(X Y A B point_in_polygon same_line line_length);
use Slic3r::Geometry qw(X Y A B point_in_polygon same_line line_length epsilon);
use Slic3r::Geometry::Clipper qw(union_ex JT_MITER);
# the constructor accepts an array of polygons
@ -59,6 +59,12 @@ sub boost_polygon {
return Boost::Geometry::Utils::polygon(@$self);
}
sub wkt {
my $self = shift;
return sprintf "POLYGON(%s)",
join ',', map "($_)", map { join ',', map "$_->[0] $_->[1]", @$_ } @$self;
}
sub offset {
my $self = shift;
return Slic3r::Geometry::Clipper::offset($self, @_);
@ -153,15 +159,13 @@ sub clip_line {
my $self = shift;
my ($line) = @_; # line must be a Slic3r::Line object
return Boost::Geometry::Utils::polygon_linestring_intersection(
$self->boost_polygon,
$line->boost_linestring,
);
return Boost::Geometry::Utils::polygon_multi_linestring_intersection($self, [$line]);
}
sub simplify {
my $self = shift;
$_->simplify(@_) for @$self;
$self;
}
sub scale {
@ -172,11 +176,13 @@ sub scale {
sub translate {
my $self = shift;
$_->translate(@_) for @$self;
$self;
}
sub rotate {
my $self = shift;
$_->rotate(@_) for @$self;
$self;
}
sub area {

View file

@ -13,12 +13,18 @@ use constant OPTIONS => [qw(
has 'id' => (is => 'rw', required => 1);
has $_ => (is => 'ro', required => 1) for @{&OPTIONS};
has 'bridge_flow' => (is => 'lazy');
has 'retracted' => (is => 'rw', default => sub {0} );
has 'restart_extra' => (is => 'rw', default => sub {0} );
has 'e_per_mm3' => (is => 'lazy');
has 'retract_speed_mm_min' => (is => 'lazy');
has '_mm3_per_mm_cache' => (is => 'ro', default => sub {{}});
sub _build_bridge_flow {
my $self = shift;
return Slic3r::Flow::Bridge->new(nozzle_diameter => $self->nozzle_diameter);
}
sub _build_e_per_mm3 {
my $self = shift;
return $self->extrusion_multiplier * (4 / (($self->filament_diameter ** 2) * PI));

View file

@ -93,6 +93,13 @@ sub endpoints {
sub is_printable { 1 }
sub is_fill {
my $self = shift;
return $self->role == EXTR_ROLE_FILL
|| $self->role == EXTR_ROLE_SOLIDFILL
|| $self->role == EXTR_ROLE_TOPSOLIDFILL;
}
sub split_at_acute_angles {
my $self = shift;

View file

@ -8,19 +8,19 @@ sub endpoints {
return [ map $_->endpoints, @{$self->paths} ];
}
sub shortest_path {
sub chained_path {
my $self = shift;
my ($start_near) = @_;
# make sure we pass the same path objects to the Collection constructor
# and the ->shortest_path() method because the latter will reverse the
# and the ->chained_path() method because the latter will reverse the
# paths in-place when needed and we need to return them that way
my @paths = map $_->unpack, @{$self->paths};
my $collection = Slic3r::Polyline::Collection->new(
polylines => [ map $_->polyline, @paths ],
);
return $collection->shortest_path($start_near, \@paths);
return $collection->chained_path($start_near, \@paths);
}
sub cleanup {

View file

@ -12,7 +12,7 @@ use Slic3r::Fill::OctagramSpiral;
use Slic3r::Fill::PlanePath;
use Slic3r::Fill::Rectilinear;
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(X Y PI scale shortest_path);
use Slic3r::Geometry qw(X Y PI scale chained_path);
use Slic3r::Geometry::Clipper qw(union_ex diff_ex);
use Slic3r::Surface ':types';
@ -48,17 +48,26 @@ sub filler {
sub make_fill {
my $self = shift;
my ($layer) = @_;
my ($layerm) = @_;
Slic3r::debugf "Filling layer %d:\n", $layer->id;
Slic3r::debugf "Filling layer %d:\n", $layerm->id;
# merge overlapping surfaces
my @surfaces = ();
# if hollow object is requested, remove internal surfaces
# (this needs to be done after internal-solid shells are created)
if ($Slic3r::Config->fill_density == 0) {
@surfaces = grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
}
# merge adjacent surfaces
# in case of bridge surfaces, the ones with defined angle will be attached to the ones
# without any angle (shouldn't this logic be moved to process_external_surfaces()?)
{
my @surfaces_with_bridge_angle = grep defined $_->bridge_angle, @{$layer->fill_surfaces};
my @surfaces_with_bridge_angle = grep defined $_->bridge_angle, @{$layerm->fill_surfaces};
# give priority to bridges
my @groups = Slic3r::Surface->group({merge_solid => 1}, @{$layer->fill_surfaces});
my @groups = Slic3r::Surface->group({merge_solid => 1}, @{$layerm->fill_surfaces});
@groups = sort { defined $a->[0]->bridge_angle ? -1 : 0 } @groups;
foreach my $group (@groups) {
@ -89,35 +98,10 @@ sub make_fill {
}
}
# add spacing between adjacent surfaces
# add spacing between surfaces
{
my $distance = $layer->infill_flow->scaled_spacing / 2;
my @offsets = ();
foreach my $surface (@surfaces) {
my $expolygon = $surface->expolygon;
my $diff = diff_ex(
[ $expolygon->offset($distance) ],
$expolygon,
1,
);
push @offsets, map @$_, @$diff;
}
my @new_surfaces = ();
foreach my $surface (@surfaces) {
my $diff = diff_ex(
$surface->expolygon,
[ @offsets ],
);
push @new_surfaces, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $surface->surface_type,
bridge_angle => $surface->bridge_angle,
depth_layers => $surface->depth_layers,
), @$diff;
}
@surfaces = @new_surfaces;
my $distance = $layerm->infill_flow->scaled_spacing / 2;
@surfaces = map $_->offset(-$distance), @surfaces;
}
my @fills = ();
@ -125,9 +109,11 @@ sub make_fill {
SURFACE: foreach my $surface (@surfaces) {
my $filler = $Slic3r::Config->fill_pattern;
my $density = $Slic3r::Config->fill_density;
my $flow_spacing = $layer->infill_flow->spacing;
my $is_bridge = $layer->id > 0 && $surface->surface_type == S_TYPE_BOTTOM;
my $is_solid = (grep { $surface->surface_type == $_ } S_TYPE_TOP, S_TYPE_BOTTOM, S_TYPE_INTERNALSOLID) ? 1 : 0;
my $flow_spacing = ($surface->surface_type == S_TYPE_TOP)
? $layerm->top_infill_flow->spacing
: $layerm->infill_flow->spacing;
my $is_bridge = $layerm->id > 0 && $surface->is_bridge;
my $is_solid = $surface->is_solid;
# force 100% density and rectilinear fill for external surfaces
if ($surface->surface_type != S_TYPE_INTERNAL) {
@ -135,7 +121,7 @@ sub make_fill {
$filler = $Slic3r::Config->solid_fill_pattern;
if ($is_bridge) {
$filler = 'rectilinear';
$flow_spacing = $layer->infill_flow->bridge_spacing;
$flow_spacing = $layerm->extruders->{infill}->bridge_flow->spacing;
} elsif ($surface->surface_type == S_TYPE_INTERNALSOLID) {
$filler = 'rectilinear';
}
@ -146,7 +132,7 @@ sub make_fill {
my @paths;
{
my $f = $self->filler($filler);
$f->layer_id($layer->id);
$f->layer_id($layerm->id);
@paths = $f->fill_surface(
$surface,
density => $density,
@ -157,7 +143,7 @@ sub make_fill {
my $params = shift @paths;
# ugly hack(tm) to get the right amount of flow (GCode.pm should be fixed)
$params->{flow_spacing} = $layer->infill_flow->bridge_width if $is_bridge;
$params->{flow_spacing} = $layerm->extruders->{infill}->bridge_flow->width if $is_bridge;
# save into layer
next unless @paths;
@ -170,7 +156,7 @@ sub make_fill {
: $is_solid
? ($surface->surface_type == S_TYPE_TOP ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL)
: EXTR_ROLE_FILL),
height => $surface->depth_layers * $layer->height,
height => $surface->depth_layers * $layerm->height,
flow_spacing => $params->{flow_spacing} || (warn "Warning: no flow_spacing was returned by the infill engine, please report this to the developer\n"),
), @paths,
],
@ -179,13 +165,11 @@ sub make_fill {
}
# add thin fill regions
push @fills, @{$layer->thin_fills};
push @fills_ordering_points, map $_->unpack->points->[0], @{$layer->thin_fills};
push @fills, @{$layerm->thin_fills};
push @fills_ordering_points, map $_->unpack->points->[0], @{$layerm->thin_fills};
# organize infill paths using a shortest path search
@fills = @{shortest_path([
map [ $fills_ordering_points[$_], $fills[$_] ], 0..$#fills,
])};
# organize infill paths using a nearest-neighbor search
@fills = @fills[ chained_path(\@fills_ordering_points) ];
return @fills;
}

View file

@ -93,7 +93,7 @@ sub fill_surface {
)};
return { flow_spacing => $params{flow_spacing} },
Slic3r::Polyline::Collection->new(polylines => \@paths)->shortest_path;
Slic3r::Polyline::Collection->new(polylines => \@paths)->chained_path;
}
1;

View file

@ -48,9 +48,9 @@ sub fill_surface {
# clip paths against a slightly offsetted expolygon, so that the first and last paths
# are kept even if the expolygon has vertical sides
my @paths = @{ Boost::Geometry::Utils::polygon_linestring_intersection(
+($expolygon->offset_ex(scaled_epsilon))[0]->boost_polygon, # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object
Boost::Geometry::Utils::linestring(@vertical_lines),
my @paths = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection(
+($expolygon->offset_ex(scaled_epsilon))[0], # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object
[ @vertical_lines ],
) };
for (@paths) {
$_->[0][Y] += $overlap_distance;
@ -73,7 +73,7 @@ sub fill_surface {
}
: sub { abs($_[X] - $distance_between_lines) <= $tolerance && $_[Y] <= $diagonal_distance };
foreach my $path ($collection->shortest_path) {
foreach my $path ($collection->chained_path) {
if (@paths) {
my @distance = map abs($path->[0][$_] - $paths[-1][-1][$_]), (X,Y);

View file

@ -9,8 +9,6 @@ has 'layer_height' => (is => 'ro', default => sub { $Slic3r::Config->layer_
has 'width' => (is => 'rwp', builder => 1);
has 'spacing' => (is => 'lazy');
has 'bridge_width' => (is => 'lazy');
has 'bridge_spacing' => (is => 'lazy');
has 'scaled_width' => (is => 'lazy');
has 'scaled_spacing' => (is => 'lazy');
@ -39,10 +37,10 @@ sub _build_width {
}
my $min = max(
((($self->nozzle_diameter/2) ** 2) / $self->layer_height * 0.8),
($volume / $self->layer_height),
($self->nozzle_diameter * 1.05),
);
my $max = $self->nozzle_diameter * 1.6;
my $max = $self->nozzle_diameter * 2;
$width = $max if $width > $max;
$width = $min if $width < $min;
@ -73,17 +71,6 @@ sub clone {
);
}
sub _build_bridge_width {
my $self = shift;
return sqrt($Slic3r::Config->bridge_flow_ratio * ($self->nozzle_diameter**2));
}
sub _build_bridge_spacing {
my $self = shift;
my $width = $self->bridge_width;
return $width + &Slic3r::OVERLAP_FACTOR * ($width * PI / 4 - $width);
}
sub _build_scaled_width {
my $self = shift;
return scale $self->width;
@ -94,4 +81,22 @@ sub _build_scaled_spacing {
return scale $self->spacing;
}
package Slic3r::Flow::Bridge;
use Moo;
extends 'Slic3r::Flow';
use Slic3r::Geometry qw(PI);
sub _build_width {
my $self = shift;
return sqrt($Slic3r::Config->bridge_flow_ratio * ($self->nozzle_diameter**2));
}
sub _build_spacing {
my $self = shift;
my $width = $self->width;
return $width + &Slic3r::OVERLAP_FACTOR * ($width * PI / 4 - $width);
}
1;

View file

@ -13,7 +13,7 @@ sub read_file {
1;
} or die "AMF parsing requires XML::SAX\n";
open my $fh, '<', $file or die "Failed to open $file\n";
Slic3r::open(\my $fh, '<', $file) or die "Failed to open $file\n";
my $model = Slic3r::Model->new;
XML::SAX::ParserFactory
@ -30,7 +30,7 @@ sub write_file {
my %vertices_offset = ();
open my $fh, '>', $file;
Slic3r::open(\my $fh, '>', $file);
binmode $fh, ':utf8';
printf $fh qq{<?xml version="1.0" encoding="UTF-8"?>\n};
printf $fh qq{<amf unit="millimeter">\n};

View file

@ -5,7 +5,7 @@ sub read_file {
my $self = shift;
my ($file) = @_;
open my $fh, '<', $file or die "Failed to open $file\n";
Slic3r::open(\my $fh, '<', $file) or die "Failed to open $file\n";
my $vertices = [];
my $facets = [];
while (my $_ = <$fh>) {

View file

@ -7,7 +7,7 @@ sub read_file {
my $self = shift;
my ($file) = @_;
open my $fh, '<', $file or die "Failed to open $file\n";
Slic3r::open(\my $fh, '<', $file) or die "Failed to open $file\n";
# let's detect whether file is ASCII or binary
my $mode;
@ -103,7 +103,7 @@ sub write_file {
my $self = shift;
my ($file, $model, %params) = @_;
open my $fh, '>', $file;
Slic3r::open(\my $fh, '>', $file);
$params{binary}
? _write_binary($fh, $model->mesh)

View file

@ -4,8 +4,10 @@ use Moo;
use List::Util qw(max first);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y B);
use Slic3r::Geometry::Clipper qw(union_ex);
has 'multiple_extruders' => (is => 'ro', default => sub {0} );
has 'layer_count' => (is => 'ro', required => 1 );
has 'layer' => (is => 'rw');
has 'move_z_callback' => (is => 'rw');
has 'shift_x' => (is => 'rw', default => sub {0} );
@ -13,6 +15,10 @@ has 'shift_y' => (is => 'rw', default => sub {0} );
has 'z' => (is => 'rw');
has 'speed' => (is => 'rw');
has 'external_mp' => (is => 'rw');
has 'layer_mp' => (is => 'rw');
has 'new_object' => (is => 'rw', default => sub {0});
has 'straight_once' => (is => 'rw', default => sub {1});
has 'extruder' => (is => 'rw');
has 'extrusion_distance' => (is => 'rw', default => sub {0} );
has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds
@ -34,7 +40,7 @@ has 'speeds' => (
default => sub {+{
map { $_ => 60 * $Slic3r::Config->get_value("${_}_speed") }
qw(travel perimeter small_perimeter external_perimeter infill
solid_infill top_solid_infill support_material bridge gap_fill),
solid_infill top_solid_infill support_material bridge gap_fill retract),
}},
);
@ -56,15 +62,36 @@ sub set_shift {
my $self = shift;
my @shift = @_;
# if shift increases (goes towards right), last_pos decreases because it goes towards left
$self->last_pos->translate(
scale ($shift[X] - $self->shift_x),
scale ($shift[Y] - $self->shift_y),
scale ($self->shift_x - $shift[X]),
scale ($self->shift_y - $shift[Y]),
);
$self->shift_x($shift[X]);
$self->shift_y($shift[Y]);
}
sub change_layer {
my $self = shift;
my ($layer) = @_;
$self->layer($layer);
if ($Slic3r::Config->avoid_crossing_perimeters) {
$self->layer_mp(Slic3r::GCode::MotionPlanner->new(
islands => union_ex([ map @$_, @{$layer->slices} ], undef, 1),
));
}
my $gcode = "";
if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) {
$gcode .= sprintf "M73 P%s%s\n",
int(99 * ($layer->id / ($self->layer_count - 1))),
($Slic3r::Config->gcode_comments ? ' ; update progress' : '');
}
return $gcode;
}
# this method accepts Z in scaled coordinates
sub move_z {
my $self = shift;
@ -137,8 +164,7 @@ sub extrude_loop {
$point->rotate($angle, $extrusion_path->polyline->[0]);
# generate the travel move
$self->speed('travel');
$gcode .= $self->G0($point, undef, 0, "move inwards before travel");
$gcode .= $self->travel_to($point, $loop->role, "move inwards before travel");
}
return $gcode;
@ -160,28 +186,9 @@ sub extrude_path {
return $gcode;
}
my $gcode = "";
# skip retract for support material
{
# retract if distance from previous position is greater or equal to the one specified by the user
my $travel = Slic3r::Line->new($self->last_pos->clone, $path->points->[0]->clone);
if ($travel->length >= scale $self->extruder->retract_before_travel
&& ($path->role != EXTR_ROLE_SUPPORTMATERIAL || !$self->layer->support_islands_enclose_line($travel))) {
# move travel back to original layer coordinates.
# note that we're only considering the current object's islands, while we should
# build a more complete configuration space
$travel->translate(-$self->shift_x, -$self->shift_y);
if (!$Slic3r::Config->only_retract_when_crossing_perimeters || $path->role != EXTR_ROLE_FILL || !first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) {
$gcode .= $self->retract(travel_to => $path->points->[0]);
}
}
}
# go to first point of extrusion path
$self->speed('travel');
$gcode .= $self->G0($path->points->[0], undef, 0, "move to first $description point")
if !points_coincide($self->last_pos, $path->points->[0]);
my $gcode = "";
$gcode .= $self->travel_to($path->points->[0], $path->role, "move to first $description point");
# compensate retraction
$gcode .= $self->unretract;
@ -234,6 +241,80 @@ sub extrude_path {
return $gcode;
}
sub travel_to {
my $self = shift;
my ($point, $role, $comment) = @_;
my $gcode = "";
my $travel = Slic3r::Line->new($self->last_pos->clone, $point->clone);
# move travel back to original layer coordinates for the island check.
# note that we're only considering the current object's islands, while we should
# build a more complete configuration space
$travel->translate(-$self->shift_x, -$self->shift_y);
if ($travel->length < scale $self->extruder->retract_before_travel
|| ($Slic3r::Config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices})
|| ($role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands_enclose_line($travel))
) {
$self->straight_once(0);
$self->speed('travel');
$gcode .= $self->G0($point, undef, 0, $comment || "");
} elsif (!$Slic3r::Config->avoid_crossing_perimeters || $self->straight_once) {
$self->straight_once(0);
$gcode .= $self->retract(travel_to => $point);
$self->speed('travel');
$gcode .= $self->G0($point, undef, 0, $comment || "");
} else {
my $plan = sub {
my $mp = shift;
my $gcode = "";
my @travel = $mp->shortest_path($self->last_pos, $point)->lines;
# if the path is not contained in a single island we need to retract
my $need_retract = !$Slic3r::Config->only_retract_when_crossing_perimeters;
if (!$need_retract) {
$need_retract = 1;
foreach my $slice (@{$self->layer->slices}) {
# discard the island if at any line is not enclosed in it
next if first { !$slice->encloses_line($_, scaled_epsilon) } @travel;
# okay, this island encloses the full travel path
$need_retract = 0;
last;
}
}
# do the retract (the travel_to argument is broken)
$gcode .= $self->retract(travel_to => $point) if $need_retract;
# append the actual path and return
$self->speed('travel');
$gcode .= join '', map $self->G0($_->[B], undef, 0, $comment || ""), @travel;
return $gcode;
};
if ($self->new_object) {
$self->new_object(0);
# represent $point in G-code coordinates
$point = $point->clone;
my @shift = ($self->shift_x, $self->shift_y);
$point->translate(map scale $_, @shift);
# calculate path (external_mp uses G-code coordinates so we temporary need a null shift)
$self->set_shift(0,0);
$gcode .= $plan->($self->external_mp);
$self->set_shift(@shift);
} else {
$gcode .= $plan->($self->layer_mp);
}
}
return $gcode;
}
sub retract {
my $self = shift;
my %params = @_;
@ -323,9 +404,9 @@ sub reset_e {
sub set_acceleration {
my $self = shift;
my ($acceleration) = @_;
return "" unless $Slic3r::Config->acceleration;
return "" if !$acceleration;
return sprintf "M201 E%s%s\n",
return sprintf "M204 S%s%s\n",
$acceleration, ($Slic3r::Config->gcode_comments ? ' ; adjust acceleration' : '');
}
@ -452,8 +533,18 @@ sub set_extruder {
# set the new extruder
$self->extruder($extruder);
$gcode .= sprintf "T%d%s\n", $extruder->id, ($Slic3r::Config->gcode_comments ? ' ; change extruder' : '');
$gcode .= $self->reset_e;
my $toolchange_gcode = sprintf "%s%d%s\n",
($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M108 T' : 'T'),
$extruder->id,
($Slic3r::Config->gcode_comments ? ' ; change extruder' : '');
if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) {
$gcode .= $self->reset_e;
$gcode .= $toolchange_gcode;
} else {
$gcode .= $toolchange_gcode;
$gcode .= $self->reset_e;
}
return $gcode;
}
@ -467,11 +558,17 @@ sub set_fan {
if ($speed == 0) {
my $code = $Slic3r::Config->gcode_flavor eq 'teacup'
? 'M106 S0'
: 'M107';
: $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/
? 'M127'
: 'M107';
return sprintf "$code%s\n", ($Slic3r::Config->gcode_comments ? ' ; disable fan' : '');
} else {
return sprintf "M106 %s%d%s\n", ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'),
(255 * $speed / 100), ($Slic3r::Config->gcode_comments ? ' ; enable fan' : '');
if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) {
return sprintf "M126%s\n", ($Slic3r::Config->gcode_comments ? ' ; enable fan' : '');
} else {
return sprintf "M106 %s%d%s\n", ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'),
(255 * $speed / 100), ($Slic3r::Config->gcode_comments ? ' ; enable fan' : '');
}
}
}
return "";
@ -481,14 +578,14 @@ sub set_temperature {
my $self = shift;
my ($temperature, $wait, $tool) = @_;
return "" if $wait && $Slic3r::Config->gcode_flavor eq 'makerbot';
return "" if $wait && $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/;
my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup')
? ('M109', 'wait for temperature to be reached')
: ('M104', 'set temperature');
my $gcode = sprintf "$code %s%d %s; $comment\n",
($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature,
(defined $tool && $self->multiple_extruders) ? "T$tool " : "";
(defined $tool && ($self->multiple_extruders || $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/)) ? "T$tool " : "";
$gcode .= "M116 ; wait for temperature to be reached\n"
if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait;
@ -501,8 +598,7 @@ sub set_bed_temperature {
my ($temperature, $wait) = @_;
my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup')
? (($Slic3r::Config->gcode_flavor eq 'makerbot' ? 'M109'
: 'M190'), 'wait for bed temperature to be reached')
? (($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached')
: ('M140', 'set bed temperature');
my $gcode = sprintf "$code %s%d ; $comment\n",
($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature;

View file

@ -0,0 +1,282 @@
package Slic3r::GCode::MotionPlanner;
use Moo;
has 'islands' => (is => 'ro', required => 1);
has 'no_internal' => (is => 'ro');
has 'last_crossings'=> (is => 'rw');
has '_inner' => (is => 'rw', default => sub { [] }); # arrayref of arrayrefs of expolygons
has '_outer' => (is => 'rw', default => sub { [] }); # arrayref of arrayrefs of polygons
has '_contours_ex' => (is => 'rw', default => sub { [] }); # arrayref of arrayrefs of expolygons
has '_pointmap' => (is => 'rw', default => sub { {} }); # { id => $point }
has '_edges' => (is => 'rw', default => sub { {} }); # node_idx => { node_idx => distance, ... }
has '_crossing_edges' => (is => 'rw', default => sub { {} }); # edge_idx => bool
use List::Util qw(first);
use Slic3r::Geometry qw(A B scale epsilon nearest_point);
use Slic3r::Geometry::Clipper qw(diff_ex JT_MITER);
# clearance (in mm) from the perimeters
has '_inner_margin' => (is => 'ro', default => sub { scale 0.5 });
has '_outer_margin' => (is => 'ro', default => sub { scale 2 });
# this factor weigths the crossing of a perimeter
# vs. the alternative path. a value of 5 means that
# a perimeter will be crossed if the alternative path
# is >= 5x the length of the straight line we could
# follow if we decided to cross the perimeter.
# a nearly-infinite value for this will only permit
# perimeter crossing when there's no alternative path.
use constant CROSSING_FACTOR => 20;
use constant INFINITY => 'inf';
# setup our configuration space
sub BUILD {
my $self = shift;
my $edges = $self->_edges;
my $crossing_edges = $self->_crossing_edges;
my $tolerance = scale epsilon;
# given an expolygon, this subroutine connects all its visible points
my $add_expolygon = sub {
my ($expolygon, $crosses_perimeter) = @_;
my @points = map @$_, @$expolygon;
for my $i (0 .. $#points) {
for my $j (($i+1) .. $#points) {
my $line = Slic3r::Line->new($points[$i], $points[$j]);
if ($expolygon->encloses_line($line, $tolerance)) {
my $dist = $line->length * ($crosses_perimeter ? CROSSING_FACTOR : 1);
$edges->{$points[$i]}{$points[$j]} = $dist;
$edges->{$points[$j]}{$points[$i]} = $dist;
$crossing_edges->{$points[$i]}{$points[$j]} = 1;
$crossing_edges->{$points[$j]}{$points[$i]} = 1;
}
}
}
};
# process individual islands
for my $i (0 .. $#{$self->islands}) {
# simplify the island's contours
$self->islands->[$i]->simplify($self->_inner_margin);
# offset the island inwards to make the boundaries for internal movements
# so that no motion along external perimeters happens
$self->_inner->[$i] = [ $self->islands->[$i]->offset_ex(-$self->_inner_margin) ]
if !$self->no_internal;
# offset the island outwards to make the boundaries for external movements
$self->_outer->[$i] = [ $self->islands->[$i]->contour->offset($self->_outer_margin) ];
# further simplification (isn't this a duplication of the one above?)
$_->simplify($self->_inner_margin) for @{$self->_inner->[$i]}, @{$self->_outer->[$i]};
# if internal motion is enabled, build a set of utility expolygons representing
# the outer boundaries (as contours) and the inner boundaries (as holes). whenever
# we jump from a hole to a contour or viceversa, we know we're crossing a perimeter
if (!$self->no_internal) {
$self->_contours_ex->[$i] = diff_ex(
$self->_outer->[$i],
[ map $_->contour, @{$self->_inner->[$i]} ],
);
# lines enclosed in inner expolygons are visible
$add_expolygon->($_) for @{ $self->_inner->[$i] };
# lines enclosed in expolygons covering perimeters are visible
# (but discouraged)
$add_expolygon->($_, 1) for @{ $self->_contours_ex->[$i] };
}
}
my $intersects = sub {
my ($polygon, $line) = @_;
@{Boost::Geometry::Utils::polygon_multi_linestring_intersection([$polygon], [$line])} > 0;
};
{
my @outer = (map @$_, @{$self->_outer});
# lines of outer polygons connect visible points
for my $i (0 .. $#outer) {
foreach my $line ($outer[$i]->lines) {
my $dist = $line->length;
$edges->{$line->[A]}{$line->[B]} = $dist;
$edges->{$line->[B]}{$line->[A]} = $dist;
}
}
# lines connecting outer polygons are visible
for my $i (0 .. $#outer) {
for my $j (($i+1) .. $#outer) {
for my $m (0 .. $#{$outer[$i]}) {
for my $n (0 .. $#{$outer[$j]}) {
my $line = Slic3r::Line->new($outer[$i][$m], $outer[$j][$n]);
if (!first { $intersects->($_, $line) } @outer) {
# this line does not cross any polygon
my $dist = $line->length;
$edges->{$outer[$i][$m]}{$outer[$j][$n]} = $dist;
$edges->{$outer[$j][$n]}{$outer[$i][$m]} = $dist;
}
}
}
}
}
}
# lines connecting inner polygons contours are visible but discouraged
if (!$self->no_internal) {
my @inner = (map $_->contour, map @$_, @{$self->_inner});
for my $i (0 .. $#inner) {
for my $j (($i+1) .. $#inner) {
for my $m (0 .. $#{$inner[$i]}) {
for my $n (0 .. $#{$inner[$j]}) {
my $line = Slic3r::Line->new($inner[$i][$m], $inner[$j][$n]);
if (!first { $intersects->($_, $line) } @inner) {
# this line does not cross any polygon
my $dist = $line->length * CROSSING_FACTOR;
$edges->{$inner[$i][$m]}{$inner[$j][$n]} = $dist;
$edges->{$inner[$j][$n]}{$inner[$i][$m]} = $dist;
$crossing_edges->{$inner[$i][$m]}{$inner[$j][$n]} = 1;
$crossing_edges->{$inner[$j][$n]}{$inner[$i][$m]} = 1;
}
}
}
}
}
}
$self->_pointmap({
map +("$_" => $_),
(map @$_, map @$_, map @$_, @{$self->_inner}),
(map @$_, map @$_, @{$self->_outer}),
(map @$_, map @$_, map @$_, @{$self->_contours_ex}),
});
if (0) {
my @lines = ();
my %lines = ();
for my $i (keys %{$self->_edges}) {
for my $j (keys %{$self->_edges->{$i}}) {
next if $lines{join '_', sort $i, $j};
push @lines, [ map $self->_pointmap->{$_}, $i, $j ];
$lines{join '_', sort $i, $j} = 1;
}
}
require "Slic3r/SVG.pm";
Slic3r::SVG::output("space.svg",
lines => \@lines,
points => [ values %{$self->_pointmap} ],
no_arrows => 1,
polygons => [ map @$_, @{$self->islands} ],
#red_polygons => [ map $_->holes, map @$_, @{$self->_inner} ],
#white_polygons => [ map @$_, @{$self->_outer} ],
);
printf "%d islands\n", scalar @{$self->islands};
}
}
sub find_node {
my $self = shift;
my ($point, $near_to) = @_;
# for optimal pathing, we should check visibility from $point to all $candidates, and then
# choose the one that is nearest to $near_to among the visible ones; however this is probably too slow
# if we're inside a hole, move to a point on hole;
{
my $polygon = first { $_->encloses_point($point) } (map $_->holes, map @$_, @{$self->_inner});
return nearest_point($point, $polygon) if $polygon;
}
# if we're inside an expolygon move to a point on contour or holes
{
my $expolygon = first { $_->encloses_point_quick($point) } (map @$_, @{$self->_inner});
return nearest_point($point, [ map @$_, @$expolygon ]) if $expolygon;
}
{
my $outer_polygon_idx;
if (!$self->no_internal) {
# look for an outer expolygon whose contour contains our point
$outer_polygon_idx = first { first { $_->contour->encloses_point($point) } @{$self->_contours_ex->[$_]} }
0 .. $#{ $self->_contours_ex };
} else {
# # look for an outer expolygon containing our point
$outer_polygon_idx = first { first { $_->encloses_point($point) } @{$self->_outer->[$_]} }
0 .. $#{ $self->_outer };
}
my $candidates = defined $outer_polygon_idx
? [ map @{$_->contour}, @{$self->_inner->[$outer_polygon_idx]} ]
: [ map @$_, map @$_, @{$self->_outer} ];
$candidates = [ map @$_, @{$self->_outer->[$outer_polygon_idx]} ]
if @$candidates == 0;
return nearest_point($point, $candidates);
}
}
sub shortest_path {
my $self = shift;
my ($from, $to) = @_;
return Slic3r::Polyline->new($from, $to) if !@{$self->islands};
# find nearest nodes
my $new_from = $self->find_node($from, $to);
my $new_to = $self->find_node($to, $from);
my $root = "$new_from";
my $target = "$new_to";
my $edges = $self->_edges;
my %dist = map { $_ => INFINITY } keys %$edges;
$dist{$root} = 0;
my %prev = map { $_ => undef } keys %$edges;
my @unsolved = keys %$edges;
my %crossings = (); # node_idx => bool
while (@unsolved) {
# sort unsolved by distance from root
# using a sorting option that accounts for infinity
@unsolved = sort {
$dist{$a} eq INFINITY ? +1 :
$dist{$b} eq INFINITY ? -1 :
$dist{$a} <=> $dist{$b};
} @unsolved;
# we'll solve the closest node
last if $dist{$unsolved[0]} eq INFINITY;
my $n = shift @unsolved;
# stop search
last if $n eq $target;
# now, look at all the nodes connected to n
foreach my $n2 (keys %{$edges->{$n}}) {
# .. and find out if any of their estimated distances
# can be improved if we go through n
if ( ($dist{$n2} eq INFINITY) || ($dist{$n2} > ($dist{$n} + $edges->{$n}{$n2})) ) {
$dist{$n2} = $dist{$n} + $edges->{$n}{$n2};
$prev{$n2} = $n;
$crossings{$n} = 1 if $self->_crossing_edges->{$n}{$n2};
}
}
}
my @points = ();
my $crossings = 0;
{
my $pointmap = $self->_pointmap;
my $u = $target;
while (defined $prev{$u}) {
unshift @points, $pointmap->{$u};
$crossings++ if $crossings{$u};
$u = $prev{$u};
}
}
$self->last_crossings($crossings);
return Slic3r::Polyline->new($from, $new_from, @points, $to); # @points already includes $new_to
}
1;

View file

@ -60,14 +60,15 @@ sub OnInit {
# locate or create data directory
$datadir ||= Wx::StandardPaths::Get->GetUserDataDir;
Slic3r::debugf "Data directory: %s\n", $datadir;
my $run_wizard = (-d $datadir) ? 0 : 1;
for ($datadir, "$datadir/print", "$datadir/filament", "$datadir/printer") {
my $encoded_datadir = Slic3r::encode_path($datadir);
my $run_wizard = (-d $encoded_datadir) ? 0 : 1;
for ($encoded_datadir, "$encoded_datadir/print", "$encoded_datadir/filament", "$encoded_datadir/printer") {
mkdir or $self->fatal_error("Slic3r was unable to create its data directory at $_ (errno: $!).")
unless -d $_;
}
# load settings
if (-f "$datadir/slic3r.ini") {
if (-f "$encoded_datadir/slic3r.ini") {
my $ini = eval { Slic3r::Config->read_ini("$datadir/slic3r.ini") };
$Settings = $ini if $ini;
$Settings->{_}{mode} ||= 'expert';
@ -422,8 +423,9 @@ sub notify {
my $notifier = $serv->get_object('/org/freedesktop/Notifications',
'org.freedesktop.Notifications');
$notifier->Notify('Slic3r', 0, $self->{icon}, $title, $message, [], {}, -1);
}
};
undef $Net::DBus::bus_session;
};
}
}
1;

View file

@ -47,12 +47,12 @@ sub new {
'<html>' .
'<body bgcolor="#ffffff" link="#808080">' .
'<font color="#808080">' .
'Copyright &copy; 2011-2012 Alessandro Ranellucci. All rights reserved. ' .
'Copyright &copy; 2011-2013 Alessandro Ranellucci. All rights reserved. ' .
'<a href="http://slic3r.org/">Slic3r</a> is licensed under the ' .
'<a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, version 3</a>.' .
'<br /><br /><br />' .
'Slic3r logo designed by Corey Daniels, <a href="http://www.famfamfam.com/lab/icons/silk/">Silk Icon Set</a> designed by Mark James. ' .
'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess and numerous others.' .
'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Mike Sheldrake and numerous others.' .
'</font>' .
'</body>' .
'</html>';

View file

@ -62,7 +62,7 @@ sub new {
Wx::ToolTip::Enable(1);
$self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL);
$self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_LESS, "Less", Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_LESS, "Fewer", Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddSeparator;
$self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_anticlockwise.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), '');
@ -1094,7 +1094,7 @@ sub _trigger_model_object {
sub check_manifoldness {
my $self = shift;
$self->is_manifold($self->get_model_object->mesh->check_manifoldness);
$self->is_manifold($self->get_model_object->check_manifoldness);
return $self->is_manifold;
}

View file

@ -18,7 +18,7 @@ use constant FILE_WILDCARDS => {
obj => 'OBJ files (*.obj)|*.obj;*.OBJ',
amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML',
ini => 'INI files *.ini|*.ini;*.INI',
gcode => 'G-code files *.gcode|*.gcode;*.GCODE|G-code files *.g|*.g;*.G',
gcode => 'G-code files (*.gcode, *.gco, *.g)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G',
svg => 'SVG files *.svg|*.svg;*.SVG',
};
use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(stl obj amf)};

View file

@ -466,6 +466,10 @@ sub build {
},
],
},
{
title => 'Advanced',
options => [qw(avoid_crossing_perimeters)],
},
]);
$self->add_options_page('Infill', 'shading.png', optgroups => [
@ -475,7 +479,8 @@ sub build {
},
{
title => 'Advanced',
options => [qw(infill_every_layers solid_infill_every_layers fill_angle solid_infill_below_area only_retract_when_crossing_perimeters)],
options => [qw(infill_every_layers infill_only_where_needed solid_infill_every_layers fill_angle
solid_infill_below_area only_retract_when_crossing_perimeters)],
},
]);
@ -492,6 +497,10 @@ sub build {
title => 'Modifiers',
options => [qw(first_layer_speed)],
},
{
title => 'Acceleration control (advanced)',
options => [qw(perimeter_acceleration infill_acceleration default_acceleration)],
},
]);
$self->add_options_page('Skirt and brim', 'box.png', optgroups => [
@ -508,7 +517,16 @@ sub build {
$self->add_options_page('Support material', 'building.png', optgroups => [
{
title => 'Support material',
options => [qw(support_material support_material_threshold support_material_pattern support_material_spacing support_material_angle)],
options => [qw(support_material support_material_threshold support_material_enforce_layers)],
},
{
title => 'Raft',
options => [qw(raft_layers)],
},
{
title => 'Options for support material and raft',
options => [qw(support_material_pattern support_material_spacing support_material_angle
support_material_interface_layers support_material_interface_spacing)],
},
]);
@ -554,7 +572,7 @@ sub build {
{
title => 'Extrusion width',
label_width => 180,
options => [qw(extrusion_width first_layer_extrusion_width perimeter_extrusion_width infill_extrusion_width support_material_extrusion_width)],
options => [qw(extrusion_width first_layer_extrusion_width perimeter_extrusion_width infill_extrusion_width top_infill_extrusion_width support_material_extrusion_width)],
},
{
title => 'Flow',

View file

@ -7,6 +7,7 @@ our @ISA = qw(Exporter);
our @EXPORT_OK = qw(
PI X Y Z A B X1 Y1 X2 Y2 MIN MAX epsilon slope line_atan lines_parallel
line_point_belongs_to_segment points_coincide distance_between_points
comparable_distance_between_points chained_path_items chained_path_points
line_length midpoint point_in_polygon point_in_segment segment_in_segment
point_is_on_left_of_segment polyline_lines polygon_lines nearest_point
point_along_segment polygon_segment_having_point polygon_has_subsegment
@ -17,7 +18,7 @@ our @EXPORT_OK = qw(
longest_segment angle3points three_points_aligned line_direction
polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices
polygon_remove_acute_vertices polygon_remove_parallel_continuous_edges
shortest_path collinear scale unscale merge_collinear_lines
chained_path collinear scale unscale merge_collinear_lines
rad2deg_dir bounding_box_center line_intersects_any douglas_peucker
polyline_remove_short_segments normal triangle_normal polygon_is_convex
scaled_epsilon bounding_box_3D size_3D size_2D
@ -114,6 +115,11 @@ sub distance_between_points {
return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2);
}
sub comparable_distance_between_points {
my ($p1, $p2) = @_;
return (($p1->[X] - $p2->[X])**2) + (($p1->[Y] - $p2->[Y])**2);
}
sub point_line_distance {
my ($point, $line) = @_;
return distance_between_points($point, $line->[A])
@ -251,7 +257,7 @@ sub nearest_point_index {
my ($nearest_point_index, $distance) = ();
for my $i (0..$#$points) {
my $d = distance_between_points($point, $points->[$i]);
my $d = comparable_distance_between_points($point, $points->[$i]);
if (!defined $distance || $d < $distance) {
$nearest_point_index = $i;
$distance = $d;
@ -793,29 +799,43 @@ sub polyline_remove_short_segments {
}
}
# accepts an arrayref; each item should be an arrayref whose first
# item is the point to be used for the shortest path, and the second
# one is the value to be returned in output (if the second item
# is not provided, the point will be returned)
sub shortest_path {
# accepts an arrayref of points; it returns a list of indices
# according to a nearest-neighbor walk
sub chained_path {
my ($items, $start_near) = @_;
my %values = map +($_->[0] => $_->[1] || $_->[0]), @$items;
my @points = map $_->[0], @$items;
my @points = @$items;
my %indices = map { $points[$_] => $_ } 0 .. $#points;
my $result = [];
my @result = ();
my $last_point;
if (!$start_near) {
$start_near = shift @points;
push @$result, $values{$start_near} if $start_near;
push @result, $indices{$start_near} if $start_near;
}
while (@points) {
$start_near = nearest_point($start_near, [@points]);
@points = grep $_ ne $start_near, @points;
push @$result, $values{$start_near};
push @result, $indices{$start_near};
}
return $result;
return @result;
}
# accepts an arrayref; each item should be an arrayref whose first
# item is the point to be used for the shortest path, and the second
# one is the value to be returned in output (if the second item
# is not provided, the point will be returned)
sub chained_path_items {
my ($items, $start_near) = @_;
my @indices = chained_path([ map $_->[0], @$items ], $start_near);
return [ map $_->[1], @$items[@indices] ];
}
sub chained_path_points {
my ($points, $start_near) = @_;
return [ @$points[ chained_path($points, $start_near) ] ];
}
sub douglas_peucker {

View file

@ -8,7 +8,7 @@ our @EXPORT_OK = qw(safety_offset offset offset_ex
diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND
JT_SQUARE is_counter_clockwise);
use Math::Clipper 1.15 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area);
use Math::Clipper 1.17 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area);
use Slic3r::Geometry qw(scale);
our $clipper = Math::Clipper->new;
@ -21,7 +21,7 @@ sub offset {
my ($polygons, $distance, $scale, $joinType, $miterLimit) = @_;
$scale ||= 100000;
$joinType //= JT_MITER;
$miterLimit //= 10;
$miterLimit //= 3;
my $offsets = Math::Clipper::offset($polygons, $distance, $scale, $joinType, $miterLimit);
return @$offsets;

View file

@ -2,6 +2,7 @@ package Slic3r::Layer;
use Moo;
use List::Util qw(first);
use Slic3r::Geometry qw(scale);
use Slic3r::Geometry::Clipper qw(union_ex);
has 'id' => (is => 'rw', required => 1, trigger => 1); # sequential number of layer, 0-based
@ -12,7 +13,6 @@ has 'slicing_errors' => (is => 'rw');
has 'slice_z' => (is => 'lazy');
has 'print_z' => (is => 'lazy');
has 'height' => (is => 'lazy');
has 'flow' => (is => 'lazy');
# collection of expolygons generated by slicing the original geometry;
# also known as 'islands' (all regions are merged here)
@ -21,7 +21,7 @@ has 'slices' => (is => 'rw');
# ordered collection of extrusion paths to fill surfaces for support material
has 'support_islands' => (is => 'rw');
has 'support_fills' => (is => 'rw');
has 'support_interface_fills' => (is => 'rw');
has 'support_contact_fills' => (is => 'rw');
sub _trigger_id {
my $self = shift;
@ -32,11 +32,16 @@ sub _trigger_id {
sub _build_slice_z {
my $self = shift;
if ($self->id == 0) {
return $Slic3r::Config->get_value('first_layer_height') / 2 / &Slic3r::SCALING_FACTOR;
if ($Slic3r::Config->raft_layers == 0) {
if ($self->id == 0) {
return scale $Slic3r::Config->get_value('first_layer_height') / 2;
}
return scale($Slic3r::Config->get_value('first_layer_height') + ($self->id-1 + 0.5) * $Slic3r::Config->layer_height);
} else {
return -1 if $self->id < $Slic3r::Config->raft_layers;
my $object_layer_id = $self->id - $Slic3r::Config->raft_layers;
return scale ($object_layer_id + 0.5) * $Slic3r::Config->layer_height;
}
return ($Slic3r::Config->get_value('first_layer_height') + (($self->id-1) * $Slic3r::Config->layer_height) + ($Slic3r::Config->layer_height/2))
/ &Slic3r::SCALING_FACTOR; #/
}
# Z used for printing in scaled coordinates
@ -51,25 +56,30 @@ sub _build_height {
return $self->id == 0 ? $Slic3r::Config->get_value('first_layer_height') : $Slic3r::Config->layer_height;
}
sub _build_flow { $Slic3r::flow }
# layer height of interface paths in unscaled coordinates
sub support_material_interface_height {
# layer height of contact paths in unscaled coordinates
sub support_material_contact_height {
my $self = shift;
return $self->height if $self->id == 0;
# this is not very correct because:
# - we should sum our height with the actual upper layers height (which might be different)
# - we should use the actual flow of the upper layer bridges, not the default one
# ...but we're close enough for now
return 2*$self->height - $self->flow->nozzle_diameter;
# TODO: check what upper region applies instead of considering the first one
my $upper_layer = $self->object->layers->[ $self->id + 1 ] // $self;
my $h = ($self->height + $upper_layer->height) - $upper_layer->regions->[0]->extruders->{infill}->bridge_flow->width;
# If layer height is less than half the bridge width then we'll get a negative height for contact area.
# The optimal solution would be to skip some layers during support material generation, but for now
# we'll apply a (dirty) workaround that should still work.
if ($h <= 0) {
$h = $self->height;
}
return $h;
}
# Z used for printing support material interface in scaled coordinates
sub support_material_interface_z {
# Z used for printing support material contact in scaled coordinates
sub support_material_contact_z {
my $self = shift;
return $self->print_z - ($self->height - $self->support_material_interface_height) / &Slic3r::SCALING_FACTOR;
return $self->print_z - ($self->height - $self->support_material_contact_height) / &Slic3r::SCALING_FACTOR;
}
sub region {
@ -109,6 +119,7 @@ sub make_perimeters {
sub support_islands_enclose_line {
my $self = shift;
my ($line) = @_;
return 0 if !$self->support_islands; # why can we arrive here if there are no support islands?
return (first { $_->encloses_line($line) } @{$self->support_islands}) ? 1 : 0;
}

View file

@ -2,7 +2,7 @@ package Slic3r::Layer::Region;
use Moo;
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale shortest_path);
use Slic3r::Geometry qw(PI scale chained_path_items points_coincide);
use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex);
use Slic3r::Surface ':types';
@ -13,9 +13,12 @@ has 'layer' => (
trigger => 1,
handles => [qw(id slice_z print_z height flow)],
);
has 'region' => (is => 'ro', required => 1);
has 'region' => (is => 'ro', required => 1, handles => [qw(extruders)]);
has 'perimeter_flow' => (is => 'rw');
has 'infill_flow' => (is => 'rw');
has 'top_infill_flow' => (is => 'rw');
has 'infill_area_threshold' => (is => 'lazy');
has 'overhang_width' => (is => 'lazy');
# collection of spare segments generated by slicing the original geometry;
# these need to be merged in continuos (closed) polylines
@ -55,13 +58,29 @@ sub _update_flows {
my $self = shift;
return if !$self->region;
$self->perimeter_flow($self->id == 0
? $self->region->first_layer_flows->{perimeter}
: $self->region->flows->{perimeter});
$self->infill_flow($self->id == 0
? $self->region->first_layer_flows->{infill}
: $self->region->flows->{infill});
if ($self->id == 0) {
$self->perimeter_flow
($self->region->first_layer_flows->{perimeter} || $self->region->flows->{perimeter});
$self->infill_flow
($self->region->first_layer_flows->{infill} || $self->region->flows->{infill});
$self->top_infill_flow
($self->region->first_layer_flows->{top_infill} || $self->region->flows->{top_infill});
} else {
$self->perimeter_flow($self->region->flows->{perimeter});
$self->infill_flow($self->region->flows->{infill});
$self->top_infill_flow($self->region->flows->{top_infill});
}
}
sub _build_overhang_width {
my $self = shift;
my $threshold_rad = PI/2 - atan2($self->perimeter_flow->width / $self->height / 2, 1);
return scale($self->height * ((cos $threshold_rad) / (sin $threshold_rad)));
}
sub _build_infill_area_threshold {
my $self = shift;
return $self->infill_flow->scaled_spacing ** 2;
}
# build polylines from lines
@ -72,28 +91,18 @@ sub make_surfaces {
return if !@$loops;
$self->slices([ _merge_loops($loops) ]);
# the contours must be offsetted by half extrusion width inwards
# detect thin walls by offsetting slices by half extrusion inwards
{
my $distance = $self->perimeter_flow->scaled_width / 2;
my @surfaces = @{$self->slices};
@{$self->slices} = ();
foreach my $surface (@surfaces) {
push @{$self->slices}, map Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL),
@{union_ex([
Slic3r::Geometry::Clipper::offset(
[Slic3r::Geometry::Clipper::offset($surface->expolygon, -2*$distance)],
+$distance,
),
])};
}
# now detect thin walls by re-outgrowing offsetted surfaces and subtracting
# them from the original slices
my $outgrown = [ Slic3r::Geometry::Clipper::offset([ map $_->p, @{$self->slices} ], $distance) ];
my $width = $self->perimeter_flow->scaled_width;
my $outgrown = union_ex([
Slic3r::Geometry::Clipper::offset(
[Slic3r::Geometry::Clipper::offset([ map @$_, map $_->expolygon, @{$self->slices} ], -$width)],
+$width,
),
]);
my $diff = diff_ex(
[ map $_->p, @surfaces ],
$outgrown,
[ map $_->p, @{$self->slices} ],
[ map @$_, @$outgrown ],
1,
);
@ -150,6 +159,8 @@ sub _merge_loops {
sub make_perimeters {
my $self = shift;
my $perimeter_spacing = $self->perimeter_flow->scaled_spacing;
my $infill_spacing = $self->infill_flow->scaled_spacing;
my $gap_area_threshold = $self->perimeter_flow->scaled_width ** 2;
# this array will hold one arrayref per original surface (island);
@ -166,8 +177,8 @@ sub make_perimeters {
# )
my @perimeters = (); # one item per depth; each item
# organize islands using a shortest path search
my @surfaces = @{shortest_path([
# organize islands using a nearest-neighbor search
my @surfaces = @{chained_path_items([
map [ $_->contour->[0], $_ ], @{$self->slices},
])};
@ -187,7 +198,7 @@ sub make_perimeters {
# this compensation only works for circular holes, while it would
# overcompensate for hexagons and other shapes having straight edges.
# so we require a minimum number of vertices.
next unless $circumference / @$hole >= 3 * $Slic3r::flow->scaled_width;
next unless $circumference / @$hole >= 3 * $self->perimeter_flow->scaled_width;
# revert the compensation done in make_surfaces() and get the actual radius
# of the hole
@ -202,23 +213,25 @@ sub make_perimeters {
}
}
my $distance = $self->perimeter_flow->scaled_spacing;
my @gaps = ();
# generate perimeters inwards (loop 0 is the external one)
my $loop_number = $Slic3r::Config->perimeters + ($surface->additional_inner_perimeters || 0);
push @perimeters, [[@last_offsets]] if $loop_number > 0;
push @perimeters, [] if $loop_number > 0;
# do one more loop (<= instead of <) so that we can detect gaps even after the desired
# number of perimeters has been generated
for (my $loop = 1; $loop <= $loop_number; $loop++) {
for (my $loop = 0; $loop <= $loop_number; $loop++) {
my $spacing = $perimeter_spacing;
$spacing /= 2 if $loop == 0;
# offsetting a polygon can result in one or many offset polygons
my @new_offsets = ();
foreach my $expolygon (@last_offsets) {
my @offsets = @{union_ex([
Slic3r::Geometry::Clipper::offset(
[Slic3r::Geometry::Clipper::offset($expolygon, -1.5*$distance)],
+0.5*$distance,
[Slic3r::Geometry::Clipper::offset($expolygon, -1.5*$spacing)],
+0.5*$spacing,
),
])};
push @new_offsets, @offsets;
@ -226,23 +239,34 @@ sub make_perimeters {
# where the above check collapses the expolygon, then there's no room for an inner loop
# and we can extract the gap for later processing
my $diff = diff_ex(
[ map @$_, $expolygon->offset_ex(-0.5*$distance) ],
[ Slic3r::Geometry::Clipper::offset([map @$_, @offsets], +0.5*$distance) ],
[ map @$_, $expolygon->offset_ex(-0.5*$spacing) ],
# +2 on the offset here makes sure that Clipper float truncation
# won't shrink the clip polygon to be smaller than intended.
[ Slic3r::Geometry::Clipper::offset([map @$_, @offsets], +0.5*$spacing + 2) ],
);
push @gaps, grep $_->area >= $gap_area_threshold, @$diff;
}
last if !@new_offsets || $loop == $loop_number;
@last_offsets = @new_offsets;
# sort loops before storing them
@last_offsets = @{chained_path_items([
map [ $_->contour->[0], $_ ], @last_offsets,
])};
push @{ $perimeters[-1] }, [@last_offsets];
}
# create one more offset to be used as boundary for fill
{
# we offset by half the perimeter spacing (to get to the actual infill boundary)
# and then we offset back and forth by the infill spacing to only consider the
# non-collapsing regions
my @fill_boundaries = @{union_ex([
Slic3r::Geometry::Clipper::offset(
[Slic3r::Geometry::Clipper::offset([ map @$_, @last_offsets ], -1.5*$distance)],
+0.5*$distance,
[Slic3r::Geometry::Clipper::offset([ map @$_, @last_offsets ], -($perimeter_spacing/2 + $infill_spacing))],
+$infill_spacing,
),
])};
$_->simplify(&Slic3r::SCALED_RESOLUTION) for @fill_boundaries;
@ -250,7 +274,7 @@ sub make_perimeters {
}
# fill gaps
if ($Slic3r::Config->gap_fill_speed > 0) {
if ($Slic3r::Config->gap_fill_speed > 0 && $Slic3r::Config->fill_density > 0) {
my $filler = Slic3r::Fill::Rectilinear->new(layer_id => $self->layer->id);
my $w = $self->perimeter_flow->width;
@ -317,18 +341,24 @@ sub make_perimeters {
}
# process one island (original surface) at time
# islands are already sorted with a nearest-neighbor search
foreach my $island (@perimeters) {
# do holes starting from innermost one
my @holes = ();
my %is_external = ();
# each item of @$island contains the expolygons having the same depth;
# for each depth we build an arrayref containing all the holes
my @hole_depths = map [ map $_->holes, @$_ ], @$island;
# organize the outermost hole loops using a shortest path search
@{$hole_depths[0]} = @{shortest_path([
# organize the outermost hole loops using a nearest-neighbor search
@{$hole_depths[0]} = @{chained_path_items([
map [ $_->[0], $_ ], @{$hole_depths[0]},
])};
# loop while we have spare holes
CYCLE: while (map @$_, @hole_depths) {
# remove first depth container if it contains no holes anymore
shift @hole_depths while !@{$hole_depths[0]};
# take first available hole
@ -389,14 +419,14 @@ sub make_perimeters {
flow_spacing => $self->perimeter_flow->spacing,
);
} @{ $self->thin_walls }
])->shortest_path;
])->chained_path;
}
sub _add_perimeter {
my $self = shift;
my ($polygon, $role) = @_;
return unless $polygon->is_printable($self->perimeter_flow->width);
return unless $polygon->is_printable($self->perimeter_flow->scaled_width);
push @{ $self->perimeters }, Slic3r::ExtrusionLoop->pack(
polygon => $polygon,
role => ($role // EXTR_ROLE_PERIMETER),
@ -415,7 +445,7 @@ sub prepare_fill_surfaces {
$_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces};
}
# turn too small internal regions into solid regions
# turn too small internal regions into solid regions according to the user setting
{
my $min_area = scale scale $Slic3r::Config->solid_infill_below_area; # scaling an area requires two calls!
my @small = grep $_->surface_type == S_TYPE_INTERNAL && $_->expolygon->contour->area <= $min_area, @{$self->fill_surfaces};
@ -424,76 +454,99 @@ sub prepare_fill_surfaces {
}
}
# make bridges printable
sub process_bridges {
sub process_external_surfaces {
my $self = shift;
# no bridges are possible if we have no internal surfaces
return if $Slic3r::Config->fill_density == 0;
my @bridges = ();
# a bottom surface on a layer > 0 is either a bridge or a overhang
# or a combination of both; any top surface is a candidate for
# reverse bridge processing
my @solid_surfaces = grep {
($_->surface_type == S_TYPE_BOTTOM && $self->id > 0) || $_->surface_type == S_TYPE_TOP
} @{$self->fill_surfaces} or return;
my @internal_surfaces = grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @{$self->slices};
SURFACE: foreach my $surface (@solid_surfaces) {
my $expolygon = $surface->expolygon->safety_offset;
my $description = $surface->surface_type == S_TYPE_BOTTOM ? 'bridge/overhang' : 'reverse bridge';
# enlarge top and bottom surfaces
{
# get all external surfaces
my @top = grep $_->surface_type == S_TYPE_TOP, @{$self->fill_surfaces};
my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces};
# offset the contour and intersect it with the internal surfaces to discover
# which of them has contact with our bridge
my @supporting_surfaces = ();
my ($contour_offset) = $expolygon->contour->offset(scale $self->flow->spacing * sqrt(2));
foreach my $internal_surface (@internal_surfaces) {
my $intersection = intersection_ex([$contour_offset], [$internal_surface->p]);
if (@$intersection) {
push @supporting_surfaces, $internal_surface;
}
# offset them and intersect the results with the actual fill boundaries
my $margin = scale 3; # TODO: ensure this is greater than the total thickness of the perimeters
@top = @{intersection_ex(
[ Slic3r::Geometry::Clipper::offset([ map $_->p, @top ], +$margin) ],
[ map $_->p, @{$self->fill_surfaces} ],
undef,
1, # to ensure adjacent expolygons are unified
)};
@bottom = @{intersection_ex(
[ Slic3r::Geometry::Clipper::offset([ map $_->p, @bottom ], +$margin) ],
[ map $_->p, @{$self->fill_surfaces} ],
undef,
1, # to ensure adjacent expolygons are unified
)};
# give priority to bottom surfaces
@top = @{diff_ex(
[ map @$_, @top ],
[ map @$_, @bottom ],
)};
# generate new surfaces
my @new_surfaces = ();
push @new_surfaces, map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_TOP,
), @top;
push @new_surfaces, map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_BOTTOM,
), @bottom;
# subtract the new top surfaces from the other non-top surfaces and re-add them
my @other = grep $_->surface_type != S_TYPE_TOP && $_->surface_type != S_TYPE_BOTTOM, @{$self->fill_surfaces};
foreach my $group (Slic3r::Surface->group(@other)) {
push @new_surfaces, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $group->[0]->surface_type,
), @{diff_ex(
[ map $_->p, @$group ],
[ map $_->p, @new_surfaces ],
)};
}
@{$self->fill_surfaces} = @new_surfaces;
}
# detect bridge direction (skip bottom layer)
if ($self->id > 0) {
my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; # surfaces
my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("bridge_surfaces.svg",
green_polygons => [ map $_->p, @supporting_surfaces ],
red_polygons => [ @$expolygon ],
);
}
Slic3r::debugf "Found $description on layer %d with %d support(s)\n",
$self->id, scalar(@supporting_surfaces);
next SURFACE unless @supporting_surfaces;
my $bridge_angle = undef;
if ($surface->surface_type == S_TYPE_BOTTOM) {
# detect optimal bridge angle
my $bridge_over_hole = 0;
my @edges = (); # edges are POLYLINES
foreach my $supporting_surface (@supporting_surfaces) {
my @surface_edges = map $_->clip_with_polygon($contour_offset),
($supporting_surface->contour, $supporting_surface->holes);
if (@supporting_surfaces == 1 && @surface_edges == 1
&& @{$supporting_surface->contour} == @{$surface_edges[0]}) {
$bridge_over_hole = 1;
foreach my $surface (@bottom) {
# detect what edges lie on lower slices
my @edges = (); # polylines
foreach my $lower (@lower) {
# turn bridge contour and holes into polylines and then clip them
# with each lower slice's contour
my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @{$surface->expolygon};
if (@clipped == 2) {
# If the split_at_first_point() call above happens to split the polygon inside the clipping area
# we would get two consecutive polylines instead of a single one, so we use this ugly hack to
# recombine them back into a single one in order to trigger the @edges == 2 logic below.
# This needs to be replaced with something way better.
if (points_coincide($clipped[0][0], $clipped[-1][-1])) {
@clipped = (Slic3r::Polyline->new(@{$clipped[-1]}, @{$clipped[0]}));
}
if (points_coincide($clipped[-1][0], $clipped[0][-1])) {
@clipped = (Slic3r::Polyline->new(@{$clipped[0]}, @{$clipped[1]}));
}
}
push @edges, grep { @$_ } @surface_edges;
push @edges, @clipped;
}
Slic3r::debugf " Bridge is supported on %d edge(s)\n", scalar(@edges);
Slic3r::debugf " and covers a hole\n" if $bridge_over_hole;
Slic3r::debugf "Found bridge on layer %d with %d support(s)\n", $self->id, scalar(@edges);
next if !@edges;
my $bridge_angle = undef;
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("bridge_edges.svg",
polylines => [ map $_->p, @edges ],
Slic3r::SVG::output("bridge.svg",
polygons => [ $surface->p ],
red_polygons => [ map @$_, @lower ],
polylines => [ @edges ],
);
}
@ -515,7 +568,7 @@ sub process_bridges {
} elsif (@edges) {
my $center = Slic3r::Geometry::bounding_box_center([ map @$_, @edges ]);
my $x = my $y = 0;
foreach my $point (map @$, @edges) {
foreach my $point (map @$_, @edges) {
my $line = Slic3r::Line->new($center, $point);
my $dir = $line->direction;
my $len = $line->length;
@ -527,80 +580,8 @@ sub process_bridges {
Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n",
$self->id, $bridge_angle if defined $bridge_angle;
}
# now, extend our bridge by taking a portion of supporting surfaces
{
# offset the bridge by the specified amount of mm (minimum 3)
my $bridge_overlap = scale 3;
my ($bridge_offset) = $expolygon->contour->offset($bridge_overlap);
# calculate the new bridge
my $intersection = intersection_ex(
[ @$expolygon, map $_->p, @supporting_surfaces ],
[ $bridge_offset ],
);
push @bridges, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $surface->surface_type,
bridge_angle => $bridge_angle,
), @$intersection;
}
}
# now we need to merge bridges to avoid overlapping
{
# build a list of unique bridge types
my @surface_groups = Slic3r::Surface->group(@bridges);
# merge bridges of the same type, removing any of the bridges already merged;
# the order of @surface_groups determines the priority between bridges having
# different surface_type or bridge_angle
@bridges = ();
foreach my $surfaces (@surface_groups) {
my $union = union_ex([ map $_->p, @$surfaces ]);
my $diff = diff_ex(
[ map @$_, @$union ],
[ map $_->p, @bridges ],
);
push @bridges, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $surfaces->[0]->surface_type,
bridge_angle => $surfaces->[0]->bridge_angle,
), @$union;
}
}
# apply bridges to layer
{
my @surfaces = @{$self->fill_surfaces};
@{$self->fill_surfaces} = ();
# intersect layer surfaces with bridges to get actual bridges
foreach my $bridge (@bridges) {
my $actual_bridge = intersection_ex(
[ map $_->p, @surfaces ],
[ $bridge->p ],
);
push @{$self->fill_surfaces}, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $bridge->surface_type,
bridge_angle => $bridge->bridge_angle,
), @$actual_bridge;
}
# difference between layer surfaces and bridges are the other surfaces
foreach my $group (Slic3r::Surface->group(@surfaces)) {
my $difference = diff_ex(
[ map $_->p, @$group ],
[ map $_->p, @bridges ],
);
push @{$self->fill_surfaces}, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $group->[0]->surface_type), @$difference;
$surface->bridge_angle($bridge_angle);
}
}
}

View file

@ -73,6 +73,7 @@ has 'attributes' => (is => 'rw', default => sub { {} });
package Slic3r::Model::Object;
use Moo;
use List::Util qw(first);
use Slic3r::Geometry qw(X Y Z);
has 'input_file' => (is => 'rw');
@ -112,6 +113,8 @@ sub add_instance {
sub mesh {
my $self = shift;
# this mesh won't be suitable for check_manifoldness as multiple
# facets from different volumes may use the same vertices
return Slic3r::TriangleMesh->new(
vertices => $self->vertices,
facets => [ map @{$_->facets}, @{$self->volumes} ],
@ -136,6 +139,11 @@ sub materials_count {
return scalar keys %materials;
}
sub check_manifoldness {
my $self = shift;
return (first { !$_->mesh->check_manifoldness } @{$self->volumes}) ? 0 : 1;
}
package Slic3r::Model::Volume;
use Moo;

View file

@ -39,12 +39,14 @@ sub rotate {
my $self = shift;
my ($angle, $center) = @_;
@$self = @{ +(Slic3r::Geometry::rotate_points($angle, $center, $self))[0] };
$self;
}
sub translate {
my $self = shift;
my ($x, $y) = @_;
@$self = @{ +(Slic3r::Geometry::move_points([$x, $y], $self))[0] };
$self;
}
sub x { $_[0]->[0] }

View file

@ -14,6 +14,11 @@ sub lines {
return polygon_lines($self);
}
sub boost_polygon {
my $self = shift;
return Boost::Geometry::Utils::polygon($self);
}
sub boost_linestring {
my $self = shift;
return Boost::Geometry::Utils::linestring([@$self, $self->[0]]);
@ -113,7 +118,7 @@ sub subdivide {
# returns false if the polyline is too tight to be printed
sub is_printable {
my $self = shift;
my ($flow_width) = @_;
my ($width) = @_;
# try to get an inwards offset
# for a distance equal to half of the extrusion width;
@ -124,7 +129,7 @@ sub is_printable {
# detect them and we would be discarding them.
my $p = $self->clone;
$p->make_counter_clockwise;
return $p->offset(Slic3r::Geometry::scale($flow_width || $Slic3r::flow->width) / 2) ? 1 : 0;
return $p->offset(-$width / 2) ? 1 : 0;
}
sub is_valid {
@ -136,7 +141,7 @@ sub split_at_index {
my $self = shift;
my ($index) = @_;
return (ref $self)->new(
return Slic3r::Polyline->new(
@$self[$index .. $#$self],
@$self[0 .. $index],
);

View file

@ -68,7 +68,7 @@ sub simplify {
my $self = shift;
my $tolerance = shift || 10;
@$self = @{ Slic3r::Geometry::douglas_peucker($self, $tolerance) };
@$self = @{ Boost::Geometry::Utils::linestring_simplify($self, $tolerance) };
bless $_, 'Slic3r::Point' for @$self;
}
@ -115,10 +115,7 @@ sub clip_with_expolygon {
my $self = shift;
my ($expolygon) = @_;
my $result = Boost::Geometry::Utils::polygon_linestring_intersection(
$expolygon->boost_polygon,
$self->boost_linestring,
);
my $result = Boost::Geometry::Utils::polygon_multi_linestring_intersection($expolygon, [$self]);
bless $_, 'Slic3r::Polyline' for @$result;
bless $_, 'Slic3r::Point' for map @$_, @$result;
return @$result;
@ -196,7 +193,7 @@ has 'polylines' => (is => 'ro', default => sub { [] });
# If the second argument is provided, this method will return its items sorted
# instead of returning the actual sorted polylines.
# Note that our polylines will be reversed in place when necessary.
sub shortest_path {
sub chained_path {
my $self = shift;
my ($start_near, $items) = @_;

View file

@ -19,6 +19,7 @@ has 'extruders' => (is => 'rw', default => sub {[]});
has 'regions' => (is => 'rw', default => sub {[]});
has 'support_material_flow' => (is => 'rw');
has 'first_layer_support_material_flow' => (is => 'rw');
has 'has_support_material' => (is => 'lazy');
# ordered collection of extrusion paths to build skirt loops
has 'skirt' => (
@ -62,6 +63,13 @@ sub _trigger_config {
$self->config->set('extrusion_axis', '') if $self->config->gcode_flavor eq 'no-extrusion';
}
sub _build_has_support_material {
my $self = shift;
return $self->config->support_material
|| $self->config->raft_layers > 0
|| $self->config->support_material_enforce_layers > 0;
}
sub add_model {
my $self = shift;
my ($model) = @_;
@ -187,36 +195,28 @@ sub init_extruders {
);
}
# calculate default flows
$Slic3r::flow = $self->extruders->[0]->make_flow(
width => $self->config->extrusion_width,
);
$Slic3r::first_layer_flow = $self->extruders->[0]->make_flow(
layer_height => $self->config->get_value('first_layer_height'),
width => $self->config->first_layer_extrusion_width,
);
# calculate regions' flows
for my $region_id (0 .. $#{$self->regions}) {
my $region = $self->regions->[$region_id];
# per-role extruders and flows
for (qw(perimeter infill)) {
for (qw(perimeter infill top_infill)) {
my $extruder_name = $_ eq 'top_infill' ? 'infill' : $_;
$region->extruders->{$_} = ($self->regions_count > 1)
? $self->extruders->[$extruder_mapping{$region_id}]
: $self->extruders->[$self->config->get("${_}_extruder")-1];
: $self->extruders->[$self->config->get("${extruder_name}_extruder")-1];
$region->flows->{$_} = $region->extruders->{$_}->make_flow(
width => $self->config->get("${_}_extrusion_width") || $self->config->extrusion_width,
);
$region->first_layer_flows->{$_} = $region->extruders->{$_}->make_flow(
layer_height => $self->config->get_value('first_layer_height'),
width => $self->config->first_layer_extrusion_width,
);
) if $self->config->first_layer_extrusion_width;
}
}
# calculate support material flow
if ($self->config->support_material) {
if ($self->has_support_material) {
my $extruder = $self->extruders->[$self->config->support_material_extruder-1];
$self->support_material_flow($extruder->make_flow(
width => $self->config->support_material_extrusion_width || $self->config->extrusion_width,
@ -226,9 +226,6 @@ sub init_extruders {
width => $self->config->first_layer_extrusion_width,
));
}
Slic3r::debugf "Default flow width = %s (spacing = %s)\n",
$Slic3r::flow->width, $Slic3r::flow->spacing;
}
sub object_copies {
@ -242,11 +239,7 @@ sub object_copies {
sub layer_count {
my $self = shift;
my $count = 0;
foreach my $object (@{$self->objects}) {
$count = @{$object->layers} if @{$object->layers} > $count;
}
return $count;
return max(map { scalar @{$_->layers} } @{$self->objects});
}
sub regions_count {
@ -345,7 +338,8 @@ sub export_gcode {
for @{$layer->slices}, (map $_->expolygon, map @{$_->slices}, @{$layer->regions});
}
# this will transform $layer->fill_surfaces from expolygon
# this will assign a type (top/bottom/internal) to $layerm->slices
# and transform $layerm->fill_surfaces from expolygon
# to typed top/bottom/internal surfaces;
$status_cb->(30, "Detecting solid surfaces");
$_->detect_surfaces_type for @{$self->objects};
@ -357,12 +351,16 @@ sub export_gcode {
# this will detect bridges and reverse bridges
# and rearrange top/bottom/internal surfaces
$status_cb->(45, "Detect bridges");
$_->process_bridges for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
$_->process_external_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
# detect which fill surfaces are near external layers
# they will be split in internal and internal-solid surfaces
$status_cb->(60, "Generating horizontal shells");
$_->discover_horizontal_shells for @{$self->objects};
$_->clip_fill_surfaces for @{$self->objects};
# the following step needs to be done before combination because it may need
# to remove only half of the combined infill
$_->bridge_over_infill for @{$self->objects};
# combine fill surfaces to honor the "infill every N layers" option
$status_cb->(70, "Combining infill");
@ -417,7 +415,7 @@ sub export_gcode {
}
# generate support material
if ($Slic3r::Config->support_material) {
if ($self->has_support_material) {
$status_cb->(85, "Generating support material");
$_->generate_support_material for @{$self->objects};
}
@ -430,6 +428,18 @@ sub export_gcode {
$self->make_skirt;
$self->make_brim; # must come after make_skirt
# time to make some statistics
if (0) {
eval "use Devel::Size";
print "MEMORY USAGE:\n";
printf " meshes = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->meshes), @{$self->objects})/1024/1024;
printf " layer slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->layers}, @{$self->objects})/1024/1024;
printf " region slices = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->slices), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
printf " perimeters = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->perimeters), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
printf " fills = %.1fMb\n", List::Util::sum(map Devel::Size::total_size($_->fills), map @{$_->regions}, map @{$_->layers}, @{$self->objects})/1024/1024;
printf " print object = %.1fMb\n", Devel::Size::total_size($self)/1024/1024;
}
# output everything to a G-code file
my $output_file = $self->expanded_output_filepath($params{output_file});
$status_cb->(90, "Exporting G-code" . ($output_file ? " to $output_file" : ""));
@ -472,7 +482,7 @@ sub export_svg {
my $output_file = $self->expanded_output_filepath($params{output_file});
$output_file =~ s/\.gcode$/.svg/i;
open my $fh, ">", $output_file or die "Failed to open $output_file for writing\n";
Slic3r::open(\my $fh, ">", $output_file) or die "Failed to open $output_file for writing\n";
print "Exporting to $output_file...";
my $print_size = $self->size;
print $fh sprintf <<"EOF", unscale($print_size->[X]), unscale($print_size->[Y]);
@ -514,7 +524,7 @@ EOF
}
}
# generate support material
if ($Slic3r::Config->support_material && $layer_id > 0) {
if ($self->has_support_material && $layer_id > 0) {
my (@supported_slices, @unsupported_slices) = ();
foreach my $expolygon (@current_layer_slices) {
my $intersection = intersection_ex(
@ -550,10 +560,10 @@ sub make_skirt {
return unless $Slic3r::Config->skirts > 0;
# collect points from all layers contained in skirt height
my $skirt_height = $Slic3r::Config->skirt_height;
$skirt_height = $self->layer_count if $skirt_height > $self->layer_count;
my @points = ();
foreach my $obj_idx (0 .. $#{$self->objects}) {
my $skirt_height = $Slic3r::Config->skirt_height;
$skirt_height = $self->objects->[$obj_idx]->layer_count if $skirt_height > $self->objects->[$obj_idx]->layer_count;
my @layers = map $self->objects->[$obj_idx]->layers->[$_], 0..($skirt_height-1);
my @layer_points = (
(map @$_, map @$_, map @{$_->slices}, @layers),
@ -568,7 +578,10 @@ sub make_skirt {
my $convex_hull = convex_hull(\@points);
my @extruded_length = (); # for each extruder
my $spacing = $Slic3r::first_layer_flow->spacing;
# TODO: use each extruder's own flow
my $spacing = $self->objects->[0]->layers->[0]->regions->[0]->perimeter_flow->spacing;
my $first_layer_height = $Slic3r::Config->get_value('first_layer_height');
my @extruders_e_per_mm = ();
my $extruder_idx = 0;
@ -607,7 +620,9 @@ sub make_brim {
my $self = shift;
return unless $Slic3r::Config->brim_width > 0;
my $grow_distance = $Slic3r::first_layer_flow->scaled_width / 2;
my $flow = $self->objects->[0]->layers->[0]->regions->[0]->perimeter_flow;
my $grow_distance = $flow->scaled_width / 2;
my @islands = (); # array of polygons
foreach my $obj_idx (0 .. $#{$self->objects}) {
my $layer0 = $self->objects->[$obj_idx]->layers->[0];
@ -622,18 +637,19 @@ sub make_brim {
}
# if brim touches skirt, make it around skirt too
if ($Slic3r::Config->skirt_distance + (($Slic3r::Config->skirts - 1) * $Slic3r::first_layer_flow->spacing) <= $Slic3r::Config->brim_width) {
# TODO: calculate actual skirt width (using each extruder's flow in multi-extruder setups)
if ($Slic3r::Config->skirt_distance + (($Slic3r::Config->skirts - 1) * $flow->spacing) <= $Slic3r::Config->brim_width) {
push @islands, map $_->unpack->split_at_first_point->polyline->grow($grow_distance), @{$self->skirt};
}
my $num_loops = sprintf "%.0f", $Slic3r::Config->brim_width / $Slic3r::first_layer_flow->width;
my $num_loops = sprintf "%.0f", $Slic3r::Config->brim_width / $flow->width;
for my $i (reverse 1 .. $num_loops) {
# JT_SQUARE ensures no vertex is outside the given offset distance
push @{$self->brim}, Slic3r::ExtrusionLoop->pack(
polygon => Slic3r::Polygon->new($_),
role => EXTR_ROLE_SKIRT,
flow_spacing => $Slic3r::first_layer_flow->spacing,
) for Slic3r::Geometry::Clipper::offset(\@islands, $i * $Slic3r::first_layer_flow->scaled_spacing, undef, JT_SQUARE);
flow_spacing => $flow->spacing,
) for Slic3r::Geometry::Clipper::offset(\@islands, ($i - 0.5) * $flow->scaled_spacing, undef, JT_SQUARE); # -0.5 because islands are not represented by their centerlines
# TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions
}
}
@ -647,7 +663,7 @@ sub write_gcode {
if (ref $file eq 'IO::Scalar') {
$fh = $file;
} else {
open $fh, ">", $file
Slic3r::open(\$fh, ">", $file)
or die "Failed to open $file for writing\n";
}
@ -669,13 +685,14 @@ sub write_gcode {
printf $fh "; infill extrusion width = %.2fmm\n", $self->regions->[0]->flows->{infill}->width;
printf $fh "; support material extrusion width = %.2fmm\n", $self->support_material_flow->width
if $self->support_material_flow;
printf $fh "; first layer extrusion width = %.2fmm\n", $Slic3r::first_layer_flow->width
if $Slic3r::first_layer_flow;
printf $fh "; first layer extrusion width = %.2fmm\n", $self->regions->[0]->first_layer_flows->{perimeter}->width
if $self->regions->[0]->first_layer_flows->{perimeter};
print $fh "\n";
# set up our extruder object
my $gcodegen = Slic3r::GCode->new(
multiple_extruders => (@{$self->extruders} > 1),
multiple_extruders => (@{$self->extruders} > 1),
layer_count => $self->layer_count,
);
my $min_print_speed = 60 * $Slic3r::Config->min_print_speed;
my $dec = $gcodegen->dec;
@ -700,7 +717,7 @@ sub write_gcode {
print $fh "G21 ; set units to millimeters\n";
if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) {
printf $fh $gcodegen->reset_e;
if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|makerbot)$/) {
if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|makerbot|sailfish)$/) {
if ($Slic3r::Config->use_relative_e_distances) {
print $fh "M83 ; use relative distances for extrusion\n";
} else {
@ -716,9 +733,35 @@ sub write_gcode {
$Slic3r::Config->print_center->[Y] - (unscale ($print_bb[Y2] - $print_bb[Y1]) / 2) - unscale $print_bb[Y1],
);
# initialize a motion planner for object-to-object travel moves
if ($Slic3r::Config->avoid_crossing_perimeters) {
my $distance_from_objects = 1;
# compute the offsetted convex hull for each object and repeat it for each copy.
my @islands = ();
foreach my $obj_idx (0 .. $#{$self->objects}) {
my $convex_hull = convex_hull([
map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
]);
# discard layers only containing thin walls (offset would fail on an empty polygon)
if (@$convex_hull) {
my @island = Slic3r::ExPolygon->new($convex_hull)
->translate(scale $shift[X], scale $shift[Y])
->offset_ex(scale $distance_from_objects, 1, JT_SQUARE);
foreach my $copy (@{ $self->objects->[$obj_idx]->copies }) {
push @islands, map $_->clone->translate(@$copy), @island;
}
}
}
$gcodegen->external_mp(Slic3r::GCode::MotionPlanner->new(
islands => union_ex([ map @$_, @islands ]),
no_internal => 1,
));
}
# prepare the logic to print one layer
my $skirt_done = 0; # count of skirt layers done
my $brim_done = 0;
my $last_obj_copy = "";
my $extrude_layer = sub {
my ($layer_id, $object_copies) = @_;
my $gcode = "";
@ -732,8 +775,8 @@ sub write_gcode {
if $Slic3r::Config->bed_temperature && $Slic3r::Config->bed_temperature != $Slic3r::Config->first_layer_bed_temperature;
}
# set new layer, but don't move Z as support material interfaces may need an intermediate one
$gcodegen->layer($self->objects->[$object_copies->[0][0]]->layers->[$layer_id]);
# set new layer, but don't move Z as support material contact areas may need an intermediate one
$gcode .= $gcodegen->change_layer($self->objects->[$object_copies->[0][0]]->layers->[$layer_id]);
$gcodegen->elapsed_time(0);
# prepare callback to call as soon as a Z command is generated
@ -748,7 +791,6 @@ sub write_gcode {
$gcodegen->set_shift(@shift);
$gcode .= $gcodegen->set_extruder($self->extruders->[0]); # move_z requires extruder
$gcode .= $gcodegen->move_z($gcodegen->layer->print_z);
$gcode .= $gcodegen->set_acceleration($Slic3r::Config->perimeter_acceleration);
# skip skirt if we have a large brim
if ($layer_id < $Slic3r::Config->skirt_height) {
# distribute skirt loops across all extruders
@ -762,6 +804,7 @@ sub write_gcode {
}
}
$skirt_done++;
$gcodegen->straight_once(1);
}
# extrude brim
@ -771,29 +814,32 @@ sub write_gcode {
$gcodegen->set_shift(@shift);
$gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim};
$brim_done = 1;
$gcodegen->straight_once(1);
}
for my $obj_copy (@$object_copies) {
my ($obj_idx, $copy) = @$obj_copy;
$gcodegen->new_object(1) if $last_obj_copy && $last_obj_copy ne "${obj_idx}_${copy}";
$last_obj_copy = "${obj_idx}_${copy}";
my $layer = $self->objects->[$obj_idx]->layers->[$layer_id];
$gcodegen->set_shift(map $shift[$_] + unscale $copy->[$_], X,Y);
# extrude support material before other things because it might use a lower Z
# and also because we avoid travelling on other things when printing it
if ($Slic3r::Config->support_material) {
$gcode .= $gcodegen->move_z($layer->support_material_interface_z)
if ($layer->support_interface_fills && @{ $layer->support_interface_fills->paths });
if ($self->has_support_material) {
$gcode .= $gcodegen->move_z($layer->support_material_contact_z)
if ($layer->support_contact_fills && @{ $layer->support_contact_fills->paths });
$gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]);
if ($layer->support_interface_fills) {
$gcode .= $gcodegen->extrude_path($_, 'support material interface')
for $layer->support_interface_fills->shortest_path($gcodegen->last_pos);
if ($layer->support_contact_fills) {
$gcode .= $gcodegen->extrude_path($_, 'support material contact area')
for $layer->support_contact_fills->chained_path($gcodegen->last_pos);
}
$gcode .= $gcodegen->move_z($layer->print_z);
if ($layer->support_fills) {
$gcode .= $gcodegen->extrude_path($_, 'support material')
for $layer->support_fills->shortest_path($gcodegen->last_pos);
for $layer->support_fills->chained_path($gcodegen->last_pos);
}
}
@ -807,7 +853,10 @@ sub write_gcode {
# extrude perimeters
if (@{ $layerm->perimeters }) {
$gcode .= $gcodegen->set_extruder($region->extruders->{perimeter});
$gcode .= $gcodegen->set_acceleration($Slic3r::Config->perimeter_acceleration);
$gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $layerm->perimeters };
$gcode .= $gcodegen->set_acceleration($Slic3r::Config->default_acceleration)
if $Slic3r::Config->perimeter_acceleration;
}
# extrude fills
@ -817,11 +866,13 @@ sub write_gcode {
for my $fill (@{ $layerm->fills }) {
if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
$gcode .= $gcodegen->extrude($_, 'fill')
for $fill->shortest_path($gcodegen->last_pos);
for $fill->chained_path($gcodegen->last_pos);
} else {
$gcode .= $gcodegen->extrude($fill, 'fill') ;
}
}
$gcode .= $gcodegen->set_acceleration($Slic3r::Config->default_acceleration)
if $Slic3r::Config->infill_acceleration;
}
}
}
@ -912,7 +963,6 @@ sub write_gcode {
# write end commands to file
print $fh $gcodegen->retract;
print $fh $gcodegen->set_fan(0);
print $fh "M501 ; reset acceleration\n" if $Slic3r::Config->acceleration;
printf $fh "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->end_gcode);
printf $fh "; filament used = %.1fmm (%.1fcm3)\n",

View file

@ -1,6 +1,7 @@
package Slic3r::Print::Object;
use Moo;
use List::Util qw(min sum);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(Z PI scale unscale deg2rad rad2deg scaled_epsilon);
use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex);
@ -34,11 +35,18 @@ sub get_layer_range {
my $self = shift;
my ($min_z, $max_z) = @_;
# $min_layer is the uppermost layer having slice_z <= $min_z
# $max_layer is the lowermost layer having slice_z >= $max_z
my ($min_layer, $max_layer) = (0, undef);
for my $layer (@{$self->layers}) {
$min_layer = $layer->id if $layer->slice_z <= $min_z;
if ($layer->slice_z >= $max_z) {
$max_layer = $layer->id;
for my $i (0 .. $#{$self->layers}) {
if ($self->layers->[$i]->slice_z >= $min_z) {
$min_layer = $i - 1;
for my $k ($i .. $#{$self->layers}) {
if ($self->layers->[$k]->slice_z >= $max_z) {
$max_layer = $k - 1;
last;
}
}
last;
}
}
@ -173,9 +181,10 @@ sub slice {
}
# remove empty layers from bottom
while (@{$self->layers} && !@{$self->layers->[0]->slices} && !map @{$_->thin_walls}, @{$self->layers->[0]->regions}) {
shift @{$self->layers};
for (my $i = 0; $i <= $#{$self->layers}; $i++) {
my $first_object_layer_id = $Slic3r::Config->raft_layers;
while (@{$self->layers} && !@{$self->layers->[$first_object_layer_id]->slices} && !map @{$_->thin_walls}, @{$self->layers->[$first_object_layer_id]->regions}) {
splice @{$self->layers}, $first_object_layer_id, 1;
for (my $i = $first_object_layer_id; $i <= $#{$self->layers}; $i++) {
$self->layers->[$i]->id($i);
}
}
@ -190,35 +199,29 @@ sub make_perimeters {
# compare each layer to the one below, and mark those slices needing
# one additional inner perimeter, like the top of domed objects-
# this algorithm makes sure that almost one perimeter is overlapping
if ($Slic3r::Config->extra_perimeters && $Slic3r::Config->perimeters > 0) {
# this algorithm makes sure that at least one perimeter is overlapping
# but we don't generate any extra perimeter if fill density is zero, as they would be floating
# inside the object - infill_only_where_needed should be the method of choice for printing
# hollow objects
if ($Slic3r::Config->extra_perimeters && $Slic3r::Config->perimeters > 0 && $Slic3r::Config->fill_density > 0) {
for my $region_id (0 .. ($self->print->regions_count-1)) {
for my $layer_id (0 .. $self->layer_count-2) {
my $layerm = $self->layers->[$layer_id]->regions->[$region_id];
my $upper_layerm = $self->layers->[$layer_id+1]->regions->[$region_id];
my $perimeter_flow = $layerm->perimeter_flow;
my $perimeter_spacing = $layerm->perimeter_flow->scaled_spacing;
my $overlap = $perimeter_flow->spacing; # one perimeter
my $overlap = $perimeter_spacing; # one perimeter
# compute polygons representing the thickness of the first external perimeter of
# the upper layer slices
# compute the polygon used to trigger the additional perimeters: the hole represents
# the required overlap, while the contour represents how different should the slices be
# (thus how horizontal should the slope be) before extra perimeters are not generated, and
# normal solid infill is used
my $upper = diff_ex(
[ map @$_, map $_->expolygon->offset_ex(+ 0.5 * $perimeter_flow->scaled_spacing), @{$upper_layerm->slices} ],
[ map @$_, map $_->expolygon->offset_ex(- scale($overlap) + (0.5 * $perimeter_flow->scaled_spacing)), @{$upper_layerm->slices} ],
[ map @$_, map $_->expolygon->offset_ex($overlap), @{$upper_layerm->slices} ],
[ map @$_, map $_->expolygon->offset_ex(-$overlap), @{$upper_layerm->slices} ],
);
next if !@$upper;
# we need to limit our detection to the areas which would actually benefit from
# more perimeters. so, let's compute the area we want to ignore
my $ignore = [];
{
my $diff = diff_ex(
[ map @$_, map $_->expolygon->offset_ex(- ($Slic3r::Config->perimeters-0.5) * $perimeter_flow->scaled_spacing), @{$layerm->slices} ],
[ map @{$_->expolygon}, @{$upper_layerm->slices} ],
);
$ignore = [ map @$_, map $_->offset_ex($perimeter_flow->scaled_spacing), @$diff ];
}
foreach my $slice (@{$layerm->slices}) {
my $hypothetical_perimeter_num = $Slic3r::Config->perimeters + 1;
CYCLE: while (1) {
@ -226,18 +229,20 @@ sub make_perimeters {
# of our slice
my $hypothetical_perimeter;
{
my $outer = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-1.5) * $perimeter_flow->scaled_spacing - scaled_epsilon) ];
my $outer = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-1) * $perimeter_spacing - scaled_epsilon) ];
last CYCLE if !@$outer;
my $inner = [ map @$_, $slice->expolygon->offset_ex(- ($hypothetical_perimeter_num-0.5) * $perimeter_flow->scaled_spacing) ];
my $inner = [ map @$_, $slice->expolygon->offset_ex(- $hypothetical_perimeter_num * $perimeter_spacing) ];
last CYCLE if !@$inner;
$hypothetical_perimeter = diff_ex($outer, $inner);
}
last CYCLE if !@$hypothetical_perimeter;
# compute the area of the hypothetical perimeter
my $hp_area = sum(map $_->area, @$hypothetical_perimeter);
# only add the perimeter if the intersection is at least 20%, otherwise we'd get no benefit
my $intersection = intersection_ex([ map @$_, @$upper ], [ map @$_, @$hypothetical_perimeter ]);
$intersection = diff_ex([ map @$_, @$intersection ], $ignore) if @$ignore;
last CYCLE if !@{ $intersection };
last CYCLE if (sum(map $_->area, @{ $intersection }) // 0) < $hp_area * 0.2;
Slic3r::debugf " adding one more perimeter at layer %d\n", $layer_id;
$slice->additional_inner_perimeters(($slice->additional_inner_perimeters || 0) + 1);
$hypothetical_perimeter_num++;
@ -247,7 +252,40 @@ sub make_perimeters {
}
}
$_->make_perimeters for @{$self->layers};
Slic3r::parallelize(
items => sub { 0 .. ($self->layer_count-1) },
thread_cb => sub {
my $q = shift;
$Slic3r::Geometry::Clipper::clipper = Math::Clipper->new;
my $result = {};
while (defined (my $layer_id = $q->dequeue)) {
my $layer = $self->layers->[$layer_id];
$layer->make_perimeters;
$result->{$layer_id} ||= {};
foreach my $region_id (0 .. $#{$layer->regions}) {
my $layerm = $layer->regions->[$region_id];
$result->{$layer_id}{$region_id} = {
perimeters => $layerm->perimeters,
fill_surfaces => $layerm->fill_surfaces,
thin_fills => $layerm->thin_fills,
};
}
}
return $result;
},
collect_cb => sub {
my $result = shift;
foreach my $layer_id (keys %$result) {
foreach my $region_id (keys %{$result->{$layer_id}}) {
$self->layers->[$layer_id]->regions->[$region_id]->$_($result->{$layer_id}{$region_id}{$_})
for qw(perimeters fill_surfaces thin_fills);
}
}
},
no_threads_cb => sub {
$_->make_perimeters for @{$self->layers};
},
);
}
sub detect_surfaces_type {
@ -258,17 +296,17 @@ sub detect_surfaces_type {
my $surface_difference = sub {
my ($subject_surfaces, $clip_surfaces, $result_type, $layerm) = @_;
my $expolygons = diff_ex(
[ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$subject_surfaces ],
[ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$clip_surfaces ],
[ map @$_, @$subject_surfaces ],
[ map @$_, @$clip_surfaces ],
1,
);
return grep $_->contour->is_printable($layerm->flow->width),
return grep $_->contour->is_printable($layerm->perimeter_flow->scaled_width),
map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type),
@$expolygons;
};
for my $region_id (0 .. ($self->print->regions_count-1)) {
for (my $i = 0; $i < $self->layer_count; $i++) {
for my $i (0 .. ($self->layer_count-1)) {
my $layerm = $self->layers->[$i]->regions->[$region_id];
# comparison happens against the *full* slices (considering all regions)
@ -280,7 +318,12 @@ sub detect_surfaces_type {
# find top surfaces (difference between current surfaces
# of current layer and upper one)
if ($upper_layer) {
@top = $surface_difference->($layerm->slices, $upper_layer->slices, S_TYPE_TOP, $layerm);
@top = $surface_difference->(
[ map $_->expolygon, @{$layerm->slices} ],
$upper_layer->slices,
S_TYPE_TOP,
$layerm,
);
} else {
# if no upper layer, all surfaces of this one are solid
@top = @{$layerm->slices};
@ -290,7 +333,13 @@ sub detect_surfaces_type {
# find bottom surfaces (difference between current surfaces
# of current layer and lower one)
if ($lower_layer) {
@bottom = $surface_difference->($layerm->slices, $lower_layer->slices, S_TYPE_BOTTOM, $layerm);
# lower layer's slices are already Surface objects
@bottom = $surface_difference->(
[ map $_->expolygon, @{$layerm->slices} ],
$lower_layer->slices,
S_TYPE_BOTTOM,
$layerm,
);
} else {
# if no lower layer, all surfaces of this one are solid
@bottom = @{$layerm->slices};
@ -303,11 +352,16 @@ sub detect_surfaces_type {
if (@top && @bottom) {
my $overlapping = intersection_ex([ map $_->p, @top ], [ map $_->p, @bottom ]);
Slic3r::debugf " layer %d contains %d membrane(s)\n", $layerm->id, scalar(@$overlapping);
@top = $surface_difference->([@top], $overlapping, S_TYPE_TOP, $layerm);
@top = $surface_difference->([map $_->expolygon, @top], $overlapping, S_TYPE_TOP, $layerm);
}
# find internal surfaces (difference between top/bottom surfaces and others)
@internal = $surface_difference->($layerm->slices, [@top, @bottom], S_TYPE_INTERNAL, $layerm);
@internal = $surface_difference->(
[ map $_->expolygon, @{$layerm->slices} ],
[ map $_->expolygon, @top, @bottom ],
S_TYPE_INTERNAL,
$layerm,
);
# save surfaces to layer
@{$layerm->slices} = (@bottom, @top, @internal);
@ -334,13 +388,122 @@ sub detect_surfaces_type {
}
}
sub clip_fill_surfaces {
my $self = shift;
return unless $Slic3r::Config->infill_only_where_needed;
# We only want infill under ceilings; this is almost like an
# internal support material.
my $additional_margin = scale 3;
my @overhangs = ();
for my $layer_id (reverse 0..$#{$self->layers}) {
my $layer = $self->layers->[$layer_id];
# clip this layer's internal surfaces to @overhangs
foreach my $layerm (@{$layer->regions}) {
my @new_internal = map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNAL,
),
@{intersection_ex(
[ map @$_, @overhangs ],
[ map @{$_->expolygon}, grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces} ],
)};
@{$layerm->fill_surfaces} = (
@new_internal,
(grep $_->surface_type != S_TYPE_INTERNAL, @{$layerm->fill_surfaces}),
);
}
# get this layer's overhangs
if ($layer_id > 0) {
my $lower_layer = $self->layers->[$layer_id-1];
# loop through layer regions so that we can use each region's
# specific overhang width
foreach my $layerm (@{$layer->regions}) {
my $overhang_width = $layerm->overhang_width;
# we want to support any solid surface, not just tops
# (internal solids might have been generated)
push @overhangs, map $_->offset_ex($additional_margin), @{intersection_ex(
[ map @{$_->expolygon}, grep $_->surface_type != S_TYPE_INTERNAL, @{$layerm->fill_surfaces} ],
[ map @$_, map $_->offset_ex(-$overhang_width), @{$lower_layer->slices} ],
)};
}
}
}
}
sub bridge_over_infill {
my $self = shift;
for my $layer_id (1..$#{$self->layers}) {
my $layer = $self->layers->[$layer_id];
my $lower_layer = $self->layers->[$layer_id-1];
foreach my $layerm (@{$layer->regions}) {
# compute the areas needing bridge math
my @internal_solid = grep $_->surface_type == S_TYPE_INTERNALSOLID, @{$layerm->fill_surfaces};
my @lower_internal = grep $_->surface_type == S_TYPE_INTERNAL, map @{$_->fill_surfaces}, @{$lower_layer->regions};
my $to_bridge = intersection_ex(
[ map $_->p, @internal_solid ],
[ map $_->p, @lower_internal ],
);
next unless @$to_bridge;
Slic3r::debugf "Bridging %d internal areas at layer %d\n", scalar(@$to_bridge), $layer_id;
# build the new collection of fill_surfaces
{
my @new_surfaces = grep $_->surface_type != S_TYPE_INTERNALSOLID, @{$layerm->fill_surfaces};
push @new_surfaces, map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNALBRIDGE,
), @$to_bridge;
push @new_surfaces, map Slic3r::Surface->new(
expolygon => $_,
surface_type => S_TYPE_INTERNALSOLID,
), @{diff_ex(
[ map $_->p, @internal_solid ],
[ map @$_, @$to_bridge ],
1,
)};
@{$layerm->fill_surfaces} = @new_surfaces;
}
# exclude infill from the layers below if needed
# see discussion at https://github.com/alexrj/Slic3r/issues/240
{
my $excess = $layerm->extruders->{infill}->bridge_flow->width - $layerm->height;
for (my $i = $layer_id-1; $excess >= $self->layers->[$i]->height; $i--) {
Slic3r::debugf " skipping infill below those areas at layer %d\n", $i;
foreach my $lower_layerm (@{$self->layers->[$i]->regions}) {
my @new_surfaces = ();
# subtract the area from all types of surfaces
foreach my $group (Slic3r::Surface->group(@{$lower_layerm->fill_surfaces})) {
push @new_surfaces, map Slic3r::Surface->new(
expolygon => $_,
surface_type => $group->[0]->surface_type,
), @{diff_ex(
[ map $_->p, @$group ],
[ map @$_, @$to_bridge ],
)};
}
@{$lower_layerm->fill_surfaces} = @new_surfaces;
}
$excess -= $self->layers->[$i]->height;
}
}
}
}
}
sub discover_horizontal_shells {
my $self = shift;
Slic3r::debugf "==> DISCOVERING HORIZONTAL SHELLS\n";
my $area_threshold = $Slic3r::flow->scaled_spacing ** 2;
for my $region_id (0 .. ($self->print->regions_count-1)) {
for (my $i = 0; $i < $self->layer_count; $i++) {
my $layerm = $self->layers->[$i]->regions->[$region_id];
@ -352,7 +515,9 @@ sub discover_horizontal_shells {
foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) {
# find slices of current type for current layer
my @surfaces = grep $_->surface_type == $type, @{$layerm->slices} or next;
# get both slices and fill_surfaces before the former contains the perimeters area
# and the latter contains the enlarged external surfaces
my @surfaces = grep $_->surface_type == $type, @{$layerm->slices}, @{$layerm->fill_surfaces} or next;
my $surfaces_p = [ map $_->p, @surfaces ];
Slic3r::debugf "Layer %d has %d surfaces of type '%s'\n",
$i, scalar(@surfaces), ($type == S_TYPE_TOP ? 'top' : 'bottom');
@ -385,7 +550,7 @@ sub discover_horizontal_shells {
( map @$_, @$new_internal_solid ),
]);
# subtract intersections from layer surfaces to get resulting inner surfaces
# subtract intersections from layer surfaces to get resulting internal surfaces
my $internal = diff_ex(
[ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @neighbor_fill_surfaces ],
[ map @$_, @$internal_solid ],
@ -394,10 +559,7 @@ sub discover_horizontal_shells {
Slic3r::debugf " %d internal-solid and %d internal surfaces found\n",
scalar(@$internal_solid), scalar(@$internal);
# Note: due to floating point math we're going to get some very small
# polygons as $internal; they will be removed by removed_small_features()
# assign resulting inner surfaces to layer
# assign resulting internal surfaces to layer
my $neighbor_fill_surfaces = $self->layers->[$n]->regions->[$region_id]->fill_surfaces;
@$neighbor_fill_surfaces = ();
push @$neighbor_fill_surfaces, Slic3r::Surface->new
@ -423,16 +585,7 @@ sub discover_horizontal_shells {
}
}
@{$layerm->fill_surfaces} = grep $_->expolygon->area > $area_threshold, @{$layerm->fill_surfaces};
}
for (my $i = 0; $i < $self->layer_count; $i++) {
my $layerm = $self->layers->[$i]->regions->[$region_id];
# if hollow object is requested, remove internal surfaces
if ($Slic3r::Config->fill_density == 0) {
@{$layerm->fill_surfaces} = grep $_->surface_type != S_TYPE_INTERNAL, @{$layerm->fill_surfaces};
}
@{$layerm->fill_surfaces} = grep $_->expolygon->area > $layerm->infill_area_threshold, @{$layerm->fill_surfaces};
}
}
}
@ -442,86 +595,75 @@ sub combine_infill {
my $self = shift;
return unless $Slic3r::Config->infill_every_layers > 1 && $Slic3r::Config->fill_density > 0;
my $area_threshold = $Slic3r::flow->scaled_spacing ** 2;
my $layer_count = $self->layer_count;
for my $region_id (0 .. ($self->print->regions_count-1)) {
# start from bottom, skip first layer
for (my $i = 1; $i < $self->layer_count; $i++) {
my $layerm = $self->layers->[$i]->regions->[$region_id];
# limit the number of combined layers to the maximum height allowed by this regions' nozzle
my $every = min(
$Slic3r::Config->infill_every_layers,
int($self->print->regions->[$region_id]->extruders->{infill}->nozzle_diameter/$Slic3r::Config->layer_height),
);
Slic3r::debugf "Infilling every %d layers\n", $every;
# skip bottom layer
for (my $layer_id = $every; $layer_id <= $layer_count-1; $layer_id += $every) {
# get the layers whose infill we want to combine (bottom-up)
my @layerms = map $self->layers->[$_]->regions->[$region_id],
($layer_id - ($every-1)) .. $layer_id;
# skip layer if no internal fill surfaces
next if !grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces};
# for each possible depth, look for intersections with the lower layer
# we do this from the greater depth to the smaller
for (my $d = $Slic3r::Config->infill_every_layers - 1; $d >= 1; $d--) {
next if ($i - $d) <= 0; # do not combine infill for bottom layer
my $lower_layerm = $self->layers->[$i - 1]->regions->[$region_id];
# process internal and internal-solid infill separately
for my $type (S_TYPE_INTERNAL, S_TYPE_INTERNALSOLID) {
# we need to perform a multi-layer intersection, so let's split it in pairs
# select surfaces of the lower layer having the depth we're looking for
my @lower_surfaces = grep $_->depth_layers == $d && $_->surface_type == S_TYPE_INTERNAL,
@{$lower_layerm->fill_surfaces};
next if !@lower_surfaces;
# initialize the intersection with the candidates of the lowest layer
my $intersection = [ map $_->expolygon, grep $_->surface_type == $type, @{$layerms[0]->fill_surfaces} ];
# calculate intersection between our surfaces and theirs
my $intersection = intersection_ex(
[ map $_->p, grep $_->depth_layers <= $d, @lower_surfaces ],
[ map $_->p, grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces} ],
undef, 1,
);
# purge intersections, skip tiny regions
@$intersection = grep $_->area > $area_threshold, @$intersection;
next if !@$intersection;
# new fill surfaces of the current layer are:
# - any non-internal surface
# - intersections found (with a $d + 1 depth)
# - any internal surface not belonging to the intersection (with its original depth)
{
my @new_surfaces = ();
push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$layerm->fill_surfaces};
push @new_surfaces, map Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $d + 1), @$intersection;
foreach my $depth (reverse $d..$Slic3r::Config->infill_every_layers) {
push @new_surfaces, map Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth),
# difference between our internal layers with depth == $depth
# and the intersection found
@{diff_ex(
[
map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth,
@{$layerm->fill_surfaces},
],
[ map @$_, @$intersection ],
1,
)};
}
@{$layerm->fill_surfaces} = @new_surfaces;
# start looping from the second layer and intersect the current intersection with it
for my $layerm (@layerms[1 .. $#layerms]) {
$intersection = intersection_ex(
[ map @$_, @$intersection ],
[ map @{$_->expolygon}, grep $_->surface_type == $type, @{$layerm->fill_surfaces} ],
);
}
# now we remove the intersections from lower layer
{
my @new_surfaces = ();
push @new_surfaces, grep $_->surface_type != S_TYPE_INTERNAL, @{$lower_layerm->fill_surfaces};
foreach my $depth (1..$Slic3r::Config->infill_every_layers) {
push @new_surfaces, map Slic3r::Surface->new
(expolygon => $_, surface_type => S_TYPE_INTERNAL, depth_layers => $depth),
# difference between internal layers with depth == $depth
# and the intersection found
@{diff_ex(
[
map $_->p, grep $_->surface_type == S_TYPE_INTERNAL && $_->depth_layers == $depth,
@{$lower_layerm->fill_surfaces},
],
[ map @$_, @$intersection ],
1,
)};
my $area_threshold = $layerms[0]->infill_area_threshold;
@$intersection = grep $_->area > $area_threshold, @$intersection;
next if !@$intersection;
Slic3r::debugf " combining %d %s regions from layers %d-%d\n",
scalar(@$intersection),
($type == S_TYPE_INTERNAL ? 'internal' : 'internal-solid'),
$layer_id-($every-1), $layer_id;
# $intersection now contains the regions that can be combined across the full amount of layers
# so let's remove those areas from all layers
my @intersection_with_clearance = map $_->offset(
$layerms[-1]->infill_flow->scaled_width / 2
+ $layerms[-1]->perimeter_flow->scaled_width / 2
# Because fill areas for rectilinear and honeycomb are grown
# later to overlap perimeters, we need to counteract that too.
+ (($type == S_TYPE_INTERNALSOLID || $Slic3r::Config->fill_pattern =~ /(rectilinear|honeycomb)/)
? $layerms[-1]->infill_flow->scaled_width * &Slic3r::PERIMETER_INFILL_OVERLAP_OVER_SPACING
: 0)
), @$intersection;
foreach my $layerm (@layerms) {
my @this_type = grep $_->surface_type == $type, @{$layerm->fill_surfaces};
my @other_types = grep $_->surface_type != $type, @{$layerm->fill_surfaces};
@this_type = map Slic3r::Surface->new(expolygon => $_, surface_type => $type),
@{diff_ex(
[ map @{$_->expolygon}, @this_type ],
[ @intersection_with_clearance ],
)};
# apply surfaces back with adjusted depth to the uppermost layer
if ($layerm->id == $layer_id) {
push @this_type,
map Slic3r::Surface->new(expolygon => $_, surface_type => $type, depth_layers => $every),
@$intersection;
}
@{$lower_layerm->fill_surfaces} = @new_surfaces;
@{$layerm->fill_surfaces} = (@this_type, @other_types);
}
}
}
@ -530,14 +672,17 @@ sub combine_infill {
sub generate_support_material {
my $self = shift;
return if $self->layer_count < 2;
my $threshold_rad = $Slic3r::Config->support_material_threshold
? deg2rad($Slic3r::Config->support_material_threshold + 1) # +1 makes the threshold inclusive
: PI/2 - atan2($self->layers->[1]->regions->[0]->perimeter_flow->width/$Slic3r::Config->layer_height/2, 1);
Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad);
my $overhang_width;
if ($Slic3r::Config->support_material_threshold) {
my $threshold_rad = deg2rad($Slic3r::Config->support_material_threshold + 1); # +1 makes the threshold inclusive
Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad);
$overhang_width = scale $Slic3r::Config->layer_height * ((cos $threshold_rad) / (sin $threshold_rad));
} else {
$overhang_width = $self->layers->[1]->regions->[0]->overhang_width;
}
my $flow = $self->print->support_material_flow;
my $overhang_width = $threshold_rad == 0 ? undef : scale $Slic3r::Config->layer_height * ((cos $threshold_rad) / (sin $threshold_rad));
my $distance_from_object = 1.5 * $flow->scaled_width;
my $pattern_spacing = ($Slic3r::Config->support_material_spacing > $flow->spacing)
? $Slic3r::Config->support_material_spacing
@ -546,86 +691,141 @@ sub generate_support_material {
# determine support regions in each layer (for upper layers)
Slic3r::debugf "Detecting regions\n";
my %layers = (); # this represents the areas of each layer having to support upper layers (excluding interfaces)
my %layers_interfaces = (); # this represents the areas of each layer having an overhang in the immediately upper layer
my %layers_interfaces = (); # this represents the areas of each layer to be filled with interface pattern, excluding the contact areas which are stored separately
my %layers_contact_areas = (); # this represents the areas of each layer having an overhang in the immediately upper layer
{
my @current_support_regions = (); # expolygons we've started to support (i.e. below the empty interface layers)
my @queue = (); # the number of items of this array determines the number of empty interface layers
my @upper_layers_overhangs = (map [], 1..$Slic3r::Config->support_material_interface_layers);
for my $i (reverse 0 .. $#{$self->layers}) {
next unless $Slic3r::Config->support_material
|| ($i <= $Slic3r::Config->raft_layers) # <= because we need to start from the first non-raft layer
|| ($i <= $Slic3r::Config->support_material_enforce_layers + $Slic3r::Config->raft_layers);
my $layer = $self->layers->[$i];
my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef;
# $queue[-1] contains the overhangs of the upper layer, regardless of any empty interface layers
# $queue[0] contains the overhangs of the first upper layer above the empty interface layers
$layers_interfaces{$i} = [@{ $queue[-1] || [] }];
my @current_layer_offsetted_slices = map $_->offset_ex($distance_from_object), @{$layer->slices};
# step 1: generate support material in current layer (for upper layers)
push @current_support_regions, @{ shift @queue } if @queue && $i < $#{$self->layers};
# $upper_layers_overhangs[-1] contains the overhangs of the upper layer, regardless of any interface layers
# $upper_layers_overhangs[0] contains the overhangs of the first upper layer above the interface layers
# we only consider the overhangs of the upper layer to define contact areas of the current one
$layers_contact_areas{$i} = diff_ex(
[ map @$_, @{ $upper_layers_overhangs[-1] || [] } ],
[ map @$_, @current_layer_offsetted_slices ],
);
$_->simplify($flow->scaled_spacing) for @{$layers_contact_areas{$i}};
# to define interface regions of this layer we consider the overhangs of all the upper layers
# minus the first one
$layers_interfaces{$i} = diff_ex(
[ map @$_, map @$_, @upper_layers_overhangs[0 .. $#upper_layers_overhangs-1] ],
[
(map @$_, @current_layer_offsetted_slices),
(map @$_, @{ $layers_contact_areas{$i} }),
],
);
$_->simplify($flow->scaled_spacing) for @{$layers_interfaces{$i}};
# generate support material in current layer (for upper layers)
@current_support_regions = @{diff_ex(
[ map @$_, @current_support_regions ],
[
(map @$_, @current_support_regions),
(map @$_, @{ $upper_layers_overhangs[-1] || [] }), # only considering -1 instead of the whole array contents is just an optimization
],
[ map @$_, @{$layer->slices} ],
)};
shift @upper_layers_overhangs;
$layers{$i} = diff_ex(
[ map @$_, @current_support_regions ],
[
(map @$_, map $_->offset_ex($distance_from_object), @{$layer->slices}),
(map @$_, @current_layer_offsetted_slices),
(map @$_, @{ $layers_interfaces{$i} }),
],
);
$_->simplify($flow->scaled_spacing * 2) for @{$layers{$i}};
$_->simplify($flow->scaled_spacing) for @{$layers{$i}};
# step 2: get layer overhangs and put them into queue for adding support inside lower layers
# get layer overhangs and put them into queue for adding support inside lower layers;
# we need an angle threshold for this
my @overhangs = ();
if ($lower_layer) {
@overhangs = map $_->offset_ex(2 * $overhang_width), @{diff_ex(
[ map @$_, map $_->offset_ex(-$overhang_width), @{$layer->slices} ],
# consider all overhangs regardless of their angle if we're told to enforce support on this layer
my $distance = $i <= ($Slic3r::Config->support_material_enforce_layers + $Slic3r::Config->raft_layers)
? 0
: $overhang_width;
@overhangs = map $_->offset_ex(2 * $distance), @{diff_ex(
[ map @$_, map $_->offset_ex(-$distance), @{$layer->slices} ],
[ map @$_, @{$lower_layer->slices} ],
1,
)};
}
push @queue, [@overhangs];
push @upper_layers_overhangs, [@overhangs];
if ($Slic3r::debug) {
printf "Layer %d (z = %.2f) has %d generic support areas, %d normal interface areas, %d contact areas\n",
$i, unscale($layer->print_z), scalar(@{$layers{$i}}), scalar(@{$layers_interfaces{$i}}), scalar(@{$layers_contact_areas{$i}});
}
}
}
return if !map @$_, values %layers;
# generate paths for the pattern that we're going to use
Slic3r::debugf "Generating patterns\n";
my $support_patterns = []; # in case we want cross-hatching
my $support_patterns = [];
my $support_interface_patterns = [];
{
# 0.5 makes sure the paths don't get clipped externally when applying them to layers
my @support_material_areas = map $_->offset_ex(- 0.5 * $flow->scaled_width),
# 0.5 ensures the paths don't get clipped externally when applying them to layers
my @areas = map $_->offset_ex(- 0.5 * $flow->scaled_width),
@{union_ex([ map $_->contour, map @$_, values %layers ])};
my $filler = Slic3r::Fill->filler($Slic3r::Config->support_material_pattern);
$filler->angle($Slic3r::Config->support_material_angle);
{
my @patterns = ();
foreach my $expolygon (@support_material_areas) {
my @paths = $filler->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon),
density => $flow->spacing / $pattern_spacing,
flow_spacing => $flow->spacing,
);
my $params = shift @paths;
push @patterns,
map Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(@$_),
role => EXTR_ROLE_SUPPORTMATERIAL,
height => undef,
flow_spacing => $params->{flow_spacing},
), @paths;
my $pattern = $Slic3r::Config->support_material_pattern;
my @angles = ($Slic3r::Config->support_material_angle);
if ($pattern eq 'rectilinear-grid') {
$pattern = 'rectilinear';
push @angles, $angles[0] + 90;
}
my $filler = Slic3r::Fill->filler($pattern);
my $make_pattern = sub {
my ($expolygon, $density) = @_;
my @paths = $filler->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon),
density => $density,
flow_spacing => $flow->spacing,
);
my $params = shift @paths;
return map Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(@$_),
role => EXTR_ROLE_SUPPORTMATERIAL,
height => undef,
flow_spacing => $params->{flow_spacing},
), @paths;
};
foreach my $angle (@angles) {
$filler->angle($angle);
{
my $density = $flow->spacing / $pattern_spacing;
push @$support_patterns, [ map $make_pattern->($_, $density), @areas ];
}
if ($Slic3r::Config->support_material_interface_layers > 0) {
# if pattern is not cross-hatched, rotate the interface pattern by 90° degrees
$filler->angle($angle + 90) if @angles == 1;
my $spacing = $Slic3r::Config->support_material_interface_spacing;
my $density = $spacing == 0 ? 1 : $flow->spacing / $spacing;
push @$support_interface_patterns, [ map $make_pattern->($_, $density), @areas ];
}
push @$support_patterns, [@patterns];
}
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("support_$_.svg",
polylines => [ map $_->polyline, map @$_, $support_patterns->[$_] ],
polygons => [ map @$_, @support_material_areas ],
red_polylines => [ map $_->polyline, map @$_, $support_interface_patterns->[$_] ],
polygons => [ map @$_, @areas ],
) for 0 .. $#$support_patterns;
}
}
@ -634,39 +834,73 @@ sub generate_support_material {
Slic3r::debugf "Applying patterns\n";
{
my $clip_pattern = sub {
my ($layer_id, $expolygons, $height) = @_;
my ($layer_id, $expolygons, $height, $is_interface) = @_;
my @paths = ();
foreach my $expolygon (@$expolygons) {
push @paths,
map $_->pack,
map {
$_->height($height);
# useless line because this coderef isn't called for layer 0 anymore;
# let's keep it here just in case we want to make the base flange optional
# in the future
$_->flow_spacing($self->print->first_layer_support_material_flow->spacing)
if $layer_id == 0;
$_;
}
map $_->clip_with_expolygon($expolygon),
map $_->clip_with_polygon($expolygon->bounding_box_polygon),
@{$support_patterns->[ $layer_id % @$support_patterns ]};
###map $_->clip_with_polygon($expolygon->bounding_box_polygon), # currently disabled as a workaround for Boost failing at being idempotent
($is_interface && @$support_interface_patterns)
? @{$support_interface_patterns->[ $layer_id % @$support_interface_patterns ]}
: @{$support_patterns->[ $layer_id % @$support_patterns ]};
};
return @paths;
};
my %layer_paths = ();
my %layer_interface_paths = ();
my %layer_contact_paths = ();
my %layer_islands = ();
my $process_layer = sub {
my ($layer_id) = @_;
my $layer = $self->layers->[$layer_id];
my $paths = [ $clip_pattern->($layer_id, $layers{$layer_id}, $layer->height) ];
my $interface_paths = [ $clip_pattern->($layer_id, $layers_interfaces{$layer_id}, $layer->support_material_interface_height) ];
my $islands = union_ex([ map @$_, map @$_, $layers{$layer_id}, $layers_interfaces{$layer_id} ]);
return ($paths, $interface_paths, $islands);
my ($paths, $contact_paths) = ([], []);
my $islands = union_ex([ map @$_, map @$_, $layers{$layer_id}, $layers_contact_areas{$layer_id} ]);
# make a solid base on bottom layer
if ($layer_id == 0) {
my $filler = Slic3r::Fill->filler('rectilinear');
$filler->angle($Slic3r::Config->support_material_angle + 90);
foreach my $expolygon (@$islands) {
my @paths = $filler->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon),
density => 0.5,
flow_spacing => $self->print->first_layer_support_material_flow->spacing,
);
my $params = shift @paths;
push @$paths, map Slic3r::ExtrusionPath->new(
polyline => Slic3r::Polyline->new(@$_),
role => EXTR_ROLE_SUPPORTMATERIAL,
height => undef,
flow_spacing => $params->{flow_spacing},
), @paths;
}
} else {
$paths = [
$clip_pattern->($layer_id, $layers{$layer_id}, $layer->height),
$clip_pattern->($layer_id, $layers_interfaces{$layer_id}, $layer->height, 1),
];
$contact_paths = [ $clip_pattern->($layer_id, $layers_contact_areas{$layer_id}, $layer->support_material_contact_height, 1) ];
}
return ($paths, $contact_paths, $islands);
};
Slic3r::parallelize(
items => [ keys %layers ],
thread_cb => sub {
my $q = shift;
$Slic3r::Geometry::Clipper::clipper = Math::Clipper->new;
my $result = {};
while (defined (my $layer_id = $q->dequeue)) {
$result->{$layer_id} = [ $process_layer->($layer_id) ];
@ -675,10 +909,10 @@ sub generate_support_material {
},
collect_cb => sub {
my $result = shift;
($layer_paths{$_}, $layer_interface_paths{$_}, $layer_islands{$_}) = @{$result->{$_}} for keys %$result;
($layer_paths{$_}, $layer_contact_paths{$_}, $layer_islands{$_}) = @{$result->{$_}} for keys %$result;
},
no_threads_cb => sub {
($layer_paths{$_}, $layer_interface_paths{$_}, $layer_islands{$_}) = $process_layer->($_) for keys %layers;
($layer_paths{$_}, $layer_contact_paths{$_}, $layer_islands{$_}) = $process_layer->($_) for keys %layers;
},
);
@ -686,9 +920,9 @@ sub generate_support_material {
my $layer = $self->layers->[$layer_id];
$layer->support_islands($layer_islands{$layer_id});
$layer->support_fills(Slic3r::ExtrusionPath::Collection->new);
$layer->support_interface_fills(Slic3r::ExtrusionPath::Collection->new);
$layer->support_contact_fills(Slic3r::ExtrusionPath::Collection->new);
push @{$layer->support_fills->paths}, @{$layer_paths{$layer_id}};
push @{$layer->support_interface_fills->paths}, @{$layer_interface_paths{$layer_id}};
push @{$layer->support_contact_fills->paths}, @{$layer_contact_paths{$layer_id}};
}
}
}

View file

@ -130,7 +130,7 @@ sub output_lines {
sub write_svg {
my ($svg, $filename) = @_;
open my $fh, '>', $filename;
Slic3r::open(\my $fh, '>', $filename);
print $fh $svg->xmlify;
close $fh;
printf "SVG written to %s\n", $filename;

View file

@ -4,7 +4,7 @@ use warnings;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(S_TYPE_TOP S_TYPE_BOTTOM S_TYPE_INTERNAL S_TYPE_INTERNALSOLID);
our @EXPORT_OK = qw(S_TYPE_TOP S_TYPE_BOTTOM S_TYPE_INTERNAL S_TYPE_INTERNALSOLID S_TYPE_INTERNALBRIDGE);
our %EXPORT_TAGS = (types => \@EXPORT_OK);
use constant S_EXPOLYGON => 0;
@ -17,6 +17,7 @@ use constant S_TYPE_TOP => 0;
use constant S_TYPE_BOTTOM => 1;
use constant S_TYPE_INTERNAL => 2;
use constant S_TYPE_INTERNALSOLID => 3;
use constant S_TYPE_INTERNALBRIDGE => 4;
sub new {
my $class = shift;
@ -34,8 +35,8 @@ sub new {
sub expolygon { $_[0][S_EXPOLYGON] }
sub surface_type { $_[0][S_SURFACE_TYPE] = $_[1] if defined $_[1]; $_[0][S_SURFACE_TYPE] }
sub depth_layers { $_[0][S_DEPTH_LAYERS] } # this integer represents the thickness of the surface expressed in layers
sub bridge_angle { $_[0][S_BRIDGE_ANGLE] }
sub additional_inner_perimeters { $_[0][S_ADDITIONAL_INNER_PERIMETERS] = $_[1] if $_[1]; $_[0][S_ADDITIONAL_INNER_PERIMETERS] }
sub bridge_angle { $_[0][S_BRIDGE_ANGLE] = $_[1] if defined $_[1]; $_[0][S_BRIDGE_ANGLE] }
sub additional_inner_perimeters { $_[0][S_ADDITIONAL_INNER_PERIMETERS] = $_[1] if defined $_[1]; $_[0][S_ADDITIONAL_INNER_PERIMETERS] }
# delegate handles
sub encloses_point { $_[0]->expolygon->encloses_point }
@ -51,7 +52,7 @@ sub group {
my %unique_types = ();
foreach my $surface (@surfaces) {
my $type = ($params->{merge_solid} && grep { $surface->surface_type == $_ } S_TYPE_TOP, S_TYPE_BOTTOM, S_TYPE_INTERNALSOLID)
my $type = ($params->{merge_solid} && $surface->is_solid)
? 'solid'
: $surface->surface_type;
$type .= "_" . ($surface->bridge_angle // ''); #/
@ -89,4 +90,28 @@ sub p {
return @{$self->expolygon};
}
sub is_solid {
my $self = shift;
my $type = $self->surface_type;
# S_TYPE_INTERNALBRIDGE is not solid because we can't merge it with other solid types
return $type == S_TYPE_TOP
|| $type == S_TYPE_BOTTOM
|| $type == S_TYPE_INTERNALSOLID;
}
sub is_internal {
my $self = shift;
my $type = $self->surface_type;
return $type == S_TYPE_INTERNAL
|| $type == S_TYPE_INTERNALSOLID
|| $type == S_TYPE_INTERNALBRIDGE;
}
sub is_bridge {
my $self = shift;
my $type = $self->surface_type;
return $type == S_TYPE_BOTTOM
|| $type == S_TYPE_INTERNALBRIDGE;
}
1;

View file

@ -15,30 +15,34 @@ my %cuboids = (
'2x20x10' => [2, 20,10],
);
sub init_print {
my ($model_name, %params) = @_;
sub model {
my ($model_name) = @_;
my ($vertices, $facets);
if ($cuboids{$model_name}) {
my ($x, $y, $z) = @{ $cuboids{$model_name} };
$vertices = [
[$x,$y,0], [$x,0,0], [0,0,0], [0,$y,0], [$x,$y,$z], [0,$y,$z], [0,0,$z], [$x,0,$z],
];
$facets = [
[0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5],
],
}
my $model = Slic3r::Model->new;
{
my ($vertices, $facets);
if ($cuboids{$model_name}) {
my ($x, $y, $z) = @{ $cuboids{$model_name} };
$vertices = [
[$x,$y,0], [$x,0,0], [0,0,0], [0,$y,0], [$x,$y,$z], [0,$y,$z], [0,0,$z], [$x,0,$z],
];
$facets = [
[0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5],
],
}
$model->add_object(vertices => $vertices)->add_volume(facets => $facets);
}
$model->add_object(vertices => $vertices)->add_volume(facets => $facets);
return $model;
}
sub init_print {
my ($model_name, %params) = @_;
my $config = Slic3r::Config->new_from_defaults;
$config->apply($params{config}) if $params{config};
$config->set('gcode_comments', 1) if $ENV{SLIC3R_TESTS_GCODE};
my $print = Slic3r::Print->new(config => $config);
$print->add_model($model);
$print->add_model(model($model_name));
$print->validate;
return $print;

View file

@ -152,9 +152,9 @@ sub check_manifoldness {
my ($first_bad_edge_id) =
grep { @{ $self->edges_facets->[$_] } != 2 } 0..$#{$self->edges_facets};
if (defined $first_bad_edge_id) {
warn sprintf "Warning: The input file contains a hole near edge %f-%f (not manifold). "
warn sprintf "Warning: The input file contains a hole near edge %f,%f,%f-%f,%f,%f (not manifold). "
. "You might want to repair it and retry, or to check the resulting G-code before printing anyway.\n",
@{$self->edges->[$first_bad_edge_id]};
map @{$self->vertices->[$_]}, @{$self->edges->[$first_bad_edge_id]};
return 0;
}
return 1;

View file

@ -156,7 +156,7 @@ $j
(default: $config->{print_center}->[0],$config->{print_center}->[1])
--z-offset Additional height in mm to add to vertical coordinates
(+/-, default: $config->{z_offset})
--gcode-flavor The type of G-code to generate (reprap/teacup/makerbot/mach3/no-extrusion,
--gcode-flavor The type of G-code to generate (reprap/teacup/makerbot/sailfish/mach3/no-extrusion,
default: $config->{gcode_flavor})
--use-relative-e-distances Enable this to get relative E values
--gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported
@ -201,6 +201,17 @@ $j
--first-layer-speed Speed of print moves for bottom layer, expressed either as an absolute
value or as a percentage over normal speeds (default: $config->{first_layer_speed})
Acceleration options:
--perimeter-acceleration
Overrides firmware's default acceleration for perimeters. (mm/s^2, set zero
to disable; default: $config->{perimeter_acceleration})
--infill-acceleration
Overrides firmware's default acceleration for infill. (mm/s^2, set zero
to disable; default: $config->{infill_acceleration})
--default-acceleration
Acceleration will be reset to this value after the specific settings above
have been applied. (mm/s^2, set zero to disable; default: $config->{travel_speed})
Accuracy options:
--layer-height Layer height in mm (default: $config->{layer_height})
--first-layer-height Layer height for first layer (mm or %, default: $config->{first_layer_height})
@ -227,12 +238,15 @@ $j
--toolchange-gcode Load tool-change G-code from the supplied file (default: nothing).
--extra-perimeters Add more perimeters when needed (default: yes)
--randomize-start Randomize starting point across layers (default: yes)
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
--only-retract-when-crossing-perimeters
Disable retraction when travelling between infill paths inside the same island.
(default: no)
--solid-infill-below-area
Force solid infill when a region has a smaller area than this threshold
(mm^2, default: $config->{solid_infill_below_area})
--infill-only-where-needed
Only infill under ceilings (default: no)
Support material options:
--support-material Generate support material for overhangs
@ -245,6 +259,14 @@ $j
Spacing between pattern lines (mm, default: $config->{support_material_spacing})
--support-material-angle
Support material angle in degrees (range: 0-90, default: $config->{support_material_angle})
--support-material-interface-layers
Number of perpendicular layers between support material and object (0+, default: $config->{support_material_interface_layers})
--support-material-interface-spacing
Spacing between interface pattern lines (mm, set 0 to get a solid layer, default: $config->{support_material_interface_spacing})
--raft-layers Number of layers to raise the printed objects by (range: 0+, default: $config->{raft_layers})
--support-material-enforce-layers
Enforce support material on the specified number of layers from bottom,
regardless of --support-material and threshold (0+, default: $config->{support_material_enforce_layers})
Retraction options:
--retract-length Length of retraction in mm when pausing extrusion (default: $config->{retract_length}[0])
@ -311,10 +333,12 @@ $j
(like 0.65) or a percentage over layer height (like 200%)
--first-layer-extrusion-width
Set a different extrusion width for first layer
--perimeters-extrusion-width
--perimeter-extrusion-width
Set a different extrusion width for perimeters
--infill-extrusion-width
Set a different extrusion width for infill
--top-infill-extrusion-width
Set a different extrusion width for top infill
--support-material-extrusion-width
Set a different extrusion width for support material
--bridge-flow-ratio Multiplier for extrusion when bridging (> 0, default: $config->{bridge_flow_ratio})
@ -322,7 +346,7 @@ $j
Multiple extruder options:
--extruder-offset Offset of each extruder, if firmware doesn't handle the displacement
(can be specified multiple times, default: 0x0)
--perimeters-extruder
--perimeter-extruder
Extruder to use for perimeters (1+, default: 1)
--infill-extruder Extruder to use for infill (1+, default: 1)
--support-material-extruder

View file

@ -32,8 +32,9 @@ use Slic3r;
my $polyline = Slic3r::Polyline->new([
[0,0],[0.5,0.5],[1,0],[1.25,-0.25],[1.5,.5],
]);
$polyline->simplify(0.25);
is_deeply $polyline, [ [0, 0], [0.5, 0.5], [1.25, -0.25], [1.5, 0.5] ], 'Douglas-Peucker';
$polyline->scale(100);
$polyline->simplify(25);
is_deeply $polyline, [ [0, 0], [50, 50], [125, -25], [150, 50] ], 'Douglas-Peucker';
}
{

74
t/combineinfill.t Normal file
View file

@ -0,0 +1,74 @@
use Test::More tests => 3;
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use List::Util qw(first);
use Slic3r;
use Slic3r::Test;
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('skirts', 0);
$config->set('solid_layers', 0);
$config->set('bottom_solid_layers', 0);
$config->set('top_solid_layers', 0);
$config->set('infill_every_layers', 6);
$config->set('layer_height', 0.06);
$config->set('perimeters', 1);
my $test = sub {
my ($shift) = @_;
my $self = Slic3r::Test::init_print('20mm_cube', config => $config);
$shift /= &Slic3r::SCALING_FACTOR;
my $scale = 4; # make room for fat infill lines with low layer height
# Put a slope on the box's sides by shifting x and y coords by $tilt * (z / boxheight).
# The test here is to put such a slight slope on the walls that it should
# not trigger any extra fill on fill layers that should be empty when
# combine infill is enabled.
$_->[0] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices};
$_->[1] += $shift * ($_->[2] / (20 / &Slic3r::SCALING_FACTOR)) for @{$self->objects->[0]->meshes->[0]->vertices};
$_ = [$_->[0]*$scale, $_->[1]*$scale, $_->[2]] for @{$self->objects->[0]->meshes->[0]->vertices};
# copy of Print::export_gcode() up to the point
# after fill surfaces are combined
$self->init_extruders;
$_->slice(keep_meshes => 1) for @{$self->objects};
$_->make_perimeters for @{$self->objects};
foreach my $layer (map @{$_->layers}, @{$self->objects}) {
$_->simplify(&Slic3r::SCALED_RESOLUTION)
for @{$layer->slices}, (map $_->expolygon, map @{$_->slices}, @{$layer->regions});
}
$_->detect_surfaces_type for @{$self->objects};
$_->prepare_fill_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
$_->process_external_surfaces for map @{$_->regions}, map @{$_->layers}, @{$self->objects};
$_->discover_horizontal_shells for @{$self->objects};
$_->combine_infill for @{$self->objects};
# Only layers with id % 6 == 0 should have fill.
my $spurious_infill = 0;
foreach my $layer (map @{$_->layers}, @{$self->objects}) {
++$spurious_infill if ($layer->id % 6 && grep @{$_->fill_surfaces} > 0, @{$layer->regions});
}
$spurious_infill -= scalar(@{$self->objects->[0]->layers} - 1) % 6;
fail "spurious fill surfaces found on layers that should have none (walls " . sprintf("%.4f", Slic3r::Geometry::rad2deg(atan2($shift, 20/&Slic3r::SCALING_FACTOR))) . " degrees off vertical)"
unless $spurious_infill == 0;
1;
};
# Test with mm skew offsets for the top of the 20mm-high box
for my $shift (0, 0.0001, 1) {
ok $test->($shift), "no spurious fill surfaces with box walls " . sprintf("%.4f",Slic3r::Geometry::rad2deg(atan2($shift, 20))) . " degrees off of vertical";
}
}
__END__

View file

@ -48,9 +48,9 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
Slic3r::Polyline->new([0,10], [0,8], [0,5]),
]);
is_deeply
[ map $_->[Y], map @$_, $collection->shortest_path(Slic3r::Point->new(0,30)) ],
[ map $_->[Y], map @$_, $collection->chained_path(Slic3r::Point->new(0,30)) ],
[20, 18, 15, 10, 8, 5],
'shortest path';
'chained path';
}
{
@ -59,9 +59,9 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
Slic3r::Polyline->new([10,5], [15,5], [20,5]),
]);
is_deeply
[ map $_->[X], map @$_, $collection->shortest_path(Slic3r::Point->new(30,0)) ],
[ map $_->[X], map @$_, $collection->chained_path(Slic3r::Point->new(30,0)) ],
[reverse 4, 10, 15, 10, 15, 20],
'shortest path';
'chained path';
}
{
@ -71,9 +71,9 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
Slic3r::Polyline->new([0,10], [0,8], [0,5]),
]);
is_deeply
[ map $_->[Y], map @{$_->unpack->polyline}, $collection->shortest_path(Slic3r::Point->new(0,30)) ],
[ map $_->[Y], map @{$_->unpack->polyline}, $collection->chained_path(Slic3r::Point->new(0,30)) ],
[20, 18, 15, 10, 8, 5],
'shortest path';
'chained path';
}
{
@ -83,9 +83,9 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
Slic3r::Polyline->new([10,5], [15,5], [20,5]),
]);
is_deeply
[ map $_->[X], map @{$_->unpack->polyline}, $collection->shortest_path(Slic3r::Point->new(30,0)) ],
[ map $_->[X], map @{$_->unpack->polyline}, $collection->chained_path(Slic3r::Point->new(30,0)) ],
[reverse 4, 10, 15, 10, 15, 20],
'shortest path';
'chained path';
}
{

20
t/gcode.t Normal file
View file

@ -0,0 +1,20 @@
use Test::More tests => 1;
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use Slic3r;
use Slic3r::Geometry qw(scale);
{
local $Slic3r::Config = Slic3r::Config->new_from_defaults;
my $gcodegen = Slic3r::GCode->new(layer_count => 1);
$gcodegen->set_shift(10, 10);
is_deeply $gcodegen->last_pos, [scale -10, scale -10], 'last_pos is shifted correctly';
}
__END__

View file

@ -2,7 +2,7 @@ use Test::More;
use strict;
use warnings;
plan tests => 20;
plan tests => 23;
BEGIN {
use FindBin;
@ -154,4 +154,23 @@ is Slic3r::Geometry::can_connect_points(@$points, $polygons), 0, 'can_connect_po
is polygon_is_convex($convex1), 0, 'concave polygon';
}
#==========================================================
{
my $polyline = Slic3r::Polyline->new([0, 0], [10, 0], [20, 0]);
is_deeply [$polyline->lines], [
[ [0, 0], [10, 0] ],
[ [10, 0], [20, 0] ],
], 'polyline_lines';
}
#==========================================================
{
my $polyline = Slic3r::Polygon->new([0, 0], [10, 0], [5, 5]);
my $result = $polyline->split_at_index(1);
is ref($result), 'Slic3r::Polyline', 'split_at_index returns polyline';
is_deeply $result, [ [10, 0], [5, 5], [0, 0], [10, 0] ], 'split_at_index';
}
#==========================================================

View file

@ -1,4 +1,4 @@
use Test::More tests => 4;
use Test::More tests => 5;
use strict;
use warnings;
@ -55,4 +55,15 @@ ok $test->(), "positive Z offset";
$config->set('z_offset', -0.8);
ok $test->(), "negative Z offset";
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('nozzle_diameter', [0.35]);
$config->set('layer_height', 0.1333);
my $print = Slic3r::Test::init_print('2x20x10', config => $config);
$print->init_extruders;
$_->region(0) for @{$print->objects->[0]->layers}; # init layer regions
ok $print->objects->[0]->layers->[1]->support_material_contact_height > 0, 'support_material_contact_height is positive';
}
__END__

View file

@ -24,90 +24,90 @@ is Slic3r::Geometry::point_in_segment([20, 15], [ [10, 10], [20, 20] ]), 0, 'poi
#==========================================================
my $square = [ # ccw
[10, 10],
[20, 10],
[20, 20],
[10, 20],
[100, 100],
[200, 100],
[200, 200],
[100, 200],
];
my $line = Slic3r::Line->new([5, 15], [30, 15]);
my $line = Slic3r::Line->new([50, 150], [300, 150]);
my $intersection = Slic3r::Geometry::clip_segment_polygon($line, $square);
is_deeply $intersection, [ [10, 15], [20, 15] ], 'line is clipped to square';
is_deeply $intersection, [ [100, 150], [200, 150] ], 'line is clipped to square';
#==========================================================
$intersection = Slic3r::Geometry::clip_segment_polygon([ [0, 15], [8, 15] ], $square);
$intersection = Slic3r::Geometry::clip_segment_polygon([ [0, 150], [80, 150] ], $square);
is $intersection, undef, 'external lines are ignored 1';
#==========================================================
$intersection = Slic3r::Geometry::clip_segment_polygon([ [30, 15], [40, 15] ], $square);
$intersection = Slic3r::Geometry::clip_segment_polygon([ [300, 150], [400, 150] ], $square);
is $intersection, undef, 'external lines are ignored 2';
#==========================================================
$intersection = Slic3r::Geometry::clip_segment_polygon([ [12, 12], [18, 16] ], $square);
is_deeply $intersection, [ [12, 12], [18, 16] ], 'internal lines are preserved';
$intersection = Slic3r::Geometry::clip_segment_polygon([ [120, 120], [180, 160] ], $square);
is_deeply $intersection, [ [120, 120], [180, 160] ], 'internal lines are preserved';
#==========================================================
{
my $hole_in_square = [ # cw
[14, 14],
[14, 16],
[16, 16],
[16, 14],
[140, 140],
[140, 160],
[160, 160],
[160, 140],
];
my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square);
is $expolygon->encloses_point([10, 10]), 1, 'corner point is recognized';
is $expolygon->encloses_point([10, 18]), 1, 'point on contour is recognized';
is $expolygon->encloses_point([14, 15]), 1, 'point on hole contour is recognized';
is $expolygon->encloses_point([14, 14]), 1, 'point on hole corner is recognized';
is $expolygon->encloses_point([100, 100]), 1, 'corner point is recognized';
is $expolygon->encloses_point([100, 180]), 1, 'point on contour is recognized';
is $expolygon->encloses_point([140, 150]), 1, 'point on hole contour is recognized';
is $expolygon->encloses_point([140, 140]), 1, 'point on hole corner is recognized';
{
my $intersections = $expolygon->clip_line(Slic3r::Line->new([15,18], [15,15]));
my $intersections = $expolygon->clip_line(Slic3r::Line->new([150,180], [150,150]));
is_deeply $intersections, [
[ [15, 18], [15, 16] ],
[ [150, 180], [150, 160] ],
], 'line is clipped to square with hole';
}
{
my $intersections = $expolygon->clip_line(Slic3r::Line->new([15,15], [15,12]));
my $intersections = $expolygon->clip_line(Slic3r::Line->new([150,150], [150,120]));
is_deeply $intersections, [
[ [15, 14], [15, 12] ],
[ [150, 140], [150, 120] ],
], 'line is clipped to square with hole';
}
{
my $intersections = $expolygon->clip_line(Slic3r::Line->new([12,18], [18,18]));
my $intersections = $expolygon->clip_line(Slic3r::Line->new([120,180], [180,180]));
is_deeply $intersections, [
[ [12,18], [18,18] ],
[ [120,180], [180,180] ],
], 'line is clipped to square with hole';
}
{
my $intersections = $expolygon->clip_line($line);
is_deeply $intersections, [
[ [10, 15], [14, 15] ],
[ [16, 15], [20, 15] ],
[ [100, 150], [140, 150] ],
[ [160, 150], [200, 150] ],
], 'line is clipped to square with hole';
}
{
my $intersections = $expolygon->clip_line(Slic3r::Line->new(reverse @$line));
is_deeply $intersections, [
[ [20, 15], [16, 15] ],
[ [14, 15], [10, 15] ],
[ [200, 150], [160, 150] ],
[ [140, 150], [100, 150] ],
], 'reverse line is clipped to square with hole';
}
{
my $intersections = $expolygon->clip_line(Slic3r::Line->new([10,18], [20,18]));
my $intersections = $expolygon->clip_line(Slic3r::Line->new([100,180], [200,180]));
is_deeply $intersections, [
[ [10, 18], [20, 18] ],
[ [100, 180], [200, 180] ],
], 'tangent line is clipped to square with hole';
}
{
my $polyline = Slic3r::Polyline->new([ [5, 18], [25, 18], [25, 15], [15, 15], [15, 12], [12, 12], [12, 5] ]);
my $polyline = Slic3r::Polyline->new([ [50, 180], [250, 180], [250, 150], [150, 150], [150, 120], [120, 120], [120, 50] ]);
is_deeply [ map $_, $polyline->clip_with_expolygon($expolygon) ], [
[ [10, 18], [20, 18] ],
[ [20, 15], [16, 15] ],
[ [15, 14], [15, 12], [12, 12], [12, 10] ],
[ [100, 180], [200, 180] ],
[ [200, 150], [160, 150] ],
[ [150, 140], [150, 120], [120, 120], [120, 100] ],
], 'polyline is clipped to square with hole';
}
}
@ -144,8 +144,8 @@ is_deeply $intersection, [ [12, 12], [18, 16] ], 'internal lines are preserved';
my $intersections = $expolygon->clip_line($line);
is_deeply $intersections, [
[ [152.742, 288.087], [152.742, 215.179], ],
[ [152.742, 108.088], [152.742, 35.1665] ],
[ [152, 287], [152, 214], ],
[ [152, 107], [152, 35] ],
], 'line is clipped to square with hole';
}

View file

@ -21,6 +21,7 @@ my $test = sub {
my $tool = 0;
my @toolchange_count = (); # track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time
my @retracted = (1); # ignore the first travel move from home to first point
my @retracted_length = (0);
my $lifted = 0;
my $changed_tool = 0;
my $wait_for_toolchange = 0;
@ -52,21 +53,22 @@ my $test = sub {
}
}
if ($info->{retracting}) {
if (_eq(-$info->{dist_E}, $print->extruders->[$tool]->retract_length)) {
$retracted[$tool] = 1;
$retracted_length[$tool] += -$info->{dist_E};
if (_eq($retracted_length[$tool], $print->extruders->[$tool]->retract_length)) {
# okay
} elsif (_eq(-$info->{dist_E}, $print->extruders->[$tool]->retract_length_toolchange)) {
} elsif (_eq($retracted_length[$tool], $print->extruders->[$tool]->retract_length_toolchange)) {
$wait_for_toolchange = 1;
} else {
fail 'retracted by the correct amount';
}
fail 'combining retraction and travel with G0'
if $cmd ne 'G0' && $conf->g0 && ($info->{dist_Z} || $info->{dist_XY});
$retracted[$tool] = 1;
}
if ($info->{extruding}) {
fail 'only extruding while not lifted' if $lifted;
if ($retracted[$tool]) {
my $expected_amount = $print->extruders->[$tool]->retract_length + $print->extruders->[$tool]->retract_restart_extra;
my $expected_amount = $retracted_length[$tool] + $print->extruders->[$tool]->retract_restart_extra;
if ($changed_tool && $toolchange_count[$tool] > 1) {
$expected_amount = $print->extruders->[$tool]->retract_length_toolchange + $print->extruders->[$tool]->retract_restart_extra_toolchange;
$changed_tool = 0;
@ -74,6 +76,7 @@ my $test = sub {
fail 'unretracted by the correct amount'
if !_eq($info->{dist_E}, $expected_amount);
$retracted[$tool] = 0;
$retracted_length[$tool] = 0;
}
}
if ($info->{travel} && $info->{dist_XY} >= $print->extruders->[$tool]->retract_before_travel) {
@ -86,6 +89,7 @@ my $test = sub {
$config->set('retract_length', [1.5]);
$config->set('retract_before_travel', [3]);
$config->set('only_retract_when_crossing_perimeters', 0);
my $retract_tests = sub {
my ($descr) = @_;

View file

@ -40,8 +40,8 @@ use Slic3r::Test;
};
ok $test->(), "proper number of shells is applied";
$config->set('fill_density', 0);
ok $test->(), "proper number of shells is applied even when fill density is none";
}

View file

@ -22,7 +22,7 @@ _arguments -S \
'*--nozzle-diameter[specify nozzle diameter]:nozzle diameter in mm' \
'--print-center[specify print center coordinates]:print center coordinates in mm,mm' \
'--z-offset[specify Z-axis offset]:Z-axis offset in mm' \
'--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup makerbot mach3 no-extrusion)' \
'--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup makerbot sailfish mach3 no-extrusion)' \
'(--use-relative-e-distances --no-use-relative-e-distances)'--{no-,}use-relative-e-distances'[disable/enable relative E values]' \
'--extrusion-axis[specify letter associated with the extrusion axis]:extrusion axis letter' \
'(--gcode-arcs --no-gcode-arcs)'--{no-,}gcode-arcs'[disable/enable G2/G3 commands for native arcs]' \