Merge branch 'master' into xsdata

Conflicts:
	lib/Slic3r.pm
	lib/Slic3r/ExPolygon.pm
	lib/Slic3r/Fill.pm
	lib/Slic3r/Fill/Rectilinear.pm
	lib/Slic3r/GCode.pm
	lib/Slic3r/GUI/Plater.pm
	lib/Slic3r/Geometry/Clipper.pm
	lib/Slic3r/Layer/Region.pm
	lib/Slic3r/Print.pm
	lib/Slic3r/Print/Object.pm
	lib/Slic3r/TriangleMesh.pm
	t/shells.t
	xs/MANIFEST
This commit is contained in:
Alessandro Ranellucci 2013-08-08 02:10:34 +02:00
commit b38cc2c244
60 changed files with 1432 additions and 798 deletions

View file

@ -99,6 +99,7 @@ EOF
# temporarily require this dev version until this upstream bug # temporarily require this dev version until this upstream bug
# is resolved: https://rt.cpan.org/Ticket/Display.html?id=86367 # is resolved: https://rt.cpan.org/Ticket/Display.html?id=86367
system $cpanm, 'SMUELLER/ExtUtils-ParseXS-3.18_04.tar.gz'; system $cpanm, 'SMUELLER/ExtUtils-ParseXS-3.18_04.tar.gz';
system './xs/Build', 'distclean' if -e './xs/Build';
system $cpanm, '--reinstall', './xs'; system $cpanm, '--reinstall', './xs';
} }

View file

@ -88,7 +88,6 @@ t/support.t
t/svg.t t/svg.t
t/vibrationlimit.t t/vibrationlimit.t
utils/amf-to-stl.pl utils/amf-to-stl.pl
utils/file_info.pl
utils/gcode_sectioncut.pl utils/gcode_sectioncut.pl
utils/post-processing/filament-weight.pl utils/post-processing/filament-weight.pl
utils/post-processing/prowl-notification.pl utils/post-processing/prowl-notification.pl

View file

@ -80,7 +80,7 @@ The author of the Silk icon set is Mark James.
## How can I invoke slic3r.pl using the command line? ## How can I invoke slic3r.pl using the command line?
Usage: slic3r.pl [ OPTIONS ] file.stl Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ...
--help Output this usage screen and exit --help Output this usage screen and exit
--version Output the version of Slic3r and exit --version Output the version of Slic3r and exit
@ -90,7 +90,10 @@ The author of the Silk icon set is Mark James.
-o, --output <file> File to output gcode to (by default, the file will be saved -o, --output <file> File to output gcode to (by default, the file will be saved
into the same directory as the input file using the into the same directory as the input file using the
--output-filename-format to generate the filename) --output-filename-format to generate the filename)
--repair Automatically repair given STL files and saves them as _fixed.obj
Non-slicing actions (no G-code will be generated):
--repair Repair given STL files and save them as <name>_fixed.obj
--info Output information about the supplied file(s) and exit
GUI options: GUI options:
--no-plater Disable the plater tab --no-plater Disable the plater tab
@ -322,6 +325,8 @@ The author of the Silk icon set is Mark James.
--infill-extruder Extruder to use for infill (1+, default: 1) --infill-extruder Extruder to use for infill (1+, default: 1)
--support-material-extruder --support-material-extruder
Extruder to use for support material (1+, default: 1) Extruder to use for support material (1+, default: 1)
--support-material-interface-extruder
Extruder to use for support material interface (1+, default: 1)
If you want to change a preset file, just do If you want to change a preset file, just do

View file

@ -19,6 +19,10 @@ our $have_threads;
BEGIN { BEGIN {
use Config; use Config;
$have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1"; $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1";
### temporarily disable threads if using the broken Moo version
use Moo;
$have_threads = 0 if $Moo::VERSION == 1.003000;
} }
warn "Running Slic3r under Perl >= 5.16 is not supported nor recommended\n" warn "Running Slic3r under Perl >= 5.16 is not supported nor recommended\n"

View file

@ -204,11 +204,18 @@ our $Options = {
}, },
'support_material_extruder' => { 'support_material_extruder' => {
label => 'Support material extruder', label => 'Support material extruder',
tooltip => 'The extruder to use when printing support material. This affects brim too.', tooltip => 'The extruder to use when printing support material. This affects brim and raft too.',
cli => 'support-material-extruder=i', cli => 'support-material-extruder=i',
type => 'i', type => 'i',
default => 1, default => 1,
}, },
'support_material_interface_extruder' => {
label => 'Support material interface extruder',
tooltip => 'The extruder to use when printing support material interface. This affects raft too.',
cli => 'support-material-interface-extruder=i',
type => 'i',
default => 1,
},
# filament options # filament options
'first_layer_bed_temperature' => { 'first_layer_bed_temperature' => {
@ -652,7 +659,7 @@ our $Options = {
type => 'select', type => 'select',
values => [qw(rectilinear rectilinear-grid honeycomb)], values => [qw(rectilinear rectilinear-grid honeycomb)],
labels => ['rectilinear', 'rectilinear grid', 'honeycomb'], labels => ['rectilinear', 'rectilinear grid', 'honeycomb'],
default => 'rectilinear', default => 'honeycomb',
}, },
'support_material_spacing' => { 'support_material_spacing' => {
label => 'Pattern spacing', label => 'Pattern spacing',
@ -676,7 +683,7 @@ our $Options = {
sidetext => 'layers', sidetext => 'layers',
cli => 'support-material-interface-layers=i', cli => 'support-material-interface-layers=i',
type => 'i', type => 'i',
default => 0, default => 3,
}, },
'support_material_interface_spacing' => { 'support_material_interface_spacing' => {
label => 'Interface pattern spacing', label => 'Interface pattern spacing',
@ -696,7 +703,7 @@ our $Options = {
}, },
'raft_layers' => { 'raft_layers' => {
label => 'Raft layers', label => 'Raft layers',
tooltip => 'Number of total raft layers to insert below the object(s).', tooltip => 'The object will be raised by this number of layers, and support material will be generated under it.',
sidetext => 'layers', sidetext => 'layers',
cli => 'raft-layers=i', cli => 'raft-layers=i',
type => 'i', type => 'i',
@ -814,7 +821,7 @@ END
}, },
'retract_lift' => { 'retract_lift' => {
label => 'Lift Z', label => 'Lift Z',
tooltip => 'If you set this to a positive value, Z is quickly raised every time a retraction is triggered.', tooltip => 'If you set this to a positive value, Z is quickly raised every time a retraction is triggered. When using multiple extruders, only the setting for the first extruder will be considered.',
sidetext => 'mm', sidetext => 'mm',
cli => 'retract-lift=f@', cli => 'retract-lift=f@',
type => 'f', type => 'f',
@ -832,7 +839,7 @@ END
default => [1], default => [1],
}, },
'wipe' => { 'wipe' => {
label => 'Wipe before retract', label => 'Wipe while retracting',
tooltip => 'This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders.', tooltip => 'This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders.',
cli => 'wipe!', cli => 'wipe!',
type => 'bool', type => 'bool',

View file

@ -44,6 +44,12 @@ sub wkt {
join ',', map "($_)", map { join ',', map "$_->[0] $_->[1]", @$_ } @$self; join ',', map "($_)", map { join ',', map "$_->[0] $_->[1]", @$_ } @$self;
} }
sub dump_perl {
my $self = shift;
return sprintf "[%s]",
join ',', map "[$_]", map { join ',', map "[$_->[0],$_->[1]]", @$_ } @$self;
}
sub offset { sub offset {
my $self = shift; my $self = shift;
return Slic3r::Geometry::Clipper::offset(\@$self, @_); return Slic3r::Geometry::Clipper::offset(\@$self, @_);
@ -108,15 +114,21 @@ sub clip_line {
]; ];
} }
sub simplify { sub simplify_as_polygons {
my $self = shift; my $self = shift;
my ($tolerance) = @_; my ($tolerance) = @_;
# it would be nice to have a multilinestring_simplify method in B::G::U # it would be nice to have a multilinestring_simplify method in B::G::U
my @simplified = Slic3r::Geometry::Clipper::simplify_polygons( return @{Slic3r::Geometry::Clipper::simplify_polygons(
[ map Boost::Geometry::Utils::linestring_simplify($_, $tolerance), @{$self->pp} ], [ map Boost::Geometry::Utils::linestring_simplify($_, $tolerance), @{$self->pp} ],
); )};
return @{ Slic3r::Geometry::Clipper::union_ex([ @simplified ]) }; }
sub simplify {
my $self = shift;
my ($tolerance) = @_;
return @{ Slic3r::Geometry::Clipper::union_ex([ $self->simplify_as_polygons($tolerance) ]) };
} }
sub area { sub area {
@ -146,7 +158,7 @@ sub medial_axis {
push @points, @$polygon; push @points, @$polygon;
} }
my $voronoi = Math::Geometry::Voronoi->new(points => \@points); my $voronoi = Math::Geometry::Voronoi->new(points => [ map $_->pp, @points ]);
$voronoi->compute; $voronoi->compute;
my @skeleton_lines = (); my @skeleton_lines = ();
@ -158,8 +170,8 @@ sub medial_axis {
next if $edge->[1] == -1 || $edge->[2] == -1; next if $edge->[1] == -1 || $edge->[2] == -1;
my ($a, $b); my ($a, $b);
$a = $vertices->[$edge->[1]]; $a = Slic3r::Point->new(@{$vertices->[$edge->[1]]});
$b = $vertices->[$edge->[2]]; $b = Slic3r::Point->new(@{$vertices->[$edge->[2]]});
next if !$self->encloses_point_quick($a) || !$self->encloses_point_quick($b); next if !$self->encloses_point_quick($a) || !$self->encloses_point_quick($b);

View file

@ -162,7 +162,6 @@ sub make_fill {
$surface, $surface,
density => $density, density => $density,
flow_spacing => $flow_spacing, flow_spacing => $flow_spacing,
dont_adjust => $is_bridge,
); );
next unless @polylines; next unless @polylines;
@ -190,8 +189,10 @@ sub make_fill {
} }
# add thin fill regions # add thin fill regions
push @fills, @{$layerm->thin_fills}; if (@{ $layerm->thin_fills }) {
push @fills_ordering_points, map $_->[0], @{$layerm->thin_fills}; push @fills, Slic3r::ExtrusionPath::Collection->new(@{$layerm->thin_fills});
push @fills_ordering_points, $fills[-1]->first_point;
}
# organize infill paths using a nearest-neighbor search # organize infill paths using a nearest-neighbor search
@fills = @fills[ chained_path(\@fills_ordering_points) ]; @fills = @fills[ chained_path(\@fills_ordering_points) ];

View file

@ -5,7 +5,7 @@ extends 'Slic3r::Fill::Base';
has 'cache' => (is => 'rw', default => sub {{}}); has 'cache' => (is => 'rw', default => sub {{}});
use Slic3r::Geometry qw(A B X Y scale unscale scaled_epsilon); use Slic3r::Geometry qw(A B X Y MIN scale unscale scaled_epsilon);
sub fill_surface { sub fill_surface {
my $self = shift; my $self = shift;
@ -16,73 +16,67 @@ sub fill_surface {
my $rotate_vector = $self->infill_direction($surface); my $rotate_vector = $self->infill_direction($surface);
$self->rotate_points($expolygon, $rotate_vector); $self->rotate_points($expolygon, $rotate_vector);
my $expolygon_off = $expolygon->offset_ex(scale $params{flow_spacing}/2)->[0]; my $flow_spacing = $params{flow_spacing};
return {} if !defined $expolygon_off; # skip some very small polygons (which shouldn't arrive here) my $min_spacing = scale $params{flow_spacing};
my $line_spacing = $min_spacing / $params{density};
my $line_oscillation = $line_spacing - $min_spacing;
my $is_line_pattern = $self->isa('Slic3r::Fill::Line');
my $bounding_box = $expolygon->bounding_box;
my $flow_spacing = $params{flow_spacing}; # define flow spacing according to requested density
my $min_spacing = scale $params{flow_spacing}; if ($params{density} == 1 && !$params{dont_adjust}) {
my $distance_between_lines = $min_spacing / $params{density}; $line_spacing = $self->adjust_solid_spacing(
my $line_oscillation = $distance_between_lines - $min_spacing; width => $bounding_box->size->[X],
my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); distance => $line_spacing,
);
$flow_spacing = unscale $line_spacing;
} else {
# extend bounding box so that our pattern will be aligned with other layers
# $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one
$bounding_box->extents->[X][MIN] -= $bounding_box->x_min;
$bounding_box->extents->[Y][MIN] -= $bounding_box->y_min;
}
my $cache_id = sprintf "d%s_s%.2f_a%.2f", # generate the basic pattern
$params{density}, $params{flow_spacing}, $rotate_vector->[0][0]; my $i = 0;
my $x = $bounding_box->x_min;
if (!$self->cache->{$cache_id}) { my $x_max = $bounding_box->x_max + scaled_epsilon;
# compute bounding box my @vertical_lines = ();
my $bounding_box; while ($x <= $x_max) {
{ my $vertical_line = Slic3r::Line->new([$x, $bounding_box->y_max], [$x, $bounding_box->y_min]);
my $bb_polygon = $self->bounding_box->polygon; if ($is_line_pattern && $i % 2) {
$bb_polygon->scale(sqrt 2); $vertical_line->[A][X] += $line_oscillation;
$self->rotate_points($bb_polygon, $rotate_vector); $vertical_line->[B][X] -= $line_oscillation;
$bounding_box = $bb_polygon->bounding_box;
} }
push @vertical_lines, $vertical_line;
# define flow spacing according to requested density $i++;
if ($params{density} == 1 && !$params{dont_adjust}) { $x += $line_spacing;
$distance_between_lines = $self->adjust_solid_spacing(
width => $bounding_box->size->[X],
distance => $distance_between_lines,
);
$flow_spacing = unscale $distance_between_lines;
}
# generate the basic pattern
my $x = $bounding_box->x_min;
my @vertical_lines = ();
for (my $i = 0; $x <= $bounding_box->x_max + scaled_epsilon; $i++) {
my $vertical_line = Slic3r::Line->new([$x, $bounding_box->y_max], [$x, $bounding_box->y_min]);
if ($is_line_pattern && $i % 2) {
$vertical_line->[A][X] += $line_oscillation;
$vertical_line->[B][X] -= $line_oscillation;
}
push @vertical_lines, $vertical_line;
$x += $distance_between_lines;
}
$self->cache->{$cache_id} = [@vertical_lines];
} }
# clip paths against a slightly offsetted expolygon, so that the first and last paths # clip paths against a slightly offsetted expolygon, so that the first and last paths
# are kept even if the expolygon has vertical sides # are kept even if the expolygon has vertical sides
# the minimum offset for preventing edge lines from being clipped is scaled_epsilon;
# however we use a larger offset to support expolygons with slightly skewed sides and
# not perfectly straight
my @polylines = map Slic3r::Polyline->new(@$_), my @polylines = map Slic3r::Polyline->new(@$_),
@{ Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection( @{ Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection(
[ map $_->pp, @{ $expolygon->offset_ex(scaled_epsilon) } ], [ map $_->pp, @{$expolygon->offset_ex($line_spacing*0.05)} ],
[ map $_->pp, @{ $self->cache->{$cache_id} } ], [ map $_->pp, @vertical_lines ],
) }; ) };
# connect lines # connect lines
unless ($params{dont_connect}) { unless ($params{dont_connect}) {
my ($expolygon_off) = @{$expolygon->offset_ex(scale $params{flow_spacing}/2)};
my $collection = Slic3r::Polyline::Collection->new( my $collection = Slic3r::Polyline::Collection->new(
polylines => [ @polylines ], polylines => [ @polylines ],
); );
@polylines = (); @polylines = ();
my $tolerance = 10 * scaled_epsilon; my $tolerance = 10 * scaled_epsilon;
my $diagonal_distance = $distance_between_lines * 2; my $diagonal_distance = $line_spacing * 2;
my $can_connect = $is_line_pattern my $can_connect = $is_line_pattern
? sub { ? sub {
($_[X] >= ($distance_between_lines - $line_oscillation) - $tolerance) && ($_[X] <= ($distance_between_lines + $line_oscillation) + $tolerance) ($_[X] >= ($line_spacing - $line_oscillation) - $tolerance) && ($_[X] <= ($line_spacing + $line_oscillation) + $tolerance)
&& $_[Y] <= $diagonal_distance && $_[Y] <= $diagonal_distance
} }
: sub { $_[X] <= $diagonal_distance && $_[Y] <= $diagonal_distance }; : sub { $_[X] <= $diagonal_distance && $_[Y] <= $diagonal_distance };

View file

@ -10,7 +10,7 @@ sub read_file {
my $tmesh = Slic3r::TriangleMesh::XS->new; my $tmesh = Slic3r::TriangleMesh::XS->new;
$tmesh->ReadSTLFile(Slic3r::encode_path($file)); $tmesh->ReadSTLFile(Slic3r::encode_path($file));
$tmesh->Repair; $tmesh->Repair;
my ($vertices, $facets) = @{$tmesh->ToPerl}; my ($vertices, $facets) = ($tmesh->vertices, $tmesh->facets);
my $model = Slic3r::Model->new; my $model = Slic3r::Model->new;
my $object = $model->add_object(vertices => $vertices, mesh_stats => $tmesh->stats); my $object = $model->add_object(vertices => $vertices, mesh_stats => $tmesh->stats);

View file

@ -3,13 +3,14 @@ use Moo;
use List::Util qw(min max first); use List::Util qw(min max first);
use Slic3r::ExtrusionPath ':roles'; use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y B); use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon points_coincide PI X Y B);
use Slic3r::Geometry::Clipper qw(union_ex); use Slic3r::Geometry::Clipper qw(union_ex);
use Slic3r::Surface ':types'; use Slic3r::Surface ':types';
has 'config' => (is => 'ro', required => 1); has 'config' => (is => 'ro', required => 1);
has 'extruders' => (is => 'ro', default => sub {0}, required => 1); has 'extruders' => (is => 'ro', default => sub {0}, required => 1);
has 'multiple_extruders' => (is => 'lazy'); has 'multiple_extruders' => (is => 'lazy');
has 'enable_loop_clipping' => (is => 'rw', default => sub {1});
has 'enable_wipe' => (is => 'lazy'); # at least one extruder has wipe enabled has 'enable_wipe' => (is => 'lazy'); # at least one extruder has wipe enabled
has 'layer_count' => (is => 'ro', required => 1 ); has 'layer_count' => (is => 'ro', required => 1 );
has 'layer' => (is => 'rw'); has 'layer' => (is => 'rw');
@ -118,22 +119,31 @@ sub change_layer {
return $gcode; return $gcode;
} }
# this method accepts Z in scaled coordinates # this method accepts Z in unscaled coordinates
sub move_z { sub move_z {
my $self = shift; my $self = shift;
my ($z, $comment) = @_; my ($z, $comment) = @_;
$z *= &Slic3r::SCALING_FACTOR;
$z += $self->config->z_offset; $z += $self->config->z_offset;
my $gcode = ""; my $gcode = "";
my $current_z = $self->z; my $current_z = $self->z;
if (!defined $current_z || $current_z != ($z + $self->lifted)) { if (!defined $self->z || $z > $self->z) {
# if we're going over the current Z we won't be lifted anymore
$self->lifted(0);
# this retraction may alter $self->z
$gcode .= $self->retract(move_z => $z) if $self->extruder->retract_layer_change; $gcode .= $self->retract(move_z => $z) if $self->extruder->retract_layer_change;
$self->speed('travel'); $self->speed('travel');
$gcode .= $self->G0(undef, $z, 0, $comment || ('move to next layer (' . $self->layer->id . ')')) $gcode .= $self->G0(undef, $z, 0, $comment || ('move to next layer (' . $self->layer->id . ')'))
unless ($current_z // -1) != ($self->z // -1); if !defined $self->z || abs($z - ($self->z - $self->lifted)) > epsilon;
$gcode .= $self->move_z_callback->() if defined $self->move_z_callback; $gcode .= $self->move_z_callback->() if defined $self->move_z_callback;
} elsif ($z < $self->z && $z > ($self->z - $self->lifted + epsilon)) {
# we're moving to a layer height which is greater than the nominal current one
# (nominal = actual - lifted) and less than the actual one. we're basically
# advancing to next layer, whose nominal Z is still lower than the previous
# layer Z with lift.
$self->lifted($self->z - $z);
} }
return $gcode; return $gcode;
@ -195,7 +205,8 @@ sub extrude_loop {
# clip the path to avoid the extruder to get exactly on the first point of the loop; # clip the path to avoid the extruder to get exactly on the first point of the loop;
# if polyline was shorter than the clipping distance we'd get a null polyline, so # if polyline was shorter than the clipping distance we'd get a null polyline, so
# we discard it in that case # we discard it in that case
$extrusion_path->clip_end(scale $extrusion_path->flow_spacing * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_SPACING); $extrusion_path->clip_end(scale $extrusion_path->flow_spacing * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_SPACING)
if $self->enable_loop_clipping;
return '' if !@{$extrusion_path->polyline}; return '' if !@{$extrusion_path->polyline};
my @paths = (); my @paths = ();
@ -346,8 +357,12 @@ sub travel_to {
# build a more complete configuration space # build a more complete configuration space
$travel->translate(-$self->shift_x, -$self->shift_y); $travel->translate(-$self->shift_x, -$self->shift_y);
# skip retraction if the travel move is contained in an island in the current layer
# *and* in an island in the upper layer (so that the ooze will not be visible)
if ($travel->length < scale $self->extruder->retract_before_travel if ($travel->length < scale $self->extruder->retract_before_travel
|| ($self->config->only_retract_when_crossing_perimeters && defined first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->upper_layer_slices}) || ($self->config->only_retract_when_crossing_perimeters
&& (first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->upper_layer_slices})
&& (first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}))
|| ($role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands_enclose_line($travel)) || ($role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands_enclose_line($travel))
) { ) {
$self->straight_once(0); $self->straight_once(0);
@ -505,6 +520,7 @@ sub unretract {
if ($self->lifted) { if ($self->lifted) {
$self->speed('travel'); $self->speed('travel');
$gcode .= sprintf ";AAA selfz = %s, lifted = %s\n", $self->z // 'nd', $self->lifted // 'nd';
$gcode .= $self->G0(undef, $self->z - $self->lifted, 0, 'restore layer Z'); $gcode .= $self->G0(undef, $self->z - $self->lifted, 0, 'restore layer Z');
$self->lifted(0); $self->lifted(0);
} }
@ -523,7 +539,7 @@ sub unretract {
sub reset_e { sub reset_e {
my $self = shift; my $self = shift;
return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware)$/; return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware|sailfish)$/;
$self->extruder->e(0) if $self->extruder; $self->extruder->e(0) if $self->extruder;
return sprintf "G92 %s0%s\n", $self->config->extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '') return sprintf "G92 %s0%s\n", $self->config->extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '')

View file

@ -18,8 +18,6 @@ sub append {
my $self = shift; my $self = shift;
my ($gcode, $obj_id, $layer_id, $print_z) = @_; my ($gcode, $obj_id, $layer_id, $print_z) = @_;
# TODO: differentiate $obj_id between normal layers and support layers
my $return = ""; my $return = "";
if (exists $self->last_z->{$obj_id} && $self->last_z->{$obj_id} != $print_z) { if (exists $self->last_z->{$obj_id} && $self->last_z->{$obj_id} != $print_z) {
$return = $self->flush; $return = $self->flush;

View file

@ -18,7 +18,7 @@ sub _build_spiralvase {
my $self = shift; my $self = shift;
return $Slic3r::Config->spiral_vase return $Slic3r::Config->spiral_vase
? Slic3r::GCode::SpiralVase->new ? Slic3r::GCode::SpiralVase->new(config => $self->gcodegen->config)
: undef; : undef;
} }
@ -27,6 +27,15 @@ sub process_layer {
my ($layer, $object_copies) = @_; my ($layer, $object_copies) = @_;
my $gcode = ""; my $gcode = "";
# check whether we're going to apply spiralvase logic
my $spiralvase = defined $self->spiralvase
&& ($layer->id > 0 || $Slic3r::Config->brim_width == 0)
&& ($layer->id >= $Slic3r::Config->skirt_height)
&& ($layer->id >= $Slic3r::Config->bottom_solid_layers);
# if we're going to apply spiralvase to this layer, disable loop clipping
$self->gcodegen->enable_loop_clipping(!$spiralvase);
if (!$self->second_layer_things_done && $layer->id == 1) { if (!$self->second_layer_things_done && $layer->id == 1) {
for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) { for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) {
$gcode .= $self->gcodegen->set_temperature($self->extruders->[$t]->temperature, 0, $t) $gcode .= $self->gcodegen->set_temperature($self->extruders->[$t]->temperature, 0, $t)
@ -88,17 +97,15 @@ sub process_layer {
# extrude support material before other things because it might use a lower Z # 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 # and also because we avoid travelling on other things when printing it
if ($self->print->has_support_material) { if ($self->print->has_support_material && $layer->isa('Slic3r::Layer::Support')) {
$gcode .= $self->gcodegen->move_z($layer->support_material_contact_z)
if ($layer->support_contact_fills && @{ $layer->support_contact_fills->paths });
$gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]);
if ($layer->support_contact_fills) {
$gcode .= $self->gcodegen->extrude_path($_, 'support material contact area')
for $layer->support_contact_fills->chained_path($self->gcodegen->last_pos);
}
$gcode .= $self->gcodegen->move_z($layer->print_z); $gcode .= $self->gcodegen->move_z($layer->print_z);
if ($layer->support_interface_fills) {
$gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_interface_extruder-1]);
$gcode .= $self->gcodegen->extrude_path($_, 'support material interface')
for $layer->support_interface_fills->chained_path($self->gcodegen->last_pos);
}
if ($layer->support_fills) { if ($layer->support_fills) {
$gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]);
$gcode .= $self->gcodegen->extrude_path($_, 'support material') $gcode .= $self->gcodegen->extrude_path($_, 'support material')
for $layer->support_fills->chained_path($self->gcodegen->last_pos); for $layer->support_fills->chained_path($self->gcodegen->last_pos);
} }
@ -116,7 +123,7 @@ sub process_layer {
} }
foreach my $region_id (@region_ids) { foreach my $region_id (@region_ids) {
my $layerm = $layer->regions->[$region_id]; my $layerm = $layer->regions->[$region_id] or next;
my $region = $self->print->regions->[$region_id]; my $region = $self->print->regions->[$region_id];
my @islands = (); my @islands = ();
@ -165,10 +172,7 @@ sub process_layer {
# apply spiral vase post-processing if this layer contains suitable geometry # apply spiral vase post-processing if this layer contains suitable geometry
$gcode = $self->spiralvase->process_layer($gcode, $layer) $gcode = $self->spiralvase->process_layer($gcode, $layer)
if defined $self->spiralvase if $spiralvase;
&& ($layer->id > 0 || $Slic3r::Config->brim_width == 0)
&& ($layer->id >= $Slic3r::Config->skirt_height)
&& ($layer->id >= $Slic3r::Config->bottom_solid_layers);
return $gcode; return $gcode;
} }

View file

@ -1,6 +1,8 @@
package Slic3r::GCode::SpiralVase; package Slic3r::GCode::SpiralVase;
use Moo; use Moo;
has 'config' => (is => 'ro', required => 1);
use Slic3r::Geometry qw(unscale); use Slic3r::Geometry qw(unscale);
sub process_layer { sub process_layer {
@ -16,7 +18,8 @@ sub process_layer {
my $new_gcode = ""; my $new_gcode = "";
my $layer_height = $layer->height; my $layer_height = $layer->height;
my $z = unscale($layer->print_z) - $layer_height; my $z = $layer->print_z + $self->config->z_offset - $layer_height;
my $newlayer = 0;
Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub { Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub {
my ($reader, $cmd, $args, $info) = @_; my ($reader, $cmd, $args, $info) = @_;
@ -24,11 +27,21 @@ sub process_layer {
my $line = $info->{raw}; my $line = $info->{raw};
$line =~ s/Z([^ ]+)/Z$z/; $line =~ s/Z([^ ]+)/Z$z/;
$new_gcode .= "$line\n"; $new_gcode .= "$line\n";
} elsif ($cmd eq 'G1' && !exists $args->{Z} && $info->{extruding} && $info->{dist_XY}) { $newlayer = 1;
$z += $info->{dist_XY} * $layer_height / $total_layer_length; } elsif ($cmd eq 'G1' && !exists $args->{Z} && $info->{dist_XY}) {
my $line = $info->{raw}; my $line = $info->{raw};
$line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e; if ($info->{extruding}) {
$new_gcode .= "$line\n"; $z += $info->{dist_XY} * $layer_height / $total_layer_length;
$line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e;
$new_gcode .= "$line\n";
} elsif ($newlayer) {
# remove the first travel move after layer change; extrusion
# will just blend to the first loop vertex
# TODO: should we adjust (stretch) E for the first loop segment?
$newlayer = 0;
} else {
$new_gcode .= "$line\n";
}
} else { } else {
$new_gcode .= "$info->{raw}\n"; $new_gcode .= "$info->{raw}\n";
} }

View file

@ -448,7 +448,7 @@ sub changescale {
return if !$scale || $scale == -1; return if !$scale || $scale == -1;
$self->{list}->SetItem($obj_idx, 2, "$scale%"); $self->{list}->SetItem($obj_idx, 2, "$scale%");
$object->scale($scale / 100); $object->changescale($scale / 100);
$self->arrange; $self->arrange;
} }
@ -1111,11 +1111,23 @@ sub _trigger_model_object {
} }
} }
sub changescale {
my $self = shift;
my ($scale) = @_;
my $variation = $scale / $self->scale;
foreach my $range (@{ $self->layer_height_ranges }) {
$range->[0] *= $variation;
$range->[1] *= $variation;
}
$self->scale($scale);
}
sub check_manifoldness { sub check_manifoldness {
my $self = shift; my $self = shift;
if ($self->mesh_stats) { if ($self->mesh_stats) {
if (first { $self->mesh_stats->{$_} > 0 } qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)) { if ($self->get_model_object->needed_repair) {
warn "Warning: the input file contains manifoldness errors. " warn "Warning: the input file contains manifoldness errors. "
. "Slic3r repaired it successfully by guessing what the correct shape should be, " . "Slic3r repaired it successfully by guessing what the correct shape should be, "
. "but you might still want to inspect the G-code before printing.\n"; . "but you might still want to inspect the G-code before printing.\n";
@ -1157,15 +1169,23 @@ sub make_thumbnail {
my $self = shift; my $self = shift;
my $mesh = $self->model_object->mesh; # $self->model_object is already aligned to origin my $mesh = $self->model_object->mesh; # $self->model_object is already aligned to origin
my $thumbnail = Slic3r::ExPolygon::Collection->new( my $thumbnail = Slic3r::ExPolygon::Collection->new;
# only simplify expolygons larger than the threshold if (@{$mesh->facets} <= 5000) {
grep @$_, $thumbnail->append(@{ $mesh->horizontal_projection });
map { ($_->area >= 1) ? $_->simplify(0.5) : $_ } } else {
(@{$mesh->facets} <= 5000) my $convex_hull = Slic3r::ExPolygon->new($self->convex_hull)->clone;
? @{$mesh->horizontal_projection} $convex_hull->scale(1/&Slic3r::SCALING_FACTOR);
: Slic3r::ExPolygon->new($self->convex_hull) $thumbnail->append($convex_hull);
); }
# remove polygons with area <= 1mm
my $area_threshold = Slic3r::Geometry::scale 1;
@{$thumbnail->expolygons} =
map $_->simplify(0.5),
grep $_->area >= $area_threshold,
@{$thumbnail->expolygons};
$thumbnail->scale(&Slic3r::SCALING_FACTOR);
$self->thumbnail($thumbnail); # ignored in multi-threaded environments $self->thumbnail($thumbnail); # ignored in multi-threaded environments
$self->free_model_object; $self->free_model_object;
@ -1188,6 +1208,7 @@ sub transformed_bounding_box {
my $bb = Slic3r::Geometry::BoundingBox->new_from_points($self->_apply_transform($self->convex_hull)); my $bb = Slic3r::Geometry::BoundingBox->new_from_points($self->_apply_transform($self->convex_hull));
$bb->extents->[Z] = $self->bounding_box->clone->extents->[Z]; $bb->extents->[Z] = $self->bounding_box->clone->extents->[Z];
$bb->extents->[Z][MAX] *= $self->scale;
return $bb; return $bb;
} }

View file

@ -4,6 +4,7 @@ use warnings;
use utf8; use utf8;
use File::Basename qw(basename dirname); use File::Basename qw(basename dirname);
use List::Util qw(min);
use Slic3r::Geometry qw(X Y); use Slic3r::Geometry qw(X Y);
use Wx qw(:dialog :filedialog :font :icon :id :misc :notebook :panel :sizer); use Wx qw(:dialog :filedialog :font :icon :id :misc :notebook :panel :sizer);
use Wx::Event qw(EVT_BUTTON); use Wx::Event qw(EVT_BUTTON);
@ -404,6 +405,10 @@ sub config {
$config->set('first_layer_height', $config->nozzle_diameter->[0]); $config->set('first_layer_height', $config->nozzle_diameter->[0]);
$config->set('avoid_crossing_perimeters', 1); $config->set('avoid_crossing_perimeters', 1);
$config->set('infill_every_layers', 10); $config->set('infill_every_layers', 10);
} else {
my $extruders_count = $self->{options_tabs}{printer}{extruders_count};
$config->set("${_}_extruder", min($config->get("${_}_extruder"), $extruders_count))
for qw(perimeter infill support_material support_material_interface);
} }
return $config; return $config;

View file

@ -526,7 +526,7 @@ sub build {
$self->add_options_page('Multiple Extruders', 'funnel.png', optgroups => [ $self->add_options_page('Multiple Extruders', 'funnel.png', optgroups => [
{ {
title => 'Extruders', title => 'Extruders',
options => [qw(perimeter_extruder infill_extruder support_material_extruder)], options => [qw(perimeter_extruder infill_extruder support_material_extruder support_material_interface_extruder)],
}, },
]); ]);

View file

@ -7,7 +7,7 @@ our @ISA = qw(Exporter);
our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex
diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND
JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt
intersection); intersection union);
use Math::Clipper 1.22 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Math::Clipper 1.22 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area);
use Slic3r::Geometry qw(scale); use Slic3r::Geometry qw(scale);
@ -25,6 +25,17 @@ sub safety_offset_ex {
@{Math::Clipper::ex_int_offset(_convert($polygons), $factor // (scale 1e-05), 100000, JT_MITER, 2)}; @{Math::Clipper::ex_int_offset(_convert($polygons), $factor // (scale 1e-05), 100000, JT_MITER, 2)};
} }
sub union {
my ($polygons, $jointype, $safety_offset) = @_;
$jointype = PFT_NONZERO unless defined $jointype;
$clipper->clear;
$clipper->add_subject_polygons($safety_offset ? safety_offset($polygons) : $polygons);
return [
map Slic3r::Polygon->new(@$_),
@{ $clipper->execute(CT_UNION, $jointype, $jointype) },
];
}
sub union_pt { sub union_pt {
my ($polygons, $jointype, $safety_offset) = @_; my ($polygons, $jointype, $safety_offset) = @_;
$jointype = PFT_NONZERO unless defined $jointype; $jointype = PFT_NONZERO unless defined $jointype;
@ -38,16 +49,6 @@ sub collapse_ex {
return offset2_ex($polygons, -$width/2, +$width/2); return offset2_ex($polygons, -$width/2, +$width/2);
} }
sub simplify_polygon {
my ($polygon, $pft) = @_;
return @{ Math::Clipper::simplify_polygon(_convert([$polygon])->[0], $pft // PFT_NONZERO) };
}
sub simplify_polygons {
my ($polygons, $pft) = @_;
return @{ Math::Clipper::simplify_polygons(_convert($polygons), $pft // PFT_NONZERO) };
}
sub traverse_pt { sub traverse_pt {
my ($polynodes) = @_; my ($polynodes) = @_;

View file

@ -11,49 +11,18 @@ has 'regions' => (is => 'ro', default => sub { [] });
has 'slicing_errors' => (is => 'rw'); has 'slicing_errors' => (is => 'rw');
has 'slice_z' => (is => 'ro', required => 1); # Z used for slicing in scaled coordinates has 'slice_z' => (is => 'ro', required => 1); # Z used for slicing in scaled coordinates
has 'print_z' => (is => 'ro', required => 1); # Z used for printing in scaled coordinates has 'print_z' => (is => 'ro', required => 1); # Z used for printing in unscaled coordinates
has 'height' => (is => 'ro', required => 1); # layer height in unscaled coordinates has 'height' => (is => 'ro', required => 1); # layer height in unscaled coordinates
# collection of expolygons generated by slicing the original geometry; # collection of expolygons generated by slicing the original geometry;
# also known as 'islands' (all regions and surface types are merged here) # also known as 'islands' (all regions and surface types are merged here)
has 'slices' => (is => 'rw', default => sub { Slic3r::ExPolygon::Collection->new }); has 'slices' => (is => 'rw', default => sub { Slic3r::ExPolygon::Collection->new });
# ordered collection of extrusion paths to fill surfaces for support material
has 'support_islands' => (is => 'rw');
has 'support_fills' => (is => 'rw');
has 'support_contact_fills' => (is => 'rw');
sub _trigger_id { sub _trigger_id {
my $self = shift; my $self = shift;
$_->_trigger_layer for @{$self->regions || []}; $_->_trigger_layer for @{$self->regions || []};
} }
# layer height of contact paths in unscaled coordinates
sub support_material_contact_height {
my $self = shift;
return $self->height if $self->id == 0;
# 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 contact in scaled coordinates
sub support_material_contact_z {
my $self = shift;
return $self->print_z - ($self->height - $self->support_material_contact_height) / &Slic3r::SCALING_FACTOR;
}
sub upper_layer_slices { sub upper_layer_slices {
my $self = shift; my $self = shift;
@ -81,7 +50,7 @@ sub make_slices {
my $slices = union_ex([ map $_->p, map @{$_->slices}, @{$self->regions} ]); my $slices = union_ex([ map $_->p, map @{$_->slices}, @{$self->regions} ]);
$self->slices->clear; $self->slices->clear;
$self->slices->append(map Slic3r::ExPolygon->new(@$_), @$slices); $self->slices->append(@$slices);
} }
sub make_perimeters { sub make_perimeters {
@ -97,4 +66,13 @@ sub support_islands_enclose_line {
return (first { $_->encloses_line($line) } @{$self->support_islands}) ? 1 : 0; return (first { $_->encloses_line($line) } @{$self->support_islands}) ? 1 : 0;
} }
package Slic3r::Layer::Support;
use Moo;
extends 'Slic3r::Layer';
# 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');
1; 1;

View file

@ -93,19 +93,21 @@ sub make_surfaces {
# detect thin walls by offsetting slices by half extrusion inwards # detect thin walls by offsetting slices by half extrusion inwards
if ($Slic3r::Config->thin_walls) { if ($Slic3r::Config->thin_walls) {
my $width = $self->perimeter_flow->scaled_width; $self->thin_walls([]);
# we use spacing here because there could be a case where
# the slice collapses with width but doesn't collapse with spacing,
# thus causing both perimeters and medial axis to be generated
my $width = $self->perimeter_flow->scaled_spacing;
my $diff = diff_ex( my $diff = diff_ex(
[ map $_->p, @{$self->slices} ], [ map $_->p, @{$self->slices} ],
offset2([ map @$_, map $_->expolygon, @{$self->slices} ], -$width, +$width), offset2([ map $_->p, @{$self->slices} ], -$width*0.5, +$width*0.5),
1, 1,
); );
$self->thin_walls->clear; my $area_threshold = $width ** 2;
if (@$diff) { if (@$diff = grep { $_->area > $area_threshold } @$diff) {
my $area_threshold = $self->perimeter_flow->scaled_spacing ** 2; @{$self->thin_walls} = map $_->medial_axis($width), @$diff;
@$diff = grep $_->area > ($area_threshold), @$diff; Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->thin_walls});
$self->thin_walls->append(map $_->medial_axis($self->perimeter_flow->scaled_width), @$diff);
Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->thin_walls}) if @{$self->thin_walls};
} }
} }
@ -144,7 +146,7 @@ sub _merge_loops {
Slic3r::debugf " %d surface(s) having %d holes detected from %d polylines\n", Slic3r::debugf " %d surface(s) having %d holes detected from %d polylines\n",
scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops); scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops);
return map Slic3r::Surface->new(expolygon => Slic3r::ExPolygon->new(@$_), surface_type => S_TYPE_INTERNAL), @$expolygons; return map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), @$expolygons;
} }
sub make_perimeters { sub make_perimeters {
@ -205,13 +207,12 @@ sub make_perimeters {
# we offset by half the perimeter spacing (to get to the actual infill boundary) # 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 # and then we offset back and forth by the infill spacing to only consider the
# non-collapsing regions # non-collapsing regions
# use a bogus surface_type
$self->fill_surfaces->append( $self->fill_surfaces->append(
map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_TOP), @{offset2_ex( @{offset2_ex(
[ map $_->simplify(&Slic3r::SCALED_RESOLUTION), @last ], [ map $_->simplify_as_polygons(&Slic3r::SCALED_RESOLUTION), @{union_ex(\@last)} ],
-($perimeter_spacing/2 + $infill_spacing), -($perimeter_spacing/2 + $infill_spacing),
+$infill_spacing, +$infill_spacing,
)} )},
); );
} }
@ -347,12 +348,11 @@ sub _fill_gaps {
my @infill = map @{$_->offset_ex(-0.5*$flow->scaled_width)}, @this_width; my @infill = map @{$_->offset_ex(-0.5*$flow->scaled_width)}, @this_width;
foreach my $expolygon (@infill) { foreach my $expolygon (@infill) {
my @paths = $filler->fill_surface( my ($params, @paths) = $filler->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNALSOLID), Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNALSOLID),
density => 1, density => 1,
flow_spacing => $flow->spacing, flow_spacing => $flow->spacing,
); );
my $params = shift @paths;
push @{ $self->thin_fills }, push @{ $self->thin_fills },
map { map {
@ -364,7 +364,19 @@ sub _fill_gaps {
role => EXTR_ROLE_GAPFILL, role => EXTR_ROLE_GAPFILL,
height => $self->height, height => $self->height,
flow_spacing => $params->{flow_spacing}, flow_spacing => $params->{flow_spacing},
), @paths; ),
# Split polylines into lines so that the chained_path() search
# at the final stage has more freedom and will choose starting
# points closer than last positions. OTOH, this will make such
# search slower. Probably, ExtrusionPath objects should support
# splitting nearby a given position so that we can choose the right
# entry point even in the middle of the path without needing a
# complex, slow, chained_path() search on all segments. TODO.
# Such logic will also avoid all the small travel moves that this
# line-splitting causes, and it will be applicable to other things
# too.
map Slic3r::Polyline->new(@$_)->lines,
@paths;
} }
} }
@ -379,195 +391,203 @@ sub _fill_gaps {
sub prepare_fill_surfaces { sub prepare_fill_surfaces {
my $self = shift; my $self = shift;
my @surfaces = @{$self->fill_surfaces};
# if no solid layers are requested, turn top/bottom surfaces to internal # if no solid layers are requested, turn top/bottom surfaces to internal
if ($Slic3r::Config->top_solid_layers == 0) { if ($Slic3r::Config->top_solid_layers == 0) {
$_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type == S_TYPE_TOP, @{$self->fill_surfaces}; for my $i (0..$#surfaces) {
next unless $surfaces[$i]->surface_type == S_TYPE_TOP;
$self->fill_surfaces->set_surface_type($i, S_TYPE_INTERNAL);
}
} }
if ($Slic3r::Config->bottom_solid_layers == 0) { if ($Slic3r::Config->bottom_solid_layers == 0) {
$_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; for my $i (0..$#surfaces) {
next unless $surfaces[$i]->surface_type == S_TYPE_BOTTOM;
$self->fill_surfaces->set_surface_type($i, S_TYPE_INTERNAL);
}
} }
# turn too small internal regions into solid regions according to the user setting # turn too small internal regions into solid regions according to the user setting
if ($Slic3r::Config->fill_density > 0) { if ($Slic3r::Config->fill_density > 0) {
my $min_area = scale scale $Slic3r::Config->solid_infill_below_area; # scaling an area requires two calls! 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}; for my $i (0..$#surfaces) {
$_->surface_type(S_TYPE_INTERNALSOLID) for @small; next unless $surfaces[$i]->surface_type == S_TYPE_INTERNAL && $surfaces[$i]->expolygon->contour->area <= $min_area;
Slic3r::debugf "identified %d small solid surfaces at layer %d\n", scalar(@small), $self->id if @small > 0; $self->fill_surfaces->set_surface_type($i, S_TYPE_INTERNALSOLID);
}
} }
} }
sub process_external_surfaces { sub process_external_surfaces {
my $self = shift; my $self = shift;
# enlarge top and bottom surfaces my $margin = scale 3; # TODO: ensure this is greater than the total thickness of the perimeters
{
# get all external surfaces my @bottom = ();
my @top = grep $_->surface_type == S_TYPE_TOP, @{$self->fill_surfaces}; foreach my $surface (grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}) {
my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; my $grown = $surface->expolygon->offset_ex(+$margin);
# if we're slicing with no infill, we can't extend external surfaces # detect bridge direction before merging grown surfaces otherwise adjacent bridges
# over non-existent infill # would get merged into a single one while they need different directions
my @fill_boundaries = $Slic3r::Config->fill_density > 0 # also, supply the original expolygon instead of the grown one, because in case
? @{$self->fill_surfaces} # of very thin (but still working) anchors, the grown expolygon would go beyond them
: grep $_->surface_type != S_TYPE_INTERNAL, @{$self->fill_surfaces}; my $angle = $self->id > 0
? $self->_detect_bridge_direction($surface->expolygon)
: undef;
# offset them and intersect the results with the actual fill boundaries push @bottom, map $surface->clone(expolygon => $_, bridge_angle => $angle), @$grown;
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, @fill_boundaries ],
1, # to ensure adjacent expolygons are unified
)};
@bottom = @{intersection_ex(
Slic3r::Geometry::Clipper::offset([ map $_->p, @bottom ], +$margin),
[ map $_->p, @fill_boundaries ],
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 $_->clone, map $group->[0]->clone(expolygon => $_), @{diff_ex(
[ map $_->p, @$group ],
[ map $_->p, @new_surfaces ],
)};
}
$self->fill_surfaces->clear;
$self->fill_surfaces->append(@new_surfaces);
} }
# detect bridge direction (skip bottom layer) my @top = ();
$self->_detect_bridges if $self->id > 0; foreach my $surface (grep $_->surface_type == S_TYPE_TOP, @{$self->fill_surfaces}) {
# give priority to bottom surfaces
my $grown = diff_ex(
$surface->expolygon->offset(+$margin),
[ map $_->p, @bottom ],
);
push @top, map $surface->clone(expolygon => $_), @$grown;
}
# if we're slicing with no infill, we can't extend external surfaces
# over non-existent infill
my @fill_boundaries = $Slic3r::Config->fill_density > 0
? @{$self->fill_surfaces}
: grep $_->surface_type != S_TYPE_INTERNAL, @{$self->fill_surfaces};
# intersect the grown surfaces with the actual fill boundaries
my @new_surfaces = ();
foreach my $group (Slic3r::Surface->group(@top, @bottom)) {
push @new_surfaces,
map $group->[0]->clone(expolygon => $_),
@{intersection_ex(
[ map $_->p, @$group ],
[ map $_->p, @fill_boundaries ],
1, # to ensure adjacent expolygons are unified
)};
}
# 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 $group->[0]->clone(expolygon => $_), @{diff_ex(
[ map $_->p, @$group ],
[ map $_->p, @new_surfaces ],
)};
}
@{$self->fill_surfaces} = @new_surfaces;
} }
sub _detect_bridges { sub _detect_bridge_direction {
my $self = shift; my $self = shift;
my ($expolygon) = @_;
my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; # surfaces my $grown = $expolygon->offset_ex(+$self->perimeter_flow->scaled_width);
my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons
foreach my $surface (@bottom) { # detect what edges lie on lower slices
# detect what edges lie on lower slices my @edges = (); # polylines
my @edges = (); # polylines foreach my $lower (@lower) {
foreach my $lower (@lower) { # turn bridge contour and holes into polylines and then clip them
# turn bridge contour and holes into polylines and then clip them # with each lower slice's contour
# with each lower slice's contour my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), map @$_, @$grown;
my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @{$surface->expolygon}; if (@clipped == 2) {
if (@clipped == 2) { # If the split_at_first_point() call above happens to split the polygon inside the clipping area
# 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
# 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.
# 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.
# This needs to be replaced with something way better. if (points_coincide($clipped[0][0], $clipped[-1][-1])) {
if (points_coincide($clipped[0][0], $clipped[-1][-1])) { @clipped = (Slic3r::Polyline->new(@{$clipped[-1]}, @{$clipped[0]}));
@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, @clipped; if (points_coincide($clipped[-1][0], $clipped[0][-1])) {
} @clipped = (Slic3r::Polyline->new(@{$clipped[0]}, @{$clipped[1]}));
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_$surface.svg",
expolygons => [ $surface->expolygon ],
red_expolygons => [ @lower ],
polylines => [ @edges ],
);
}
if (@edges == 2) {
my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges;
my @midpoints = map $_->midpoint, @chords;
my $line_between_midpoints = Slic3r::Line->new(@midpoints);
$bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction);
} elsif (@edges == 1) {
# TODO: this case includes both U-shaped bridges and plain overhangs;
# we need a trapezoidation algorithm to detect the actual bridged area
# and separate it from the overhang area.
# in the mean time, we're treating as overhangs all cases where
# our supporting edge is a straight line
if (@{$edges[0]} > 2) {
my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]);
$bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction);
} }
} elsif (@edges) {
# inset the bridge expolygon; we'll use this one to clip our test lines
my $inset = $surface->expolygon->offset_ex($self->infill_flow->scaled_width);
# detect anchors as intersection between our bridge expolygon and the lower slices
my $anchors = intersection_ex(
[ $surface->p ],
[ map @$_, @lower ],
1, # safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some @edges
);
# we'll now try several directions using a rudimentary visibility check:
# bridge in several directions and then sum the length of lines having both
# endpoints within anchors
my %directions = (); # angle => score
my $angle_increment = PI/36; # 5°
my $line_increment = $self->infill_flow->scaled_width;
for (my $angle = 0; $angle <= PI; $angle += $angle_increment) {
# rotate everything - the center point doesn't matter
$_->rotate($angle, [0,0]) for @$inset, @$anchors;
# generate lines in this direction
my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$anchors ]);
my @lines = ();
for (my $x = $bounding_box->x_min; $x <= $bounding_box->x_max; $x += $line_increment) {
push @lines, [ [$x, $bounding_box->y_min], [$x, $bounding_box->y_max] ];
}
# TODO: use a multi_polygon_multi_linestring_intersection() call
my @clipped_lines = map @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection($_, \@lines) }, @$inset;
# remove any line not having both endpoints within anchors
@clipped_lines = grep {
my $line = $_;
!(first { $_->encloses_point_quick($line->[A]) } @$anchors)
&& !(first { $_->encloses_point_quick($line->[B]) } @$anchors);
} @clipped_lines;
# sum length of bridged lines
$directions{-$angle} = sum(map Slic3r::Geometry::line_length($_), @clipped_lines) // 0;
}
# this could be slightly optimized with a max search instead of the sort
my @sorted_directions = sort { $directions{$a} <=> $directions{$b} } keys %directions;
# the best direction is the one causing most lines to be bridged
$bridge_angle = Slic3r::Geometry::rad2deg_dir($sorted_directions[-1]);
} }
push @edges, @clipped;
Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n",
$self->id, $bridge_angle if defined $bridge_angle;
$surface->bridge_angle($bridge_angle // -1);
} }
Slic3r::debugf "Found bridge on layer %d with %d support(s)\n", $self->id, scalar(@edges);
return undef if !@edges;
my $bridge_angle = undef;
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("bridge_$expolygon.svg",
expolygons => [ $expolygon ],
red_expolygons => [ @lower ],
polylines => [ @edges ],
);
}
if (@edges == 2) {
my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges;
my @midpoints = map $_->midpoint, @chords;
my $line_between_midpoints = Slic3r::Line->new(@midpoints);
$bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction);
} elsif (@edges == 1) {
# TODO: this case includes both U-shaped bridges and plain overhangs;
# we need a trapezoidation algorithm to detect the actual bridged area
# and separate it from the overhang area.
# in the mean time, we're treating as overhangs all cases where
# our supporting edge is a straight line
if (@{$edges[0]} > 2) {
my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]);
$bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction);
}
} elsif (@edges) {
# inset the bridge expolygon; we'll use this one to clip our test lines
my $inset = [ $expolygon->offset_ex($self->infill_flow->scaled_width) ];
# detect anchors as intersection between our bridge expolygon and the lower slices
my $anchors = intersection_ex(
[ @$grown ],
[ map @$_, @lower ],
undef,
1, # safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some @edges
);
# we'll now try several directions using a rudimentary visibility check:
# bridge in several directions and then sum the length of lines having both
# endpoints within anchors
my %directions = (); # angle => score
my $angle_increment = PI/36; # 5°
my $line_increment = $self->infill_flow->scaled_width;
for (my $angle = 0; $angle <= PI; $angle += $angle_increment) {
# rotate everything - the center point doesn't matter
$_->rotate($angle, [0,0]) for @$inset, @$anchors;
# generate lines in this direction
my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$anchors ]);
my @lines = ();
for (my $x = $bounding_box->x_min; $x <= $bounding_box->x_max; $x += $line_increment) {
push @lines, [ [$x, $bounding_box->y_min], [$x, $bounding_box->y_max] ];
}
# TODO: use a multi_polygon_multi_linestring_intersection() call
my @clipped_lines = map @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection($_, \@lines) }, @$inset;
# remove any line not having both endpoints within anchors
@clipped_lines = grep {
my $line = $_;
!(first { $_->encloses_point_quick($line->[A]) } @$anchors)
&& !(first { $_->encloses_point_quick($line->[B]) } @$anchors);
} @clipped_lines;
# sum length of bridged lines
$directions{-$angle} = sum(map Slic3r::Geometry::line_length($_), @clipped_lines) // 0;
}
# this could be slightly optimized with a max search instead of the sort
my @sorted_directions = sort { $directions{$a} <=> $directions{$b} } keys %directions;
# the best direction is the one causing most lines to be bridged
$bridge_angle = Slic3r::Geometry::rad2deg_dir($sorted_directions[-1]);
}
Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n",
$self->id, $bridge_angle if defined $bridge_angle;
return $bridge_angle;
} }
1; 1;

View file

@ -50,4 +50,9 @@ sub midpoint {
); );
} }
sub grow {
my $self = shift;
return Slic3r::Polyline->new(@$self[0,1,0])->grow(@_);
}
1; 1;

View file

@ -278,6 +278,11 @@ sub split_meshes {
} }
} }
sub print_info {
my $self = shift;
$_->print_info for @{$self->objects};
}
package Slic3r::Model::Region; package Slic3r::Model::Region;
use Moo; use Moo;
@ -287,6 +292,7 @@ has 'attributes' => (is => 'rw', default => sub { {} });
package Slic3r::Model::Object; package Slic3r::Model::Object;
use Moo; use Moo;
use File::Basename qw(basename);
use List::Util qw(first); use List::Util qw(first);
use Slic3r::Geometry qw(X Y Z MIN MAX move_points move_points_3D); use Slic3r::Geometry qw(X Y Z MIN MAX move_points move_points_3D);
use Storable qw(dclone); use Storable qw(dclone);
@ -396,6 +402,7 @@ sub scale {
} }
$self->_bounding_box->scale($factor) if defined $self->_bounding_box; $self->_bounding_box->scale($factor) if defined $self->_bounding_box;
$self->mesh_stats->{volume} *= ($factor**3) if defined $self->mesh_stats;
} }
sub rotate { sub rotate {
@ -425,6 +432,39 @@ sub check_manifoldness {
return (first { !$_->mesh->check_manifoldness } @{$self->volumes}) ? 0 : 1; return (first { !$_->mesh->check_manifoldness } @{$self->volumes}) ? 0 : 1;
} }
sub needed_repair {
my $self = shift;
return $self->mesh_stats
&& first { $self->mesh_stats->{$_} > 0 }
qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges);
}
sub print_info {
my $self = shift;
printf "Info about %s:\n", basename($self->input_file);
printf " size: x=%.3f y=%.3f z=%.3f\n", @{$self->size};
if (my $stats = $self->mesh_stats) {
printf " number of facets: %d\n", $stats->{number_of_facets};
printf " number of shells: %d\n", $stats->{number_of_parts};
printf " volume: %.3f\n", $stats->{volume};
if ($self->needed_repair) {
printf " needed repair: yes\n";
printf " degenerate facets: %d\n", $stats->{degenerate_facets};
printf " edges fixed: %d\n", $stats->{edges_fixed};
printf " facets removed: %d\n", $stats->{facets_removed};
printf " facets added: %d\n", $stats->{facets_added};
printf " facets reversed: %d\n", $stats->{facets_reversed};
printf " backwards edges: %d\n", $stats->{backwards_edges};
} else {
printf " needed repair: no\n";
}
} else {
printf " number of facets: %d\n", scalar(map @{$_->facets}, @{$self->volumes});
}
}
sub clone { dclone($_[0]) } sub clone { dclone($_[0]) }
package Slic3r::Model::Volume; package Slic3r::Model::Volume;

View file

@ -44,9 +44,11 @@ sub grow {
return $self->split_at_first_point->grow(@_); return $self->split_at_first_point->grow(@_);
} }
# NOTE that this will turn the polygon to ccw regardless of its
# original orientation
sub simplify { sub simplify {
my $self = shift; my $self = shift;
return Slic3r::Geometry::Clipper::simplify_polygon( $self->SUPER::simplify(@_) ); return @{Slic3r::Geometry::Clipper::simplify_polygons([ $self->SUPER::simplify(@_) ])};
} }
# this method subdivides the polygon segments to that no one of them # this method subdivides the polygon segments to that no one of them

View file

@ -43,14 +43,15 @@ sub length {
sub grow { sub grow {
my $self = shift; my $self = shift;
my ($distance, $scale, $joinType, $miterLimit) = @_; my ($distance, $scale, $joinType, $miterLimit) = @_;
$joinType //= JT_SQUARE; $joinType //= JT_SQUARE; # we override this one
$scale //= 100000; # we init these because we can't pass undef
$miterLimit //= 3;
my @points = @$self; my @points = @$self;
return map Slic3r::Polygon->new(@$_), return @{Slic3r::Geometry::Clipper::offset(
@{Slic3r::Geometry::Clipper::offset( [ Slic3r::Polygon->new(@points, CORE::reverse @points[1..($#points-1)]) ],
[ Slic3r::Polygon->new(@points, CORE::reverse @points[1..($#points-1)]) ], $distance, $scale, $joinType, $miterLimit,
$distance, $scale, $joinType, $miterLimit, )};
)};
} }
sub nearest_point_to { sub nearest_point_to {
@ -144,6 +145,38 @@ sub clip_start {
return (ref $self)->new($points); return (ref $self)->new($points);
} }
# this method returns a collection of points picked on the polygon contour
# so that they are evenly spaced according to the input distance
# (find a better name!)
sub regular_points {
my $self = shift;
my ($distance) = @_;
my @points = ($self->[0]);
my $len = 0;
for (my $i = 1; $i <= $#$self; $i++) {
my $point = $self->[$i];
my $segment_length = $point->distance_to($self->[$i-1]);
$len += $segment_length;
next if $len < $distance;
if ($len == $distance) {
push @points, $point;
$len = 0;
next;
}
my $take = $segment_length - ($len - $distance); # how much we take of this segment
my $new_point = Slic3r::Geometry::point_along_segment($self->[$i-1], $point, $take);
push @points, Slic3r::Point->new($new_point);
$i--;
$len = -$take;
}
return @points;
}
package Slic3r::Polyline::Collection; package Slic3r::Polyline::Collection;
use Moo; use Moo;

View file

@ -3,7 +3,7 @@ use Moo;
use File::Basename qw(basename fileparse); use File::Basename qw(basename fileparse);
use File::Spec; use File::Spec;
use List::Util qw(max first); use List::Util qw(min max first);
use Math::ConvexHull::MonotoneChain qw(convex_hull); use Math::ConvexHull::MonotoneChain qw(convex_hull);
use Slic3r::ExtrusionPath ':roles'; use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points
@ -73,6 +73,9 @@ sub _trigger_config {
$self->config->set('support_material_enforce_layers', 0); $self->config->set('support_material_enforce_layers', 0);
$self->config->set('retract_layer_change', [0]); # TODO: only apply this to the spiral layers $self->config->set('retract_layer_change', [0]); # TODO: only apply this to the spiral layers
} }
# force all retraction lift values to be the same
$self->config->set('retract_lift', [ map $self->config->retract_lift->[0], @{$self->config->retract_lift} ]);
} }
sub _build_has_support_material { sub _build_has_support_material {
@ -104,6 +107,9 @@ sub add_model {
# this mesh into distinct objects so that we reduce the complexity # this mesh into distinct objects so that we reduce the complexity
# of the graphs # of the graphs
# -- Disabling this one because there are too many legit objects having nested shells # -- Disabling this one because there are too many legit objects having nested shells
# -- It also caused a bug where plater rotation was applied to each single object by the
# -- code below (thus around its own center), instead of being applied to the whole
# -- thing before the split.
###$model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects; ###$model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects;
foreach my $object (@{ $model->objects }) { foreach my $object (@{ $model->objects }) {
@ -222,7 +228,7 @@ sub init_extruders {
# initialize all extruder(s) we need # initialize all extruder(s) we need
my @used_extruders = ( my @used_extruders = (
0, 0,
(map $self->config->get("${_}_extruder")-1, qw(perimeter infill support_material)), (map $self->config->get("${_}_extruder")-1, qw(perimeter infill support_material support_material_interface)),
(values %extruder_mapping), (values %extruder_mapping),
); );
for my $extruder_id (keys %{{ map {$_ => 1} @used_extruders }}) { for my $extruder_id (keys %{{ map {$_ => 1} @used_extruders }}) {
@ -257,6 +263,7 @@ sub init_extruders {
} }
# calculate support material flow # calculate support material flow
# Note: we should calculate a different flow for support material interface
if ($self->has_support_material) { if ($self->has_support_material) {
my $extruder = $self->extruders->[$self->config->support_material_extruder-1]; my $extruder = $self->extruders->[$self->config->support_material_extruder-1];
$self->support_material_flow($extruder->make_flow( $self->support_material_flow($extruder->make_flow(
@ -565,15 +572,19 @@ sub make_skirt {
# collect points from all layers contained in skirt height # collect points from all layers contained in skirt height
my @points = (); my @points = ();
foreach my $obj_idx (0 .. $#{$self->objects}) { foreach my $obj_idx (0 .. $#{$self->objects}) {
my $skirt_height = $Slic3r::Config->skirt_height; my $object = $self->objects->[$obj_idx];
$skirt_height = $self->objects->[$obj_idx]->layer_count if $skirt_height > $self->objects->[$obj_idx]->layer_count; my @layers = map $object->layers->[$_], 0..min($Slic3r::Config->skirt_height-1, $#{$object->layers});
my @layers = map $self->objects->[$obj_idx]->layers->[$_], 0..($skirt_height-1);
my @layer_points = ( my @layer_points = (
(map @$_, map @$_, map @{$_->slices}, @layers), (map @$_, map @$_, map @{$_->slices}, @layers),
(map @$_, map @{$_->thin_walls}, map @{$_->regions}, @layers), (map @$_, map @{$_->thin_walls}, map @{$_->regions}, @layers),
(map @{$_->polyline}, map @{$_->support_fills}, grep $_->support_fills, @layers),
); );
push @points, map move_points($_, @layer_points), @{$self->objects->[$obj_idx]->copies}; if (@{ $object->support_layers }) {
my @support_layers = map $object->support_layers->[$_], 0..min($Slic3r::Config->skirt_height-1, $#{$object->support_layers});
push @layer_points,
(map @{$_->unpack->polyline}, map @{$_->support_fills->paths}, grep $_->support_fills, @support_layers),
(map @{$_->unpack->polyline}, map @{$_->support_interface_fills->paths}, grep $_->support_interface_fills, @support_layers);
}
push @points, map move_points($_, @layer_points), @{$object->copies};
} }
return if @points < 3; # at least three points required for a convex hull return if @points < 3; # at least three points required for a convex hull
@ -627,13 +638,22 @@ sub make_brim {
my $grow_distance = $flow->scaled_width / 2; my $grow_distance = $flow->scaled_width / 2;
my @islands = (); # array of polygons my @islands = (); # array of polygons
foreach my $obj_idx (0 .. $#{$self->objects}) { foreach my $obj_idx (0 .. $#{$self->objects}) {
my $layer0 = $self->objects->[$obj_idx]->layers->[0]; my $object = $self->objects->[$obj_idx];
my $layer0 = $object->layers->[0];
my @object_islands = ( my @object_islands = (
(map $_->contour, @{$layer0->slices}), (map $_->contour, @{$layer0->slices}),
(map { $_->isa('Slic3r::Polygon') ? $_ : $_->grow($grow_distance) } map @{$_->thin_walls}, @{$layer0->regions}), (map { $_->isa('Slic3r::Polygon') ? $_ : $_->grow($grow_distance) } map @{$_->thin_walls}, @{$layer0->regions}),
(map $_->polyline->grow($grow_distance), map @{$_->support_fills}, grep $_->support_fills, $layer0),
); );
foreach my $copy (@{$self->objects->[$obj_idx]->copies}) { if (@{ $object->support_layers }) {
my $support_layer0 = $object->support_layers->[0];
push @object_islands,
(map $_->unpack->polyline->grow($grow_distance), @{$support_layer0->support_fills->paths})
if $support_layer0->support_fills;
push @object_islands,
(map $_->unpack->polyline->grow($grow_distance), @{$support_layer0->support_interface_fills->paths})
if $support_layer0->support_interface_fills;
}
foreach my $copy (@{$object->copies}) {
push @islands, map $_->clone->translate(@$copy), @object_islands; push @islands, map $_->clone->translate(@$copy), @object_islands;
} }
} }
@ -706,21 +726,26 @@ sub write_gcode {
print $fh "G21 ; set units to millimeters\n" if $Slic3r::Config->gcode_flavor ne 'makerware'; print $fh "G21 ; set units to millimeters\n" if $Slic3r::Config->gcode_flavor ne 'makerware';
print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers; print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers;
# write start commands to file # set bed temperature
printf $fh $gcodegen->set_bed_temperature($Slic3r::Config->first_layer_bed_temperature, 1), if ((my $temp = $Slic3r::Config->first_layer_bed_temperature) && $Slic3r::Config->start_gcode !~ /M(?:190|140)/i) {
if $Slic3r::Config->first_layer_bed_temperature && $Slic3r::Config->start_gcode !~ /M(?:190|140)/i; printf $fh $gcodegen->set_bed_temperature($temp, 1);
}
# set extruder(s) temperature before and after start G-code
my $print_first_layer_temperature = sub { my $print_first_layer_temperature = sub {
for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) { my ($wait) = @_;
printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 0, $t)
if $self->extruders->[$t]->first_layer_temperature; return if $Slic3r::Config->start_gcode =~ /M(?:109|104)/i;
for my $t (0 .. $#{$self->extruders}) {
my $temp = $self->extruders->[$t]->first_layer_temperature;
printf $fh $gcodegen->set_temperature($temp, $wait, $t) if $temp > 0;
} }
}; };
$print_first_layer_temperature->() if $Slic3r::Config->start_gcode !~ /M(?:109|104)/i; $print_first_layer_temperature->(0);
printf $fh "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->start_gcode); printf $fh "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->start_gcode);
for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) { $print_first_layer_temperature->(1);
printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 1, $t)
if $self->extruders->[$t]->first_layer_temperature && $Slic3r::Config->start_gcode !~ /M(?:109|104)/i; # set other general things
}
print $fh "G90 ; use absolute coordinates\n" if $Slic3r::Config->gcode_flavor ne 'makerware'; print $fh "G90 ; use absolute coordinates\n" if $Slic3r::Config->gcode_flavor ne 'makerware';
if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) { if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) {
printf $fh $gcodegen->reset_e; printf $fh $gcodegen->reset_e;
@ -795,7 +820,9 @@ sub write_gcode {
gcodegen => $gcodegen, gcodegen => $gcodegen,
); );
for my $layer (@{$self->objects->[$obj_idx]->layers}) { my $object = $self->objects->[$obj_idx];
my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers};
for my $layer (@layers) {
# if we are printing the bottom layer of an object, and we have already finished # if we are printing the bottom layer of an object, and we have already finished
# another one, set first layer temperatures. this happens before the Z move # another one, set first layer temperatures. this happens before the Z move
# is triggered, so machine has more time to reach such temperatures # is triggered, so machine has more time to reach such temperatures
@ -820,11 +847,13 @@ sub write_gcode {
my @obj_idx = chained_path([ map Slic3r::Point->new(@{$_->copies->[0]}), @{$self->objects} ]); my @obj_idx = chained_path([ map Slic3r::Point->new(@{$_->copies->[0]}), @{$self->objects} ]);
# sort layers by Z # sort layers by Z
my %layers = (); # print_z => [ layer, layer, layer ] by obj_idx my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx
foreach my $obj_idx (0 .. $#{$self->objects}) { foreach my $obj_idx (0 .. $#{$self->objects}) {
foreach my $layer (@{$self->objects->[$obj_idx]->layers}) { my $object = $self->objects->[$obj_idx];
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
$layers{ $layer->print_z } ||= []; $layers{ $layer->print_z } ||= [];
$layers{ $layer->print_z }[$obj_idx] = $layer; # turn this into [$layer] when merging support layers $layers{ $layer->print_z }[$obj_idx] ||= [];
push @{$layers{ $layer->print_z }[$obj_idx]}, $layer;
} }
} }
@ -834,13 +863,14 @@ sub write_gcode {
); );
foreach my $print_z (sort { $a <=> $b } keys %layers) { foreach my $print_z (sort { $a <=> $b } keys %layers) {
foreach my $obj_idx (@obj_idx) { foreach my $obj_idx (@obj_idx) {
next unless my $layer = $layers{$print_z}[$obj_idx]; foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) {
print $fh $buffer->append( print $fh $buffer->append(
$layer_gcode->process_layer($layer, $layer->object->copies), $layer_gcode->process_layer($layer, $layer->object->copies),
$layer->object."", $layer->object . ref($layer), # differentiate $obj_id between normal layers and support layers
$layer->id, $layer->id,
$layer->print_z, $layer->print_z,
); );
}
} }
} }
print $fh $buffer->flush; print $fh $buffer->flush;

View file

@ -1,11 +1,11 @@
package Slic3r::Print::Object; package Slic3r::Print::Object;
use Moo; use Moo;
use List::Util qw(min sum first); use List::Util qw(min max sum first);
use Slic3r::ExtrusionPath ':roles'; use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(Z PI scale unscale deg2rad rad2deg scaled_epsilon chained_path_points); use Slic3r::Geometry qw(Z PI scale unscale deg2rad rad2deg scaled_epsilon chained_path_points);
use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex offset collapse_ex use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex
offset2 diff intersection); offset offset_ex offset2);
use Slic3r::Surface ':types'; use Slic3r::Surface ':types';
has 'print' => (is => 'ro', weak_ref => 1, required => 1); has 'print' => (is => 'ro', weak_ref => 1, required => 1);
@ -14,9 +14,10 @@ has 'meshes' => (is => 'rw', default => sub { [] }); # by region_id
has 'size' => (is => 'rw', required => 1); # XYZ in scaled coordinates has 'size' => (is => 'rw', required => 1); # XYZ in scaled coordinates
has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates
has 'layers' => (is => 'rw', default => sub { [] }); has 'layers' => (is => 'rw', default => sub { [] });
has 'support_layers' => (is => 'rw', default => sub { [] });
has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ]
has 'fill_maker' => (is => 'lazy'); has 'fill_maker' => (is => 'lazy');
has '_z_table' => (is => 'lazy'); has '_slice_z_table' => (is => 'lazy');
sub BUILD { sub BUILD {
my $self = shift; my $self = shift;
@ -36,7 +37,7 @@ sub BUILD {
object => $self, object => $self,
id => $id, id => $id,
height => $height, height => $height,
print_z => scale $print_z, print_z => $print_z,
slice_z => -1, slice_z => -1,
); );
} }
@ -71,7 +72,7 @@ sub BUILD {
object => $self, object => $self,
id => $id, id => $id,
height => $height, height => $height,
print_z => scale $print_z, print_z => $print_z,
slice_z => scale $slice_z, slice_z => scale $slice_z,
); );
@ -84,7 +85,7 @@ sub _build_fill_maker {
return Slic3r::Fill->new(object => $self); return Slic3r::Fill->new(object => $self);
} }
sub _build__z_table { sub _build__slice_z_table {
my $self = shift; my $self = shift;
return Slic3r::Object::XS::ZTable->new([ map $_->slice_z, @{$self->layers} ]); return Slic3r::Object::XS::ZTable->new([ map $_->slice_z, @{$self->layers} ]);
} }
@ -105,7 +106,13 @@ sub layer_count {
sub get_layer_range { sub get_layer_range {
my $self = shift; my $self = shift;
return @{ $self->_z_table->get_range(@_) }; my ($min_z, $max_z) = @_;
my $min_layer = $self->_slice_z_table->lower_bound($min_z); # first layer whose slice_z is >= $min_z
return (
$min_layer,
$self->_slice_z_table->upper_bound($max_z, $min_layer)-1, # last layer whose slice_z is <= $max_z
);
} }
sub bounding_box { sub bounding_box {
@ -561,11 +568,14 @@ sub discover_horizontal_shells {
if ($Slic3r::Config->solid_infill_every_layers && $Slic3r::Config->fill_density > 0 if ($Slic3r::Config->solid_infill_every_layers && $Slic3r::Config->fill_density > 0
&& ($i % $Slic3r::Config->solid_infill_every_layers) == 0) { && ($i % $Slic3r::Config->solid_infill_every_layers) == 0) {
$_->surface_type(S_TYPE_INTERNALSOLID) my @surfaces = @{$layerm->fill_surfaces};
for grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces}; for my $i (0..$#surfaces) {
next unless $surfaces[$i]->surface_type == S_TYPE_INTERNAL;
$layerm->fill_surfaces->set_surface_type($i, S_TYPE_INTERNALSOLID);
}
} }
foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) { EXTERNAL: foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) {
# find slices of current type for current layer # find slices of current type for current layer
# get both slices and fill_surfaces before the former contains the perimeters area # get both slices and fill_surfaces before the former contains the perimeters area
# and the latter contains the enlarged external surfaces # and the latter contains the enlarged external surfaces
@ -576,7 +586,7 @@ sub discover_horizontal_shells {
my $solid_layers = ($type == S_TYPE_TOP) my $solid_layers = ($type == S_TYPE_TOP)
? $Slic3r::Config->top_solid_layers ? $Slic3r::Config->top_solid_layers
: $Slic3r::Config->bottom_solid_layers; : $Slic3r::Config->bottom_solid_layers;
for (my $n = ($type == S_TYPE_TOP) ? $i-1 : $i+1; NEIGHBOR: for (my $n = ($type == S_TYPE_TOP) ? $i-1 : $i+1;
abs($n - $i) <= $solid_layers-1; abs($n - $i) <= $solid_layers-1;
($type == S_TYPE_TOP) ? $n-- : $n++) { ($type == S_TYPE_TOP) ? $n-- : $n++) {
@ -587,12 +597,19 @@ sub discover_horizontal_shells {
# find intersection between neighbor and current layer's surfaces # find intersection between neighbor and current layer's surfaces
# intersections have contours and holes # intersections have contours and holes
my $new_internal_solid = intersection_ex( # we update $solid so that we limit the next neighbor layer to the areas that were
# found on this one - in other words, solid shells on one layer (for a given external surface)
# are always a subset of the shells found on the previous shell layer
# this approach allows for DWIM in hollow sloping vases, where we want bottom
# shells to be generated in the base but not in the walls (where there are many
# narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the
# upper perimeter as an obstacle and shell will not be propagated to more upper layers
my $new_internal_solid = $solid = intersection_ex(
[ map @$_, @$solid ], [ map @$_, @$solid ],
[ map $_->p, grep { ($_->surface_type == S_TYPE_INTERNAL) || ($_->surface_type == S_TYPE_INTERNALSOLID) } @neighbor_fill_surfaces ], [ map $_->p, grep { ($_->surface_type == S_TYPE_INTERNAL) || ($_->surface_type == S_TYPE_INTERNALSOLID) } @neighbor_fill_surfaces ],
1, 1,
); );
next if !@$new_internal_solid; next EXTERNAL if !@$new_internal_solid;
# make sure the new internal solid is wide enough, as it might get collapsed when # make sure the new internal solid is wide enough, as it might get collapsed when
# spacing is added in Fill.pm # spacing is added in Fill.pm
@ -600,25 +617,30 @@ sub discover_horizontal_shells {
my $margin = 3 * $layerm->solid_infill_flow->scaled_width; # require at least this size my $margin = 3 * $layerm->solid_infill_flow->scaled_width; # require at least this size
my $too_narrow = diff_ex( my $too_narrow = diff_ex(
[ map @$_, @$new_internal_solid ], [ map @$_, @$new_internal_solid ],
offset(offset([ map @$_, @$new_internal_solid ], -$margin), +$margin), offset2([ map @$_, @$new_internal_solid ], -$margin, +$margin),
1, 1,
); );
# if some parts are going to collapse, let's grow them and add the extra area to the neighbor layer # if some parts are going to collapse, use a different strategy according to fill density
# as well as to our original surfaces so that we support this additional area in the next shell too
if (@$too_narrow) { if (@$too_narrow) {
# consider the actual fill area if ($Slic3r::Config->fill_density > 0) {
my @fill_boundaries = $Slic3r::Config->fill_density > 0 # if we have internal infill, grow the collapsing parts and add the extra area to
? @neighbor_fill_surfaces # the neighbor layer as well as to our original surfaces so that we support this
: grep $_->surface_type != S_TYPE_INTERNAL, @neighbor_fill_surfaces; # additional area in the next shell too
# make sure our grown surfaces don't exceed the fill area # make sure our grown surfaces don't exceed the fill area
my @grown = map @$_, @{intersection_ex( my @grown = map @$_, @{intersection_ex(
offset([ map @$_, @$too_narrow ], +$margin), offset([ map @$_, @$too_narrow ], +$margin),
[ map $_->p, @fill_boundaries ], [ map $_->p, @neighbor_fill_surfaces ],
)}; )};
$new_internal_solid = union_ex([ @grown, (map @$_, @$new_internal_solid) ]); $new_internal_solid = $solid = union_ex([ @grown, (map @$_, @$new_internal_solid) ]);
$solid = union_ex([ @grown, (map @$_, @$solid) ]); } else {
# if we're printing a hollow object, we discard such small parts
$new_internal_solid = $solid = diff_ex(
[ map @$_, @$new_internal_solid ],
[ map @$_, @$too_narrow ],
);
}
} }
} }
@ -778,263 +800,452 @@ sub generate_support_material {
my $self = shift; my $self = shift;
return if $self->layer_count < 2; return if $self->layer_count < 2;
my $flow = $self->print->support_material_flow;
# how much we extend support around the actual contact area
#my $margin = $flow->scaled_width / 2;
my $margin = scale 3;
# increment used to reach $margin in steps to avoid trespassing thin objects
my $margin_step = $margin/3;
# if user specified a custom angle threshold, convert it to radians
my $threshold_rad; my $threshold_rad;
if ($Slic3r::Config->support_material_threshold) { if ($Slic3r::Config->support_material_threshold) {
$threshold_rad = deg2rad($Slic3r::Config->support_material_threshold + 1); # +1 makes the threshold inclusive $threshold_rad = deg2rad($Slic3r::Config->support_material_threshold + 1); # +1 makes the threshold inclusive
Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad); Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad);
} }
my $flow = $self->print->support_material_flow;
my $distance_from_object = 1.5 * $flow->scaled_width;
my $pattern_spacing = ($Slic3r::Config->support_material_spacing > $flow->spacing)
? $Slic3r::Config->support_material_spacing
: $flow->spacing;
# determine support regions in each layer (for upper layers) # shape of contact area
Slic3r::debugf "Detecting regions\n"; my $contact_loops = 1;
my %layers = (); # this represents the areas of each layer having to support upper layers (excluding interfaces) my $circle_distance = 3 * $flow->scaled_width;
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 $circle;
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) # TODO: make sure teeth between circles are compatible with support material flow
my @upper_layers_overhangs = (map [], 1..$Slic3r::Config->support_material_interface_layers); my $r = 1.5 * $flow->scaled_width;
for my $i (reverse 0 .. $#{$self->layers}) { $circle = Slic3r::Polygon->new(map [ $r * cos $_, $r * sin $_ ], (5*PI/3, 4*PI/3, PI, 2*PI/3, PI/3, 0));
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); # determine contact areas
my %contact = (); # contact_z => [ polygons ]
my %overhang = (); # contact_z => [ expolygons ] - this stores the actual overhang supported by each contact layer
for my $layer_id (1 .. $#{$self->layers}) {
my $layer = $self->layers->[$layer_id];
my $lower_layer = $self->layers->[$layer_id-1];
# detect overhangs and contact areas needed to support them
my (@overhang, @contact) = ();
foreach my $layerm (@{$layer->regions}) {
my $fw = $layerm->perimeter_flow->scaled_width;
my $diff;
my $layer = $self->layers->[$i]; # If a threshold angle was specified, use a different logic for detecting overhangs.
my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef; if (defined $threshold_rad || $layer_id <= $Slic3r::Config->support_material_enforce_layers) {
my $d = defined $threshold_rad
my @current_layer_offsetted_slices = map @{$_->offset_ex($distance_from_object)}, @{$layer->slices}; ? scale $lower_layer->height * ((cos $threshold_rad) / (sin $threshold_rad))
: 0;
# $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 ],
);
$layers_contact_areas{$i} = [
@{collapse_ex([ map @$_, @{$layers_contact_areas{$i}} ], $flow->scaled_width)},
];
# 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} }),
],
);
$layers_interfaces{$i} = [
@{collapse_ex([ map @$_, @{$layers_interfaces{$i}} ], $flow->scaled_width)},
];
# generate support material in current layer (for upper layers)
@current_support_regions = @{diff_ex(
[
(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 @$_, @current_layer_offsetted_slices),
(map @$_, @{ $layers_interfaces{$i} }),
],
);
$layers{$i} = [
@{collapse_ex([ map @$_, @{$layers{$i}} ], $flow->scaled_width)},
];
# 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) {
# 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
: $Slic3r::Config->support_material_threshold
? scale $lower_layer->height * ((cos $threshold_rad) / (sin $threshold_rad))
: $self->layers->[1]->regions->[0]->overhang_width;
@overhangs = map @{$_->offset_ex(+$distance)}, @{diff_ex( $diff = diff(
[ map @$_, @{$layer->slices} ], offset([ map $_->p, @{$layerm->slices} ], -$d),
[ map @$_, @{$lower_layer->slices} ], [ map @$_, @{$lower_layer->slices} ],
1, );
# only enforce spacing from the object ($fw/2) if the threshold angle
# is not too high: in that case, $d will be very small (as we need to catch
# very short overhangs), and such contact area would be eaten by the
# enforced spacing, resulting in high threshold angles to be almost ignored
$diff = diff(
offset($diff, $d - $fw/2),
[ map @$_, @{$lower_layer->slices} ],
) if $d > $fw/2;
} else {
$diff = diff(
offset([ map $_->p, @{$layerm->slices} ], -$fw/2),
[ map @$_, @{$lower_layer->slices} ],
);
# $diff now contains the ring or stripe comprised between the boundary of
# lower slices and the centerline of the last perimeter in this overhanging layer.
# Void $diff means that there's no upper perimeter whose centerline is
# outside the lower slice boundary, thus no overhang
}
next if !@$diff;
push @overhang, @{union_ex($diff)}; # NOTE: this is not the full overhang as it misses the outermost half of the perimeter width!
# Let's define the required contact area by using a max gap of half the upper
# extrusion width and extending the area according to the configured margin.
# We increment the area in steps because we don't want our support to overflow
# on the other side of the object (if it's very thin).
{
my @slices_margin = @{offset([ map @$_, @{$lower_layer->slices} ], $fw/2)};
for ($fw/2, map {$margin_step} 1..($margin / $margin_step)) {
$diff = diff(
offset($diff, $_),
\@slices_margin,
);
}
}
push @contact, @$diff;
}
next if !@contact;
# now apply the contact areas to the layer were they need to be made
{
# get the average nozzle diameter used on this layer
my @nozzle_diameters = map $_->nozzle_diameter,
map { $_->perimeter_flow, $_->solid_infill_flow }
@{$layer->regions};
my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters;
my $contact_z = $layer->print_z - $nozzle_diameter * 1.5;
###$contact_z = $layer->print_z - $layer->height;
# ignore this contact area if it's too low
next if $contact_z < $Slic3r::Config->first_layer_height;
$contact{$contact_z} = [ @contact ];
$overhang{$contact_z} = [ @overhang ];
}
}
my @contact_z = sort keys %contact;
# find object top surfaces
# we'll use them to clip our support and detect where does it stick
my %top = (); # print_z => [ expolygons ]
{
my $projection = [];
foreach my $layer (reverse @{$self->layers}) {
if (my @top = grep $_->surface_type == S_TYPE_TOP, map @{$_->slices}, @{$layer->regions}) {
# compute projection of the contact areas above this top layer
# first add all the 'new' contact areas to the current projection
# ('new' means all the areas that are lower than the last top layer
# we considered)
my $min_top = min(keys %top) // max(keys %contact);
push @$projection, map @{$contact{$_}}, grep { $_ > $layer->print_z && $_ < $min_top } keys %contact;
# now find whether any projection falls onto this top surface
my $touching = intersection($projection, [ map $_->p, @top ]);
if (@$touching) {
$top{ $layer->print_z } = $touching;
}
# remove the areas that touched from the projection that will continue on
# next, lower, top surfaces
$projection = diff($projection, $touching);
}
}
}
my @top_z = sort keys %top;
# we now know the upper and lower boundaries for our support material object
# (@contact_z and @top_z), so we can generate intermediate layers
my @support_layers = _compute_support_layers(\@contact_z, \@top_z, $Slic3r::Config, $flow);
# if we wanted to apply some special logic to the first support layers lying on
# object's top surfaces this is the place to detect them
# Let's now determine shells (interface layers) and normal support below them.
# Let's now fill each support layer by generating shells (interface layers) and
# clipping support area to the actual object boundaries.
my %interface = (); # layer_id => [ polygons ]
my %support = (); # layer_id => [ polygons ]
my $interface_layers = $Slic3r::Config->support_material_interface_layers;
for my $layer_id (0 .. $#support_layers) {
my $z = $support_layers[$layer_id];
my $this = $contact{$z} // next;
# count contact layer as interface layer
for (my $i = $layer_id; $i >= 0 && $i > $layer_id-$interface_layers; $i--) {
$z = $support_layers[$i];
# Compute interface area on this layer as diff of upper contact area
# (or upper interface area) and layer slices.
# This diff is responsible of the contact between support material and
# the top surfaces of the object. We should probably offset the top
# surfaces before performing the diff, but this needs investigation.
$this = $interface{$i} = diff(
[
@$this,
@{ $interface{$i} || [] },
],
[
@{ $top{$z} || [] },
],
1,
);
}
# determine what layers does our support belong to
for (my $i = $layer_id-$interface_layers; $i >= 0; $i--) {
$z = $support_layers[$i];
# Compute support area on this layer as diff of upper support area
# and layer slices.
$this = $support{$i} = diff(
[
@$this,
@{ $support{$i} || [] },
],
[
@{ $top{$z} || [] },
@{ $interface{$i} || [] },
],
1,
);
}
}
push @{$self->support_layers}, map Slic3r::Layer::Support->new(
object => $self,
id => $_,
height => ($_ == 0) ? $support_layers[$_] : ($support_layers[$_] - $support_layers[$_-1]),
print_z => $support_layers[$_],
slice_z => -1,
slices => [],
), 0 .. $#support_layers;
Slic3r::debugf "Generating patterns\n";
# prepare fillers
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 %fillers = (
interface => $self->fill_maker->filler('rectilinear'),
support => $self->fill_maker->filler($pattern),
);
my $interface_angle = $Slic3r::Config->support_material_angle + 90;
my $interface_spacing = $Slic3r::Config->support_material_interface_spacing + $flow->spacing;
my $interface_density = $interface_spacing == 0 ? 1 : $flow->spacing / $interface_spacing;
my $support_spacing = $Slic3r::Config->support_material_spacing + $flow->spacing;
my $support_density = $support_spacing == 0 ? 1 : $flow->spacing / $support_spacing;
my $process_layer = sub {
my ($layer_id) = @_;
my $result = { contact => [], interface => [], support => [] };
$contact{$layer_id} ||= [];
$interface{$layer_id} ||= [];
$support{$layer_id} ||= [];
# contact
if ((my $contact = $contact{$support_layers[$layer_id]}) && $contact_loops > 0) {
my $overhang = $overhang{$support_layers[$layer_id]};
$contact = [ grep $_->is_counter_clockwise, @$contact ];
# generate the outermost loop
my @loops0;
{
# find centerline of the external loop of the contours
my @external_loops = @{offset($contact, -$flow->scaled_width/2)};
# apply a pattern to the loop
my @positions = map Slic3r::Polygon->new(@$_)->split_at_first_point->regular_points($circle_distance), @external_loops;
@loops0 = @{diff(
[ @external_loops ],
[ map $circle->clone->translate(@$_), @positions ],
)}; )};
} }
push @upper_layers_overhangs, [@overhangs];
if ($Slic3r::debug) { # make more loops
printf "Layer %d (z = %.2f) has %d generic support areas, %d normal interface areas, %d contact areas\n", my @loops = @loops0;
$i, unscale($layer->print_z), scalar(@{$layers{$i}}), scalar(@{$layers_interfaces{$i}}), scalar(@{$layers_contact_areas{$i}}); for my $i (2..$contact_loops) {
my $d = ($i-1) * $flow->scaled_spacing;
push @loops, offset2(\@loops0, -$d -0.5*$flow->scaled_spacing, +0.5*$flow->scaled_spacing);
} }
}
}
return if !map @$_, values %layers;
# generate paths for the pattern that we're going to use
Slic3r::debugf "Generating patterns\n";
my $support_patterns = [];
my $support_interface_patterns = [];
{
# 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, values %layers_interfaces, values %layers_contact_areas ])};
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 = $self->fill_maker->filler($pattern);
my $make_pattern = sub {
my ($expolygon, $density) = @_;
my @paths = $filler->fill_surface( # clip such loops to the side oriented towards the object
Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL), @loops = map Slic3r::Polyline->new(@$_),
density => $density, @{ Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection(
flow_spacing => $flow->spacing, [ offset_ex([ map @$_, @$overhang ], +scale 3) ],
[ map Slic3r::Polygon->new(@$_)->split_at_first_point, @loops ],
) };
# subtract loops from the contact area to detect the remaining part
$interface{$layer_id} = intersection(
$interface{$layer_id},
[ offset2(\@loops0, -($contact_loops) * $flow->scaled_spacing, +0.5*$flow->scaled_spacing) ],
); );
my $params = shift @paths;
return map Slic3r::ExtrusionPath->new( # transform loops into ExtrusionPath objects
polyline => Slic3r::Polyline->new(@$_), @loops = map Slic3r::ExtrusionPath->pack(
polyline => $_,
role => EXTR_ROLE_SUPPORTMATERIAL, role => EXTR_ROLE_SUPPORTMATERIAL,
height => undef, flow_spacing => $flow->spacing,
flow_spacing => $params->{flow_spacing}, ), @loops;
), @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) { $result->{contact} = [ @loops ];
# 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 ];
}
} }
if (0) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("support_$_.svg",
polylines => [ map $_->polyline, map @$_, $support_patterns->[$_] ],
red_polylines => [ map $_->polyline, map @$_, $support_interface_patterns->[$_] ],
polygons => [ map @$_, @areas ],
) for 0 .. $#$support_patterns;
}
}
# apply the pattern to layers
Slic3r::debugf "Applying patterns\n";
{
my $clip_pattern = sub {
my ($layer_id, $expolygons, $height, $is_interface) = @_;
my @paths = ();
foreach my $expolygon (@$expolygons) {
push @paths,
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), # 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 $process_layer = sub {
my ($layer_id) = @_;
my $layer = $self->layers->[$layer_id];
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 = $self->fill_maker->filler('rectilinear');
$filler->angle($Slic3r::Config->support_material_angle + 90);
foreach my $expolygon (@$islands) {
my @paths = $filler->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon, surface_type => S_TYPE_INTERNAL),
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);
};
my %layer_paths = ();
my %layer_contact_paths = ();
my %layer_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) ];
}
return $result;
},
collect_cb => sub {
my $result = shift;
($layer_paths{$_}, $layer_contact_paths{$_}, $layer_islands{$_}) = @{$result->{$_}} for keys %$result;
},
no_threads_cb => sub {
($layer_paths{$_}, $layer_contact_paths{$_}, $layer_islands{$_}) = $process_layer->($_) for keys %layers;
},
);
foreach my $layer_id (keys %layer_paths) { # interface
my $layer = $self->layers->[$layer_id]; if (@{$interface{$layer_id}}) {
$layer->support_islands($layer_islands{$layer_id}); $fillers{interface}->angle($interface_angle);
$layer->support_fills(Slic3r::ExtrusionPath::Collection->new);
$layer->support_contact_fills(Slic3r::ExtrusionPath::Collection->new); # steal some space from support
$layer->support_fills->append(@{$layer_paths{$layer_id}}); $interface{$layer_id} = intersection(
$layer->support_contact_fills->append(@{$layer_contact_paths{$layer_id}}); [ offset($interface{$layer_id}, scale 3) ],
[ @{$interface{$layer_id}}, @{$support{$layer_id}} ],
);
$support{$layer_id} = diff(
$support{$layer_id},
$interface{$layer_id},
);
my @paths = ();
foreach my $expolygon (offset_ex($interface{$layer_id}, -$flow->scaled_width/2)) {
my @p = $fillers{interface}->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon),
density => $interface_density,
flow_spacing => $flow->spacing,
complete => 1,
);
my $params = shift @p;
push @paths, map Slic3r::ExtrusionPath->pack(
polyline => Slic3r::Polyline->new(@$_),
role => EXTR_ROLE_SUPPORTMATERIAL,
height => undef,
flow_spacing => $params->{flow_spacing},
), @p;
}
$result->{interface} = [ @paths ];
}
# support or flange
if (@{$support{$layer_id}}) {
my $filler = $fillers{support};
$filler->angle($angles[ ($layer_id) % @angles ]);
my $density = $support_density;
my $flow_spacing = $flow->spacing;
# TODO: use offset2_ex()
my $to_infill = offset_ex(union($support{$layer_id}), -$flow->scaled_width/2);
my @paths = ();
# base flange
if ($layer_id == 0) {
$filler = $fillers{interface};
$filler->angle($Slic3r::Config->support_material_angle + 90);
$density = 0.5;
$flow_spacing = $self->print->first_layer_support_material_flow->spacing;
} else {
# draw a perimeter all around support infill
# TODO: use brim ordering algorithm
push @paths, map Slic3r::ExtrusionPath->pack(
polyline => $_->split_at_first_point,
role => EXTR_ROLE_SUPPORTMATERIAL,
height => undef,
flow_spacing => $flow->spacing,
), map @$_, @$to_infill;
# TODO: use offset2_ex()
$to_infill = [ offset_ex([ map @$_, @$to_infill ], -$flow->scaled_spacing) ];
}
foreach my $expolygon (@$to_infill) {
my @p = $filler->fill_surface(
Slic3r::Surface->new(expolygon => $expolygon),
density => $density,
flow_spacing => $flow_spacing,
complete => 1,
);
my $params = shift @p;
push @paths, map Slic3r::ExtrusionPath->pack(
polyline => Slic3r::Polyline->new(@$_),
role => EXTR_ROLE_SUPPORTMATERIAL,
height => undef,
flow_spacing => $params->{flow_spacing},
), @p;
}
$result->{support} = [ @paths ];
}
# islands
$result->{islands} = union_ex([
@{$interface{$layer_id} || []},
@{$support{$layer_id} || []},
]);
return $result;
};
my $apply = sub {
my ($layer_id, $result) = @_;
my $layer = $self->support_layers->[$layer_id];
my $interface_collection = Slic3r::ExtrusionPath::Collection->new(paths => [ @{$result->{contact}}, @{$result->{interface}} ]);
$layer->support_interface_fills($interface_collection) if @{$interface_collection->paths} > 0;
my $support_collection = Slic3r::ExtrusionPath::Collection->new(paths => $result->{support});
$layer->support_fills($support_collection) if @{$support_collection->paths} > 0;
$layer->support_islands($result->{islands});
};
Slic3r::parallelize(
items => [ 0 .. $#{$self->support_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);
}
return $result;
},
collect_cb => sub {
my $result = shift;
$apply->($_, $result->{$_}) for keys %$result;
},
no_threads_cb => sub {
$apply->($_, $process_layer->($_)) for 0 .. $#{$self->support_layers};
},
);
}
sub _compute_support_layers {
my ($contact_z, $top_z, $config, $flow) = @_;
# quick table to check whether a given Z is a top surface
my %top = map { $_ => 1 } @$top_z;
# determine layer height for any non-contact layer
# we use max() to prevent many ultra-thin layers to be inserted in case
# layer_height > nozzle_diameter * 0.75
my $support_material_height = max($config->layer_height, $flow->nozzle_diameter * 0.75);
my @support_layers = sort { $a <=> $b } @$contact_z, @$top_z,
(map { $_ + $flow->nozzle_diameter } @$top_z);
# enforce first layer height
my $first_layer_height = $config->get_value('first_layer_height');
shift @support_layers while @support_layers && $support_layers[0] <= $first_layer_height;
unshift @support_layers, $first_layer_height;
for (my $i = $#support_layers; $i >= 0; $i--) {
my $target_height = $support_material_height;
if ($i > 0 && $top{ $support_layers[$i-1] }) {
$target_height = $flow->nozzle_diameter;
}
# enforce first layer height
if (($i == 0 && $support_layers[$i] > $target_height + $first_layer_height)
|| ($support_layers[$i] - $support_layers[$i-1] > $target_height + Slic3r::Geometry::epsilon)) {
splice @support_layers, $i, 0, ($support_layers[$i] - $target_height);
$i++;
} }
} }
# remove duplicates and make sure all 0.x values have the leading 0
{
my %sl = map { 1 * $_ => 1 } @support_layers;
@support_layers = sort { $a <=> $b } keys %sl;
}
return @support_layers;
} }
1; 1;

File diff suppressed because one or more lines are too long

View file

@ -25,7 +25,7 @@ sub export_svg {
my ($filename) = @_; my ($filename) = @_;
my $print_size = $self->print->size; my $print_size = $self->print->size;
$self->height(unscale max(map $_->print_z, map @{$_->layers}, @{$self->print->objects})); $self->height(max(map $_->print_z, map @{$_->layers}, @{$self->print->objects}));
my $svg = SVG->new( my $svg = SVG->new(
width => $self->scale * unscale($print_size->[X]), width => $self->scale * unscale($print_size->[X]),
height => $self->scale * $self->height, height => $self->scale * $self->height,
@ -58,7 +58,7 @@ sub export_svg {
); );
$group->( $group->(
filter => sub { $_[0]->support_fills, $_[0]->support_contact_fills }, filter => sub { $_[0]->isa('Slic3r::Layer::Support') ? ($_[0]->support_fills, $_[0]->support_interface_fills) : () },
style => { style => {
'stroke-width' => 1, 'stroke-width' => 1,
'stroke' => '#444444', 'stroke' => '#444444',
@ -80,19 +80,19 @@ sub _plot {
foreach my $object (@{$self->print->objects}) { foreach my $object (@{$self->print->objects}) {
foreach my $copy (@{$object->copies}) { foreach my $copy (@{$object->copies}) {
foreach my $layer (@{$object->layers}) { foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
# get all ExtrusionPath objects # get all ExtrusionPath objects
my @paths = my @paths =
map { $_->polyline->translate(@$copy); $_ } map { $_->polyline->translate(@$copy); $_ }
map { $_->isa('Slic3r::ExtrusionLoop') ? $_->split_at_first_point : $_ } map { $_->isa('Slic3r::ExtrusionLoop') ? $_->split_at_first_point : $_ }
map { $_->isa('Slic3r::ExtrusionPath::Collection') ? @{$_->paths} : $_ } map { $_->isa('Slic3r::ExtrusionPath::Collection') ? @$_ : $_ }
grep defined $_, grep defined $_,
$filter->($layer); $filter->($layer);
foreach my $path (@paths) { foreach my $path (@paths) {
foreach my $line ($path->lines) { foreach my $line (@{$path->lines}) {
my @intersections = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection( my @intersections = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection(
Slic3r::ExPolygon->new($line->grow(Slic3r::Geometry::scale $path->flow_spacing/2)), Slic3r::ExPolygon->new($line->grow(Slic3r::Geometry::scale $path->flow_spacing/2))->pp,
[ $self->line ], [ $self->line ],
) }; ) };
die "Intersection has more than two points!\n" if first { @$_ > 2 } @intersections; die "Intersection has more than two points!\n" if first { @$_ > 2 } @intersections;
@ -105,7 +105,7 @@ sub _plot {
# we're cutting the path in the longitudinal direction, so we've got a rectangle # we're cutting the path in the longitudinal direction, so we've got a rectangle
push @rectangles, { push @rectangles, {
'x' => $self->scale * unscale $line->[A][X], 'x' => $self->scale * unscale $line->[A][X],
'y' => $self->scale * $self->_y(unscale($layer->print_z)), 'y' => $self->scale * $self->_y($layer->print_z),
'width' => $self->scale * $width, 'width' => $self->scale * $width,
'height' => $self->scale * $radius * 2, 'height' => $self->scale * $radius * 2,
'rx' => $self->scale * $radius * 0.35, 'rx' => $self->scale * $radius * 0.35,
@ -114,7 +114,7 @@ sub _plot {
} else { } else {
push @circles, { push @circles, {
'cx' => $self->scale * (unscale($line->[A][X]) + $radius), 'cx' => $self->scale * (unscale($line->[A][X]) + $radius),
'cy' => $self->scale * $self->_y(unscale($layer->print_z) - $radius), 'cy' => $self->scale * $self->_y($layer->print_z - $radius),
'r' => $self->scale * $radius, 'r' => $self->scale * $radius,
}; };
} }
@ -124,7 +124,7 @@ sub _plot {
my $height = $path->height // $layer->height; my $height = $path->height // $layer->height;
{ {
'x' => $self->scale * unscale $_->[A][X], 'x' => $self->scale * unscale $_->[A][X],
'y' => $self->scale * $self->_y(unscale($layer->print_z)), 'y' => $self->scale * $self->_y($layer->print_z),
'width' => $self->scale * unscale(abs($_->[B][X] - $_->[A][X])), 'width' => $self->scale * unscale(abs($_->[B][X] - $_->[A][X])),
'height' => $self->scale * $height, 'height' => $self->scale * $height,
'rx' => $self->scale * $height * 0.35, 'rx' => $self->scale * $height * 0.35,

View file

@ -3,7 +3,7 @@ use Moo;
use List::Util qw(reduce min max first); use List::Util qw(reduce min max first);
use Slic3r::Geometry qw(X Y Z A B unscale same_point); use Slic3r::Geometry qw(X Y Z A B unscale same_point);
use Slic3r::Geometry::Clipper qw(union_ex); use Slic3r::Geometry::Clipper qw(union_ex offset);
use Storable; use Storable;
# public # public
@ -191,7 +191,7 @@ sub make_loops {
next unless defined $lines[$j] && defined $lines[$j][I_FACET_EDGE]; next unless defined $lines[$j] && defined $lines[$j][I_FACET_EDGE];
# are these facets adjacent? (sharing a common edge on this layer) # are these facets adjacent? (sharing a common edge on this layer)
if ($lines[$i][I_A_ID] == $lines[$j][I_B_ID] && $lines[$i][I_B_ID] == $lines[$j][I_A_ID]) { if ($lines[$i][I_A_ID] == $lines[$j][I_A_ID] && $lines[$i][I_B_ID] == $lines[$j][I_B_ID]) {
# if they are both oriented upwards or downwards (like a 'V') # if they are both oriented upwards or downwards (like a 'V')
# then we can remove both edges from this layer since it won't # then we can remove both edges from this layer since it won't
@ -205,7 +205,7 @@ sub make_loops {
# if one of them is oriented upwards and the other is oriented # if one of them is oriented upwards and the other is oriented
# downwards, let's only keep one of them (it doesn't matter which # downwards, let's only keep one of them (it doesn't matter which
# one since all 'top' lines were reversed at slicing) # one since all 'top' lines were reversed at slicing)
if ($lines[$i][I_FACET_EDGE] == FE_TOP && $lines[$j][I_FACET_EDGE] == FE_BOTTOM) { if ($lines[$i][I_FACET_EDGE] != $lines[$j][I_FACET_EDGE]) {
$lines[$j] = undef; $lines[$j] = undef;
last; last;
} }
@ -267,10 +267,10 @@ sub make_loops {
} }
# TODO: we should try to combine failed loops # TODO: we should try to combine failed loops
for (grep @$_ >= 3, @failed_loops) { for my $loop (grep @$_ >= 3, @failed_loops) {
push @polygons, Slic3r::Polygon->new(@$_); push @polygons, Slic3r::Polygon->new(map $_->[I_A], @$loop);
Slic3r::debugf " Discovered failed %s polygon of %d points\n", Slic3r::debugf " Discovered failed %s polygon of %d points\n",
($polygons[-1]->is_counter_clockwise ? 'ccw' : 'cw'), scalar(@$_) ($polygons[-1]->is_counter_clockwise ? 'ccw' : 'cw'), scalar(@$loop)
if $Slic3r::debug; if $Slic3r::debug;
} }
@ -301,6 +301,16 @@ sub scale {
} }
} }
sub scale_xyz {
my $self = shift;
my ($versor) = @_;
# transform vertex coordinates
foreach my $vertex (@{$self->vertices}) {
$vertex->[$_] *= $versor->[$_] for X,Y,Z;
}
}
sub move { sub move {
my $self = shift; my $self = shift;
my (@shift) = @_; my (@shift) = @_;
@ -540,20 +550,20 @@ sub split_mesh {
return @meshes; return @meshes;
} }
# this will return *scaled* expolygons, so it is expected to be run
# on unscaled meshes
sub horizontal_projection { sub horizontal_projection {
my $self = shift; my $self = shift;
my @f = (); my @f = ();
foreach my $facet (@{$self->facets}) { foreach my $facet (@{$self->facets}) {
push @f, Slic3r::Polygon->new(map [ @{$self->vertices->[$_]}[X,Y] ], @$facet); push @f, Slic3r::Polygon->new(
map [ map $_ / &Slic3r::SCALING_FACTOR, @{$self->vertices->[$_]}[X,Y] ], @$facet
);
} }
my $scale_vector = Math::Clipper::integerize_coordinate_sets({ bits => 32 }, @f);
$_->make_counter_clockwise for @f; # do this after scaling, as winding order might change while doing that $_->make_counter_clockwise for @f; # do this after scaling, as winding order might change while doing that
my $union = union_ex(Slic3r::Geometry::Clipper::offset(\@f, 10000)); return union_ex(\@f, 1);
$union = [ map $_->arrayref, @$union ];
Math::Clipper::unscale_coordinate_sets($scale_vector, $_) for @$union;
return $union;
} }
1; 1;

View file

@ -34,6 +34,7 @@ my %cli_options = ();
'export-svg' => \$opt{export_svg}, 'export-svg' => \$opt{export_svg},
'merge|m' => \$opt{merge}, 'merge|m' => \$opt{merge},
'repair' => \$opt{repair}, 'repair' => \$opt{repair},
'info' => \$opt{info},
); );
foreach my $opt_key (keys %{$Slic3r::Config::Options}) { foreach my $opt_key (keys %{$Slic3r::Config::Options}) {
my $cli = $Slic3r::Config::Options->{$opt_key}->{cli} or next; my $cli = $Slic3r::Config::Options->{$opt_key}->{cli} or next;
@ -120,6 +121,11 @@ if (@ARGV) { # slicing from command line
$_->rotate($config->rotate) for @{$model->objects}; $_->rotate($config->rotate) for @{$model->objects};
$model->arrange_objects($config); $model->arrange_objects($config);
if ($opt{info}) {
$model->print_info;
next;
}
my $print = Slic3r::Print->new(config => $config); my $print = Slic3r::Print->new(config => $config);
$print->add_model($model); $print->add_model($model);
$print->validate; $print->validate;
@ -156,7 +162,7 @@ EOF
Slic3r $Slic3r::VERSION is a STL-to-GCODE translator for RepRap 3D printers Slic3r $Slic3r::VERSION is a STL-to-GCODE translator for RepRap 3D printers
written by Alessandro Ranellucci <aar\@cpan.org> - http://slic3r.org/ written by Alessandro Ranellucci <aar\@cpan.org> - http://slic3r.org/
Usage: slic3r.pl [ OPTIONS ] file.stl Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ...
--help Output this usage screen and exit --help Output this usage screen and exit
--version Output the version of Slic3r and exit --version Output the version of Slic3r and exit
@ -166,7 +172,11 @@ Usage: slic3r.pl [ OPTIONS ] file.stl
-o, --output <file> File to output gcode to (by default, the file will be saved -o, --output <file> File to output gcode to (by default, the file will be saved
into the same directory as the input file using the into the same directory as the input file using the
--output-filename-format to generate the filename) --output-filename-format to generate the filename)
--repair Automatically repair given STL files and saves them as _fixed.obj
Non-slicing actions (no G-code will be generated):
--repair Repair given STL files and save them as <name>_fixed.obj
--info Output information about the supplied file(s) and exit
$j $j
GUI options: GUI options:
--no-plater Disable the plater tab --no-plater Disable the plater tab
@ -407,6 +417,8 @@ $j
--infill-extruder Extruder to use for infill (1+, default: 1) --infill-extruder Extruder to use for infill (1+, default: 1)
--support-material-extruder --support-material-extruder
Extruder to use for support material (1+, default: 1) Extruder to use for support material (1+, default: 1)
--support-material-interface-extruder
Extruder to use for support material interface (1+, default: 1)
EOF EOF
exit ($exit_code || 0); exit ($exit_code || 0);

View file

@ -2,7 +2,7 @@ use Test::More;
use strict; use strict;
use warnings; use warnings;
plan tests => 7; plan tests => 10;
BEGIN { BEGIN {
use FindBin; use FindBin;
@ -78,6 +78,28 @@ use Slic3r;
ok @simplified == 1, 'gear simplified to a single polygon'; ok @simplified == 1, 'gear simplified to a single polygon';
note sprintf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified[0]}); note sprintf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified[0]});
ok @{$simplified[0]} < $num_points, 'gear was further simplified using Douglas-Peucker'; ok @{$simplified[0]} < $num_points, 'gear was further simplified using Douglas-Peucker';
my @simplified_ex = Slic3r::ExPolygon->new($polygon)->simplify(10);
is_deeply [ map $_->pp, @simplified_ex ], [ [ map $_->pp, @simplified ] ], 'simplified polygon equals simplified expolygon';
}
{
my $square = Slic3r::Polygon->new( # ccw
[100, 100],
[200, 100],
[200, 200],
[100, 200],
);
my $hole_in_square = Slic3r::Polygon->new( # cw
[140, 140],
[140, 160],
[160, 160],
[160, 140],
);
my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square);
my @simplified = $hole_in_square->simplify;
is scalar(@simplified), 1, 'hole simplification returns one polygon';
ok $simplified[0]->is_counter_clockwise, 'hole simplification turns cw polygon into ccw polygon';
} }
{ {

View file

@ -2,7 +2,7 @@ use Test::More;
use strict; use strict;
use warnings; use warnings;
plan tests => 10; plan tests => 32;
BEGIN { BEGIN {
use FindBin; use FindBin;
@ -11,6 +11,7 @@ BEGIN {
use Slic3r; use Slic3r;
use Slic3r::Geometry qw(scale X Y); use Slic3r::Geometry qw(scale X Y);
use Slic3r::Geometry::Clipper qw(diff_ex);
use Slic3r::Surface qw(:types); use Slic3r::Surface qw(:types);
use Slic3r::Test; use Slic3r::Test;
@ -48,6 +49,59 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ }
} }
} }
{
my $test = sub {
my ($expolygon, $flow_spacing) = @_;
my $filler = Slic3r::Fill::Rectilinear->new(
bounding_box => $expolygon->bounding_box,
angle => 0,
);
my $surface = Slic3r::Surface->new(
surface_type => S_TYPE_BOTTOM,
expolygon => $expolygon,
);
my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => $flow_spacing, density => 1);
# check whether any part was left uncovered
my @grown_paths = map Slic3r::Polyline->new(@$_)->grow(scale $params->{flow_spacing}/2), @paths;
my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1);
# ignore very small dots
@$uncovered = grep $_->area > (scale $flow_spacing)**2, @$uncovered;
is scalar(@$uncovered), 0, 'solid surface is fully filled';
if (0 && @$uncovered) {
require "Slic3r/SVG.pm";
Slic3r::SVG::output(
"uncovered.svg",
expolygons => [$expolygon],
red_expolygons => $uncovered,
);
exit;
}
};
my $expolygon = Slic3r::ExPolygon->new([
[6883102, 9598327.01296997],
[6883102, 20327272.01297],
[3116896, 20327272.01297],
[3116896, 9598327.01296997],
]);
$test->($expolygon, 0.55);
for (1..20) {
$expolygon->scale(1.05);
$test->($expolygon, 0.55);
}
$expolygon = Slic3r::ExPolygon->new(
[[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]]
);
$test->($expolygon, 0.524341649025257);
}
{ {
my $collection = Slic3r::Polyline::Collection->new(polylines => [ my $collection = Slic3r::Polyline::Collection->new(polylines => [
Slic3r::Polyline->new([0,15], [0,18], [0,20]), Slic3r::Polyline->new([0,15], [0,18], [0,20]),

View file

@ -2,7 +2,7 @@ use Test::More;
use strict; use strict;
use warnings; use warnings;
plan tests => 24; plan tests => 25;
BEGIN { BEGIN {
use FindBin; use FindBin;
@ -183,3 +183,10 @@ is Slic3r::Geometry::can_connect_points(@$points, $polygons), 0, 'can_connect_po
} }
#========================================================== #==========================================================
{
my $line = Slic3r::Line->new([10,10], [20,10]);
is +($line->grow(5))[0]->area, Slic3r::Polygon->new([5,5], [25,5], [25,15], [5,15])->area, 'grow line';
}
#==========================================================

View file

@ -1,4 +1,4 @@
use Test::More tests => 5; use Test::More tests => 4;
use strict; use strict;
use warnings; use warnings;
@ -56,15 +56,4 @@ ok $test->(), "positive Z offset";
$config->set('z_offset', -0.8); $config->set('z_offset', -0.8);
ok $test->(), "negative Z offset"; 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__ __END__

View file

@ -48,8 +48,9 @@ my $test = sub {
} }
if ($info->{dist_Z} < 0) { if ($info->{dist_Z} < 0) {
fail 'going down only after lifting' if !$lifted; fail 'going down only after lifting' if !$lifted;
fail 'going down by the same amount of the lift' fail 'going down by the same amount of the lift or by the amount needed to get to next layer'
if !_eq($info->{dist_Z}, -$print->extruders->[$tool]->retract_lift); if !_eq($info->{dist_Z}, -$print->extruders->[$tool]->retract_lift)
&& !_eq($info->{dist_Z}, -$print->extruders->[$tool]->retract_lift + $conf->layer_height);
$lifted = 0; $lifted = 0;
} }
fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60; fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60;
@ -123,6 +124,8 @@ $retract_tests->(' (G0 and duplicate)');
$config->set('duplicate', 1); $config->set('duplicate', 1);
$config->set('g0', 0); $config->set('g0', 0);
$config->set('infill_extruder', 2); $config->set('infill_extruder', 2);
$retract_tests->(' (dual extruder)'); $config->set('skirts', 4);
$config->set('skirt_height', 3);
$retract_tests->(' (dual extruder with multiple skirt layers)');
__END__ __END__

View file

@ -1,4 +1,4 @@
use Test::More tests => 4; use Test::More tests => 10;
use strict; use strict;
use warnings; use warnings;
@ -84,4 +84,72 @@ use Slic3r::Test;
"correct number of top solid shells is generated in V-shaped object"; "correct number of top solid shells is generated in V-shaped object";
} }
{
my $config = Slic3r::Config->new_from_defaults;
# we need to check against one perimeter because this test is calibrated
# (shape, extrusion_width) so that perimeters cover the bottom surfaces of
# their lower layer - the test checks that shells are not generated on the
# above layers (thus 'across' the shadow perimeter)
# the test is actually calibrated to leave a narrow bottom region for each
# layer - we test that in case of fill_density = 0 such narrow shells are
# discarded instead of grown
$config->set('perimeters', 1);
$config->set('fill_density', 0);
$config->set('cooling', 0); # prevent speed alteration
$config->set('first_layer_speed', '100%'); # prevent speed alteration
$config->set('layer_height', 0.4);
$config->set('first_layer_height', '100%');
$config->set('extrusion_width', 0.5);
$config->set('bottom_solid_layers', 3);
$config->set('top_solid_layers', 0);
$config->set('solid_infill_speed', 99);
my $print = Slic3r::Test::init_print('V', config => $config);
my %layers = (); # Z => 1
Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
my ($self, $cmd, $args, $info) = @_;
$layers{$self->Z} = 1
if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60;
});
is scalar(keys %layers), $config->bottom_solid_layers,
"shells are not propagated across perimeters of the neighbor layer";
}
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('spiral_vase', 1);
$config->set('bottom_solid_layers', 0);
$config->set('skirts', 0);
$config->set('first_layer_height', '100%');
# TODO: this needs to be tested with a model with sloping edges, where starting
# points of each layer are not aligned - in that case we would test that no
# travel moves are left to move to the new starting point - in a cube, end
# points coincide with next layer starting points (provided there's no clipping)
my $test = sub {
my ($model_name, $description) = @_;
my $print = Slic3r::Test::init_print($model_name, config => $config);
my $travel_moves_after_first_extrusion = 0;
my $started_extruding = 0;
my @z_steps = ();
Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub {
my ($self, $cmd, $args, $info) = @_;
$started_extruding = 1 if $info->{extruding};
push @z_steps, ($args->{Z} - $self->Z)
if $started_extruding && exists $args->{Z};
$travel_moves_after_first_extrusion++
if $info->{travel} && $started_extruding && !exists $args->{Z};
});
is $travel_moves_after_first_extrusion, 0, "no gaps in spiral vase ($description)";
ok !(grep { $_ > $config->layer_height } @z_steps), "no gaps in Z ($description)";
};
$test->('20mm_cube', 'solid model');
$test->('40x10', 'hollow model');
$config->set('z_offset', -10);
$test->('20mm_cube', 'solid model with negative z-offset');
}
__END__ __END__

View file

@ -1,4 +1,4 @@
use Test::More tests => 1; use Test::More tests => 13;
use strict; use strict;
use warnings; use warnings;
@ -7,15 +7,58 @@ BEGIN {
use lib "$FindBin::Bin/../lib"; use lib "$FindBin::Bin/../lib";
} }
use List::Util qw(first);
use Slic3r; use Slic3r;
use Slic3r::Geometry qw(epsilon);
use Slic3r::Test; use Slic3r::Test;
{
my $config = Slic3r::Config->new_from_defaults;
my @contact_z = my @top_z = ();
my $test = sub {
my $flow = Slic3r::Flow->new(nozzle_diameter => $config->nozzle_diameter->[0], layer_height => $config->layer_height);
my @support_layers = Slic3r::Print::Object::_compute_support_layers(\@contact_z, \@top_z, $config, $flow);
is $support_layers[0], $config->first_layer_height,
'first layer height is honored';
is scalar(grep { $support_layers[$_]-$support_layers[$_-1] <= 0 } 1..$#support_layers), 0,
'no null or negative support layers';
is scalar(grep { $support_layers[$_]-$support_layers[$_-1] > $flow->nozzle_diameter + epsilon } 1..$#support_layers), 0,
'no layers thicker than nozzle diameter';
my $wrong_top_spacing = 0;
foreach my $top_z (@top_z) {
# find layer index of this top surface
my $layer_id = first { abs($support_layers[$_] - $top_z) < epsilon } 0..$#support_layers;
# check that first support layer above this top surface is spaced with nozzle diameter
$wrong_top_spacing = 1
if ($support_layers[$layer_id+1] - $support_layers[$layer_id]) != $flow->nozzle_diameter;
}
ok !$wrong_top_spacing, 'layers above top surfaces are spaced correctly';
};
$config->set('layer_height', 0.2);
$config->set('first_layer_height', 0.3);
@contact_z = (1.9);
@top_z = (1.1);
$test->();
$config->set('first_layer_height', 0.4);
$test->();
$config->set('layer_height', $config->nozzle_diameter->[0]);
$test->();
}
{ {
my $config = Slic3r::Config->new_from_defaults; my $config = Slic3r::Config->new_from_defaults;
$config->set('raft_layers', 3); $config->set('raft_layers', 3);
$config->set('brim_width', 6); $config->set('brim_width', 6);
$config->set('skirts', 0); $config->set('skirts', 0);
$config->set('support_material_extruder', 2); $config->set('support_material_extruder', 2);
$config->set('support_material_interface_extruder', 2);
$config->set('layer_height', 0.4); $config->set('layer_height', 0.4);
$config->set('first_layer_height', '100%'); $config->set('first_layer_height', '100%');
my $print = Slic3r::Test::init_print('20mm_cube', config => $config); my $print = Slic3r::Test::init_print('20mm_cube', config => $config);

View file

@ -1,5 +1,6 @@
#!/usr/bin/perl #!/usr/bin/perl
# This script dumps a STL file into Perl syntax for writing tests # This script dumps a STL file into Perl syntax for writing tests
# or dumps a test model into a STL file
use strict; use strict;
use warnings; use warnings;
@ -10,15 +11,24 @@ BEGIN {
} }
use Slic3r; use Slic3r;
use Slic3r::Test;
$|++; $|++;
$ARGV[0] or usage(1); $ARGV[0] or usage(1);
{ if (-e $ARGV[0]) {
my $model = Slic3r::Format::STL->read_file($ARGV[0]); my $model = Slic3r::Format::STL->read_file($ARGV[0]);
my $mesh = $model->mesh; my $mesh = $model->mesh;
printf "VERTICES = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->vertices}; printf "VERTICES = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->vertices};
printf "FACETS = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->facets}; printf "FACETS = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->facets};
exit 0;
} elsif ((my $model = Slic3r::Test::model($ARGV[0]))) {
$ARGV[1] or die "Missing writeable destination as second argument\n";
Slic3r::Format::STL->write_file($ARGV[1], $model);
printf "Model $ARGV[0] written to $ARGV[1]\n";
exit 0;
} else {
die "No such model exists\n";
} }
@ -27,6 +37,7 @@ sub usage {
print <<"EOF"; print <<"EOF";
Usage: dump-stl.pl file.stl Usage: dump-stl.pl file.stl
dump-stl.pl modelname file.stl
EOF EOF
exit ($exit_code || 0); exit ($exit_code || 0);
} }

View file

@ -1,54 +0,0 @@
#!/usr/bin/perl
# This script reads a file and outputs information about it
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use File::Basename qw(basename);
use Getopt::Long qw(:config no_auto_abbrev);
use Slic3r;
$|++;
my %opt = ();
{
my %options = (
'help' => sub { usage() },
);
GetOptions(%options) or usage(1);
$ARGV[0] or usage(1);
}
{
my $input_file = $ARGV[0];
die "This script doesn't support AMF yet\n" if $input_file =~ /\.amf$/i;
my $model;
$model = Slic3r::Format::STL->read_file($input_file) if $input_file =~ /\.stl$/i;
die "Unable to read file\n" if !$model;
printf "Info about %s:\n", basename($input_file);
my $mesh = $model->mesh;
$mesh->check_manifoldness;
printf " number of facets: %d\n", scalar @{$mesh->facets};
printf " size: x=%s y=%s z=%s\n", @{$mesh->size};
}
sub usage {
my ($exit_code) = @_;
print <<"EOF";
Usage: file_info.pl [ OPTIONS ] file.stl
--help Output this usage screen and exit
EOF
exit ($exit_code || 0);
}
__END__

View file

@ -25,7 +25,6 @@ use overload
package Slic3r::Polyline; package Slic3r::Polyline;
use overload use overload
'@{}' => sub { $_[0]->arrayref }, '@{}' => sub { $_[0]->arrayref },
'fallback' => 1,
'fallback' => 1; 'fallback' => 1;
package Slic3r::Polygon; package Slic3r::Polygon;
@ -128,7 +127,7 @@ sub clone {
my ($self, %args) = @_; my ($self, %args) = @_;
return (ref $self)->_new( return (ref $self)->_new(
delete $args{expolygon} // $self->expolygon->clone, delete $args{expolygon} // $self->expolygon,
delete $args{surface_type} // $self->surface_type, delete $args{surface_type} // $self->surface_type,
delete $args{thickness} // $self->thickness, delete $args{thickness} // $self->thickness,
delete $args{thickness_layers} // $self->thickness_layers, delete $args{thickness_layers} // $self->thickness_layers,

View file

@ -277,4 +277,19 @@ void union_ex(Slic3r::Polygons &subject, Slic3r::ExPolygons &retval, bool safety
_clipper(ClipperLib::ctUnion, subject, p, retval, safety_offset); _clipper(ClipperLib::ctUnion, subject, p, retval, safety_offset);
} }
void simplify_polygons(Slic3r::Polygons &subject, Slic3r::Polygons &retval)
{
// convert into Clipper polygons
ClipperLib::Polygons* input_subject = new ClipperLib::Polygons();
Slic3rPolygons_to_ClipperPolygons(subject, *input_subject);
ClipperLib::Polygons* output = new ClipperLib::Polygons();
ClipperLib::SimplifyPolygons(*input_subject, *output, ClipperLib::pftNonZero);
delete input_subject;
// convert into Slic3r polygons
ClipperPolygons_to_Slic3rPolygons(*output, retval);
delete output;
}
} }

View file

@ -62,6 +62,8 @@ void xor_ex(Slic3r::Polygons &subject, Slic3r::Polygons &clip, Slic3r::ExPolygon
void union_ex(Slic3r::Polygons &subject, Slic3r::ExPolygons &retval, bool safety_offset = false); void union_ex(Slic3r::Polygons &subject, Slic3r::ExPolygons &retval, bool safety_offset = false);
void simplify_polygons(Slic3r::Polygons &subject, Slic3r::Polygons &retval);
} }
#endif #endif

View file

@ -11,7 +11,6 @@ class ExPolygon
public: public:
Polygon contour; Polygon contour;
Polygons holes; Polygons holes;
bool in_collection;
void from_SV(SV* poly_sv); void from_SV(SV* poly_sv);
void from_SV_check(SV* poly_sv); void from_SV_check(SV* poly_sv);
SV* to_SV(); SV* to_SV();
@ -23,7 +22,6 @@ class ExPolygon
}; };
typedef std::vector<ExPolygon> ExPolygons; typedef std::vector<ExPolygon> ExPolygons;
typedef std::vector<ExPolygon*> ExPolygonsPtr;
} }

View file

@ -5,24 +5,24 @@ namespace Slic3r {
void void
ExPolygonCollection::scale(double factor) ExPolygonCollection::scale(double factor)
{ {
for (ExPolygonsPtr::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
(**it).scale(factor); (*it).scale(factor);
} }
} }
void void
ExPolygonCollection::translate(double x, double y) ExPolygonCollection::translate(double x, double y)
{ {
for (ExPolygonsPtr::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
(**it).translate(x, y); (*it).translate(x, y);
} }
} }
void void
ExPolygonCollection::rotate(double angle, Point* center) ExPolygonCollection::rotate(double angle, Point* center)
{ {
for (ExPolygonsPtr::iterator it = expolygons.begin(); it != expolygons.end(); ++it) { for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
(**it).rotate(angle, center); (*it).rotate(angle, center);
} }
} }

View file

@ -9,7 +9,7 @@ namespace Slic3r {
class ExPolygonCollection class ExPolygonCollection
{ {
public: public:
ExPolygonsPtr expolygons; ExPolygons expolygons;
void scale(double factor); void scale(double factor);
void translate(double x, double y); void translate(double x, double y);
void rotate(double angle, Point* center); void rotate(double angle, Point* center);

View file

@ -16,11 +16,9 @@ class Surface
unsigned short thickness_layers; // in layers unsigned short thickness_layers; // in layers
double bridge_angle; double bridge_angle;
unsigned short extra_perimeters; unsigned short extra_perimeters;
bool in_collection;
}; };
typedef std::vector<Surface> Surfaces; typedef std::vector<Surface> Surfaces;
typedef std::vector<Surface*> SurfacesPtr;
} }

View file

@ -8,7 +8,7 @@ namespace Slic3r {
class SurfaceCollection class SurfaceCollection
{ {
public: public:
SurfacesPtr surfaces; Surfaces surfaces;
}; };
} }

View file

@ -28,17 +28,17 @@ void TriangleMesh::ReadFromPerl(SV* vertices, SV* facets)
for (unsigned int i = 0; i < stl.stats.number_of_facets; i++) { for (unsigned int i = 0; i < stl.stats.number_of_facets; i++) {
AV* facet_av = (AV*)SvRV(*av_fetch(facets_av, i, 0)); AV* facet_av = (AV*)SvRV(*av_fetch(facets_av, i, 0));
stl_facet facet; stl_facet facet;
facet.normal.x = NULL; facet.normal.x = 0;
facet.normal.y = NULL; facet.normal.y = 0;
facet.normal.z = NULL; facet.normal.z = 0;
for (unsigned int v = 0; v <= 2; v++) { for (unsigned int v = 0; v <= 2; v++) {
AV* vertex_av = (AV*)SvRV(*av_fetch(vertices_av, SvIV(*av_fetch(facet_av, v, 0)), 0)); AV* vertex_av = (AV*)SvRV(*av_fetch(vertices_av, SvIV(*av_fetch(facet_av, v, 0)), 0));
facet.vertex[v].x = SvNV(*av_fetch(vertex_av, 0, 0)); facet.vertex[v].x = SvNV(*av_fetch(vertex_av, 0, 0));
facet.vertex[v].y = SvNV(*av_fetch(vertex_av, 1, 0)); facet.vertex[v].y = SvNV(*av_fetch(vertex_av, 1, 0));
facet.vertex[v].z = SvNV(*av_fetch(vertex_av, 2, 0)); facet.vertex[v].z = SvNV(*av_fetch(vertex_av, 2, 0));
} }
facet.extra[0] = NULL; facet.extra[0] = 0;
facet.extra[1] = NULL; facet.extra[1] = 0;
stl.facet_start[i] = facet; stl.facet_start[i] = facet;
} }

View file

@ -82,6 +82,7 @@ stl_check_facets_exact(stl_file *stl)
{ {
facet = stl->facet_start[i]; facet = stl->facet_start[i];
//If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet.
if( !memcmp(&facet.vertex[0], &facet.vertex[1], if( !memcmp(&facet.vertex[0], &facet.vertex[1],
sizeof(stl_vertex)) sizeof(stl_vertex))
|| !memcmp(&facet.vertex[1], &facet.vertex[2], || !memcmp(&facet.vertex[1], &facet.vertex[2],

View file

@ -121,9 +121,13 @@ stl_fix_normal_directions(stl_file *stl)
facet_num = 0; facet_num = 0;
//If normal vector is not within tolerance and backwards:
//Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances
// of it being wrong randomly are low if most of the triangles are right:
if(stl_check_normal_vector(stl, 0, 0) == 2) if(stl_check_normal_vector(stl, 0, 0) == 2)
stl_reverse_facet(stl, 0); stl_reverse_facet(stl, 0);
//Say that we've fixed this facet:
norm_sw[facet_num] = 1; norm_sw[facet_num] = 1;
/* edge_num = 0; /* edge_num = 0;
vnot = stl->neighbors_start[0].which_vertex_not[0]; vnot = stl->neighbors_start[0].which_vertex_not[0];
@ -133,19 +137,24 @@ stl_fix_normal_directions(stl_file *stl)
for(;;) for(;;)
{ {
/* Add neighbors_to_list. */ /* Add neighbors_to_list. */
//Add unconnected neighbors to the list:a
for(j = 0; j < 3; j++) for(j = 0; j < 3; j++)
{ {
/* Reverse the neighboring facets if necessary. */ /* Reverse the neighboring facets if necessary. */
if(stl->neighbors_start[facet_num].which_vertex_not[j] > 2) if(stl->neighbors_start[facet_num].which_vertex_not[j] > 2)
{ {
// If the facet has a neighbor that is -1, it means that edge isn't shared by another
// facet.
if(stl->neighbors_start[facet_num].neighbor[j] != -1) if(stl->neighbors_start[facet_num].neighbor[j] != -1)
{ {
stl_reverse_facet stl_reverse_facet
(stl, stl->neighbors_start[facet_num].neighbor[j]); (stl, stl->neighbors_start[facet_num].neighbor[j]);
} }
} }
//If this edge of the facet is connected:
if(stl->neighbors_start[facet_num].neighbor[j] != -1) if(stl->neighbors_start[facet_num].neighbor[j] != -1)
{ {
//If we haven't fixed this facet yet, add it to the list:
if(norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) if(norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1)
{ {
/* Add node to beginning of list. */ /* Add node to beginning of list. */
@ -170,14 +179,14 @@ stl_fix_normal_directions(stl_file *stl)
head->next = head->next->next; head->next = head->next->next;
free(temp); free(temp);
} }
else else //if we ran out of facets to fix:
{ {
/* All of the facets in this part have been fixed. */ /* All of the facets in this part have been fixed. */
stl->stats.number_of_parts += 1; stl->stats.number_of_parts += 1;
/* There are (checked-checked_before) facets */ /* There are (checked-checked_before) facets */
/* in part stl->stats.number_of_parts */ /* in part stl->stats.number_of_parts */
checked_before = checked; checked_before = checked;
if(checked == stl->stats.number_of_facets) if(checked >= stl->stats.number_of_facets)
{ {
/* All of the facets have been checked. Bail out. */ /* All of the facets have been checked. Bail out. */
break; break;
@ -350,7 +359,7 @@ void stl_normalize_vector(float v[])
min_normal_length = 0.000000000001; min_normal_length = 0.000000000001;
if(length < min_normal_length) if(length < min_normal_length)
{ {
v[0] = 1.0; v[0] = 0.0;
v[1] = 0.0; v[1] = 0.0;
v[2] = 0.0; v[2] = 0.0;
return; return;

View file

@ -198,6 +198,7 @@ stl_print_neighbors(stl_file *stl, char *file)
stl->neighbors_start[i].neighbor[2], stl->neighbors_start[i].neighbor[2],
(int)stl->neighbors_start[i].which_vertex_not[2]); (int)stl->neighbors_start[i].which_vertex_not[2]);
} }
fclose(fp);
} }
static void static void

View file

@ -4,9 +4,22 @@ use strict;
use warnings; use warnings;
use Slic3r::XS; use Slic3r::XS;
use Test::More tests => 1; use Test::More tests => 10;
my $table = Slic3r::Object::XS::ZTable->new([ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ]); my $table = Slic3r::Object::XS::ZTable->new([ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ]);
is_deeply $table->get_range(31, 61), [2, 6], 'get_layer_range';
is_deeply $table->get_range(39, 69), [2, 6], 'get_layer_range'; is_deeply $table->get_range(39, 69), [2, 6], 'get_layer_range';
is_deeply $table->get_range(30, 60), [2, 5], 'get_layer_range';
# upper_bound points to the first element that is greater than argument
is $table->upper_bound(30), 3, 'upper_bound';
is $table->upper_bound(31), 3, 'upper_bound';
is $table->upper_bound(39), 3, 'upper_bound';
is $table->upper_bound(39, 4), 4, 'upper_bound with offset';
# lower_bound points to the first element that is not less than argument
is $table->lower_bound(31), 3, 'lower_bound';
is $table->lower_bound(39), 3, 'lower_bound';
is $table->lower_bound(40), 3, 'lower_bound';
__END__ __END__

View file

@ -95,9 +95,7 @@ is_deeply $expolygon->clone->pp, [$square, $hole_in_square], 'clone';
my $exp = $collection->[0]; my $exp = $collection->[0];
$exp->scale(3); $exp->scale(3);
### we store a copy, not the original by reference isnt $collection->[0][0][0][0], $exp->[0][0][0], 'collection items are not returned by reference';
###is_deeply $expolygon->pp, $exp->pp, 'input is stored by reference in collection';
is_deeply $collection->[0]->pp, $exp->pp, 'collection items are returned by reference';
is_deeply $collection->[0]->clone->pp, $collection->[0]->pp, 'clone collection item'; is_deeply $collection->[0]->clone->pp, $collection->[0]->pp, 'clone collection item';
} }

View file

@ -52,7 +52,7 @@ is $surface->extra_perimeters, 2, 'extra_perimeters';
my $item = $collection->[0]; my $item = $collection->[0];
$item->surface_type(Slic3r::Surface::S_TYPE_INTERNAL); $item->surface_type(Slic3r::Surface::S_TYPE_INTERNAL);
is $item->surface_type, $collection->[0]->surface_type, 'changing item affects actual item'; isnt $item->surface_type, $collection->[0]->surface_type, 'collection returns copies of items';
} }
__END__ __END__

View file

@ -119,4 +119,12 @@ union_ex(subject, safety_offset = false)
OUTPUT: OUTPUT:
RETVAL RETVAL
Polygons
simplify_polygons(subject)
Polygons subject
CODE:
simplify_polygons(subject, RETVAL);
OUTPUT:
RETVAL
%} %}

View file

@ -6,8 +6,9 @@
%} %}
%name{Slic3r::ExPolygon} class ExPolygon { %name{Slic3r::ExPolygon} class ExPolygon {
~ExPolygon();
ExPolygon* clone() ExPolygon* clone()
%code{% const char* CLASS = "Slic3r::ExPolygon"; RETVAL = new ExPolygon(*THIS); RETVAL->in_collection = false; %}; %code{% const char* CLASS = "Slic3r::ExPolygon"; RETVAL = new ExPolygon(*THIS); %};
SV* arrayref() SV* arrayref()
%code{% RETVAL = THIS->to_SV(); %}; %code{% RETVAL = THIS->to_SV(); %};
SV* pp() SV* pp()
@ -29,14 +30,6 @@ ExPolygon::new(...)
OUTPUT: OUTPUT:
RETVAL RETVAL
void
ExPolygon::DESTROY()
CODE:
if (!THIS->in_collection) {
delete THIS;
THIS = NULL;
}
void void
ExPolygon::rotate(angle, center_sv) ExPolygon::rotate(angle, center_sv)
double angle; double angle;

View file

@ -24,9 +24,7 @@ ExPolygonCollection::new(...)
RETVAL->expolygons.resize(items-1); RETVAL->expolygons.resize(items-1);
for (unsigned int i = 1; i < items; i++) { for (unsigned int i = 1; i < items; i++) {
// Note: a COPY of the input is stored // Note: a COPY of the input is stored
RETVAL->expolygons[i-1] = new ExPolygon; RETVAL->expolygons[i-1].from_SV_check(ST(i));
RETVAL->expolygons[i-1]->from_SV_check(ST(i));
RETVAL->expolygons[i-1]->in_collection = true;
} }
OUTPUT: OUTPUT:
RETVAL RETVAL
@ -37,10 +35,8 @@ ExPolygonCollection::arrayref()
AV* av = newAV(); AV* av = newAV();
av_fill(av, THIS->expolygons.size()-1); av_fill(av, THIS->expolygons.size()-1);
int i = 0; int i = 0;
for (ExPolygonsPtr::iterator it = THIS->expolygons.begin(); it != THIS->expolygons.end(); ++it) { for (ExPolygons::iterator it = THIS->expolygons.begin(); it != THIS->expolygons.end(); ++it) {
SV* sv = newSV(0); av_store(av, i++, (*it).to_SV_ref());
sv_setref_pv( sv, "Slic3r::ExPolygon", *it );
av_store(av, i++, sv);
} }
RETVAL = newRV_noinc((SV*)av); RETVAL = newRV_noinc((SV*)av);
OUTPUT: OUTPUT:
@ -52,8 +48,8 @@ ExPolygonCollection::pp()
AV* av = newAV(); AV* av = newAV();
av_fill(av, THIS->expolygons.size()-1); av_fill(av, THIS->expolygons.size()-1);
int i = 0; int i = 0;
for (ExPolygonsPtr::iterator it = THIS->expolygons.begin(); it != THIS->expolygons.end(); ++it) { for (ExPolygons::iterator it = THIS->expolygons.begin(); it != THIS->expolygons.end(); ++it) {
av_store(av, i++, (*it)->to_SV_pureperl()); av_store(av, i++, (*it).to_SV_pureperl());
} }
RETVAL = newRV_noinc((SV*)av); RETVAL = newRV_noinc((SV*)av);
OUTPUT: OUTPUT:
@ -63,9 +59,8 @@ void
ExPolygonCollection::append(...) ExPolygonCollection::append(...)
CODE: CODE:
for (unsigned int i = 1; i < items; i++) { for (unsigned int i = 1; i < items; i++) {
ExPolygon* expolygon = new ExPolygon; ExPolygon expolygon;
expolygon->from_SV_check( ST(i) ); expolygon.from_SV_check( ST(i) );
expolygon->in_collection = true;
THIS->expolygons.push_back(expolygon); THIS->expolygons.push_back(expolygon);
} }

View file

@ -4,10 +4,12 @@
#include <myinit.h> #include <myinit.h>
#include "ZTable.hpp" #include "ZTable.hpp"
#include <vector> #include <vector>
#include <algorithm>
%} %}
%name{Slic3r::Object::XS::ZTable} class ZTable { %name{Slic3r::Object::XS::ZTable} class ZTable {
ZTable(std::vector<unsigned int>* z_array); ZTable(std::vector<unsigned int>* z_array);
~ZTable();
%{ %{
std::vector<unsigned int> std::vector<unsigned int>
@ -47,6 +49,25 @@ get_range(THIS, min_z, max_z)
} }
OUTPUT: OUTPUT:
RETVAL RETVAL
unsigned int
ZTable::lower_bound(z, offset = 0)
unsigned int z
unsigned int offset
CODE:
RETVAL = std::lower_bound(THIS->z.begin() + offset, THIS->z.end(), z) - THIS->z.begin();
OUTPUT:
RETVAL
unsigned int
ZTable::upper_bound(z, offset = 0)
unsigned int z
unsigned int offset
CODE:
RETVAL = std::upper_bound(THIS->z.begin() + offset, THIS->z.end(), z) - THIS->z.begin();
OUTPUT:
RETVAL
%} %}
}; };

View file

@ -6,6 +6,7 @@
%} %}
%name{Slic3r::Surface} class Surface { %name{Slic3r::Surface} class Surface {
~Surface();
ExPolygon* expolygon() ExPolygon* expolygon()
%code{% const char* CLASS = "Slic3r::ExPolygon"; RETVAL = new ExPolygon(THIS->expolygon); %}; %code{% const char* CLASS = "Slic3r::ExPolygon"; RETVAL = new ExPolygon(THIS->expolygon); %};
double thickness() double thickness()
@ -31,17 +32,11 @@ _new(CLASS, expolygon, surface_type, thickness, thickness_layers, bridge_angle,
RETVAL->thickness_layers = thickness_layers; RETVAL->thickness_layers = thickness_layers;
RETVAL->bridge_angle = bridge_angle; RETVAL->bridge_angle = bridge_angle;
RETVAL->extra_perimeters = extra_perimeters; RETVAL->extra_perimeters = extra_perimeters;
// we don't delete expolygon here because it's referenced by a Perl SV
// whose DESTROY will take care of destruction
OUTPUT: OUTPUT:
RETVAL RETVAL
void
Surface::DESTROY()
CODE:
if (!THIS->in_collection) {
delete THIS;
THIS = NULL;
}
SurfaceType SurfaceType
Surface::surface_type(...) Surface::surface_type(...)
CODE: CODE:

View file

@ -14,13 +14,12 @@
SurfaceCollection* SurfaceCollection*
SurfaceCollection::new(...) SurfaceCollection::new(...)
CODE: CODE:
RETVAL = new SurfaceCollection (); RETVAL = new SurfaceCollection;
// ST(0) is class name, others are surfaces // ST(0) is class name, others are surfaces
RETVAL->surfaces.resize(items-1); RETVAL->surfaces.resize(items-1);
for (unsigned int i = 1; i < items; i++) { for (unsigned int i = 1; i < items; i++) {
// Note: a COPY of the input is stored // Note: a COPY of the input is stored
RETVAL->surfaces[i-1] = (Surface *)SvIV((SV*)SvRV( ST(i) )); RETVAL->surfaces[i-1] = *(Surface *)SvIV((SV*)SvRV( ST(i) ));
RETVAL->surfaces[i-1]->in_collection = true;
} }
OUTPUT: OUTPUT:
RETVAL RETVAL
@ -31,9 +30,9 @@ SurfaceCollection::arrayref()
AV* av = newAV(); AV* av = newAV();
av_fill(av, THIS->surfaces.size()-1); av_fill(av, THIS->surfaces.size()-1);
int i = 0; int i = 0;
for (SurfacesPtr::iterator it = THIS->surfaces.begin(); it != THIS->surfaces.end(); ++it) { for (Surfaces::iterator it = THIS->surfaces.begin(); it != THIS->surfaces.end(); ++it) {
SV* sv = newSV(0); SV* sv = newSV(0);
sv_setref_pv( sv, "Slic3r::Surface", *it ); sv_setref_pv( sv, "Slic3r::Surface", new Surface(*it) );
av_store(av, i++, sv); av_store(av, i++, sv);
} }
RETVAL = newRV_noinc((SV*)av); RETVAL = newRV_noinc((SV*)av);
@ -44,9 +43,22 @@ void
SurfaceCollection::append(...) SurfaceCollection::append(...)
CODE: CODE:
for (unsigned int i = 1; i < items; i++) { for (unsigned int i = 1; i < items; i++) {
THIS->surfaces.push_back((Surface *)SvIV((SV*)SvRV( ST(i) ))); THIS->surfaces.push_back(*(Surface *)SvIV((SV*)SvRV( ST(i) )));
THIS->surfaces.back()->in_collection = true;
} }
void
SurfaceCollection::replace(index, surface)
int index
Surface* surface
CODE:
THIS->surfaces[index] = *surface;
void
SurfaceCollection::set_surface_type(index, surface_type)
int index
SurfaceType surface_type;
CODE:
THIS->surfaces[index].surface_type = surface_type;
%} %}
}; };

View file

@ -44,8 +44,9 @@ OUTPUT
T_ARRAYREF T_ARRAYREF
AV* av = newAV(); AV* av = newAV();
$arg = newRV_noinc((SV*)av); $arg = newRV_noinc((SV*)av);
const unsigned int len = $var.size(); av_extend(av, $var.size()-1);
av_extend(av, len-1); int i = 0;
for (unsigned int i = 0; i < len; i++) { for (${type}::iterator it = $var.begin(); it != $var.end(); ++it) {
av_store(av, i, ${var}[i].to_SV_ref()); av_store(av, i++, (*it).to_SV_ref());
} }
$var.clear();